Как передать экземпляр объекта .NET COM из Delphi в другой объект .NET COM? - PullRequest
1 голос
/ 18 октября 2010

У меня есть устаревшее приложение, написанное на Delphi 7. Мы добавляем новые модули в приложение. Модули написаны на Visual Studio 2010, .NET 4, C # и доступны приложению через COM.

Я успешно определил класс, зарегистрировал сборку, экспортировал библиотеку типов, импортировал библиотеку типов в Delphi, создал COM-клиент в Delphi и выполнил модуль. Теперь начинается сложная часть: я хочу передать другой объект (который был определен-зарегистрирован-экспортирован-бла-бла-бла, как указано выше) в качестве параметра методу первого модуля.

.NET

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("11111111-1111-1111-1111-AAAAAAAAAAAA")]
public interface IUserMaintenance
{
    bool AssignUser(IUserInfo);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("11111111-1111-1111-1111-BBBBBBBBBBBB")]
public class UserMaintenance: IUserMaintenance
{
    private IUserInfo _UserInfo;

    public bool AssignUser(IUserInfo userInfo)
    {
        _UserInfo = userInfo;
        LoadUser();
    }

    private void LoadUser()
    {
        //load user data from database using _UserInfo.UserName
    }
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("22222222-2222-2222-2222-AAAAAAAAAAAA")]
public interface IUserInfo
{
    void Initialize(string userName);
    string UserName { get; }
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("22222222-2222-2222-2222-BBBBBBBBBBBB")]
public class UserInfo: IUserInfo
{
    public string UserName { get; private set };

    public void Initialize(string userName)
    {
        UserName = userName;
    }
}

Предполагая, что у меня есть отдельные классы, реализующие каждый интерфейс, и сборки, содержащие эти классы, успешно компилируются и регистрируются, я импортирую библиотеки типов в Delphi, создавая UserMaintenance_TLB.pas и UserInfo_TLB.pas. Однако я вижу что-то необнаруженное: хотя интерфейсы, которые я определил в .NET, существуют (те, которые начинаются с «I»), Delphi сгенерировал другой набор интерфейсов (те, которые начинаются с «_»). Delphi не использует I-интерфейсы, которые я объявил в .NET вообще.

Delphi

UserMaintenance_TLB.pas

// *********************************************************************//
// Forward declaration of types defined in TypeLibrary                    
// *********************************************************************//
  IUserMaintenance = interface;
  IUserMaintenanceDisp = dispinterface;
  _UserMaintenance = interface;
  _UserMaintenanceDisp = dispinterface;

UserInfo_TLB.pas

// *********************************************************************//
// Forward declaration of types defined in TypeLibrary                    
// *********************************************************************//
  IUserInfo = interface;
  IUserInfoDisp = dispinterface;
  _UserInfo = interface;
  _UserInfoDisp = dispinterface;

Delphi также создала соответствующие типы Delphi:

// *********************************************************************//
// OLE Server Proxy class declaration
// Server Object    : TUserMaintenance
// Help String      : 
// Default Interface: _UserMaintenance
// Def. Intf. DISP? : No
// Event   Interface:
// TypeFlags        : (2) CanCreate
// *********************************************************************//

  TUserMaintenance = class(TOleServer)
  private
    FIntf:        _UserMaintenance;
    function      GetDefaultInterface: _UserMaintenance;
  protected
    procedure InitServerData; override;
    function Get_ToString: WideString;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    procedure Connect; override;
    procedure ConnectTo(svrIntf: _UserMaintenance);
    procedure Disconnect; override;
    function Equals(obj: OleVariant): WordBool;
    function GetHashCode: Integer;
    function GetType: _Type;
    WordBool AssignUser(IUserInfo userInfo);
    property DefaultInterface: _UserMaintenance read GetDefaultInterface;
    property ToString: WideString read Get_ToString;
  published
  end;

Я хотел бы создать экземпляр TUserMaintenance, а затем передать ему экземпляр TUserInfo. Однако сразу очевидны две вещи: класс Delphi TUserInfo НЕ реализует IUserInfo, а DefaultInterface - это новый интерфейс, созданный Delphi, _UserInfo. Я не могу объявить мою переменную UserInfo как тип IUserInfo, потому что TUserInfo не реализует интерфейс. Также я не могу объявить его как тип _UserInfo, потому что UserMaintenance.LoadUser ожидает экземпляр IUserInfo.

Правда, это очень упрощенный пример моей реальной проблемы, но я думаю, что он достаточно иллюстрирует проблему.

Итак, мой вопрос таков: могу ли я заставить тип интерфейса в Delphi оставаться согласованным с интерфейсом, объявленным в .NET? Или есть другой способ, которым я могу передать экземпляр UserInfo в UserMaintenance?

Ответы [ 2 ]

2 голосов
/ 20 января 2011

Часть проблемы, заключающаяся в том, что в Delphi объекты могут реализовывать интерфейсы, но сами по себе они не являются интерфейсными объектами. Чтобы понять это различие, вы должны взглянуть на грубую реализацию интерфейса на объекте Delphi, и понять механизм подсчета ссылок, используемый COM. Еще одна вещь следует понимать, что .NET не работает таким же образом, так что, похоже, Интерфейс в .NET для объекта такой же, как представлен аспект ComVisible.

TYPE
  TMyObject = class(TInterfacedObject, IMyObject, IMyNewInterface)
  end;

Учитывая вышесказанное, об этом объекте можно сказать следующее. Вы создали новый COM-объект с помощью мастера Delphi COM-объектов. Интерфейс IMyObject определен как интерфейс по умолчанию, а TMyObject - конкретный класс который будет реализовывать этот интерфейс. IMyNewInterface - это дополнительный интерфейс, определенный где-то еще, который вы указали объект реализует.

Вы можете сделать следующее с этим объектом

 var
   I1: IMyObject;
   I2: IMyNewInterface;
   T: TMyObject;
 begin
   I1:=TMyObject.Create;
   I2:=TMyObject.Create;
   T:=TMyObject.Create;
 end;

Вы можете делать эти вещи, потому что TMyObject РЕАЛИЗУЕТ эти интерфейсы. Также, так как они подсчитаны, вам не нужно освобождать их из памяти, когда область действия метода заканчивается, так же как и время жизни объекта - С ИСКЛЮЧЕНИЕМ ПОСЛЕДНЕГО Поскольку вы используете ОБЪЕКТНУЮ ССЫЛКУ вместо ИНТЕРФЕЙСНОЙ ССЫЛКИ, вы должны освободить ОБЪЕКТ, который вы создали.

Учитывая код, который вы разместили, если вы присмотритесь, вы фактически найдете ту же ситуацию. В вашем случае ваш объект полностью реализован в .NET, так что вы пытаетесь use это Delphi code Wrapper - это ОБЪЕКТ, а не ИНТЕРФЕЙС.
Вы замечаете на обертке, что нет второго способа передать экземпляр объекта IUserInfo, и это потому, что этот объект не реализует интерфейс, (ваш объект .NET был создан для этого) - в Delphi вам нужно «выбрать этот интерфейс»

Вы выполнили бы эту задачу, выполнив следующее:

Если вы уже сделали вызов TUserMaintenance.Create (nil) и имеете экземпляр этого объекта, получить интерфейс по умолчанию, затем «привести» его к соответствующему реализованному интерфейсу

 var
   UMDelphiWrapper: TUserMaintenance;
   UIDelphiWrapper: TUserInfo;
   UI: IUserInfo;
   UM: IUserMaintenance;


 begin
   //this part creates a Delphi OBJECT reference to the Implementation class -
   //this is NOT Reference counted, because it doesn't implement an interface.
   UIDelphiWrapper:=TUserInfo.Create(nil);
   try
     //this is the part where you acquire the interface of the object that actually
     //implementes this interface - e.g. your .NET class
     //not that this is the INTERFACE reference - which WILL be reference counted
     UI:=UIDelphiWrapper.DefaultInterface as IUserInfo;

     //UI.<Set some properties of your IUserInfo object>

     try
       //this part creates a Delphi OBJECT reference to the Implementation class -
       //this is NOT Reference counted, because it doesn't implement an interface.
       UMDelhpiWrapper:=TUserMaintenance.Create(nil);
       try
         //this is the part where you acquire the interface of the object that actually
         //implementes this interface - e.g. your .NET class
         //not that this is the INTERFACE reference - which WILL be reference counted
         UM:=UMdelphiWrapper.DefaultInterface as IUserMaintenance;
         try
           //Here, you have an interface type implemented by your .NET class that you are
           //sending to the implementation of your management object (Also a .NET class)
           UM.SendUser(UI);

           //do whatever else you need to do with your interface and user/management .NET object(s)
         finally
           //this IS a reference counted COM object - no "free" necessary 
           //this would naturally happen when the reference goes out of scope in the method
           //but for clairity sake is set to NIL to explicitly release your reference
           UM:=nil;
         end;

       finally
         //This is a delphi object that is NOT reference counted and must be released
        FreeAndNil(UMDelphiWrapper);     
       end;

     finally
       //this IS a reference counted COM object - no "free" necessary 
       //this would naturally happen when the reference goes out of scope in the method
       //but for clairity sake is set to NIL to explicitly release your reference
       UI:=nil; 
     end;
   Finally
     //This is a delphi object that is NOT reference counted and must be released
     FreeAndNIl(UIDelphiWrapper);
   end;

В дополнение к фактическому использованию оболочек, предоставляемых Delphi, вы можете вместо этого напрямую создавать ссылки на ваши объекты .NET, если вы знаете правильную информацию.

var
  UI: IUserInfo;
  UM: IUserManager;

begin
  UI:=CreateOleObject('YourAssembly.YourImplementationClass') as IUserInfo;
  UI.SomeProperty:=SomeValue;
  UM:=CreateOleObject('YourAssembly.YourImplementationClass') as IUserManger;
  UM.SomeMetohd(UI);
end;

Этот код намного чище - однако вы все равно должны иметь точное определение IUserInfo и IUserMaintenance, а также знать понятное для класса имя ваших объектов, поскольку они были зарегистрированы в COM.

Вы могли бы сделать это самостоятельно, набрав код - это то, что должна была бы сделать функция импорта типов, когда вы импортировали открытую для COM DLL из вашей сборки. Я не видел фактическую реализацию в коде, который вы предоставили, но вы все равно должны найти эту информацию в вашем заголовочном файле. - Если вы этого не сделаете, значит, вы не импортировали правильную сборку или импорт Delphi 7 работал неправильно - или его необходимо обновить (например, вы добавили новые методы в вашу реализацию .NET, но не сделали этого). • повторно зарегистрируйтесь (с помощью COM) и повторно импортируйте информацию о новом типе сборки).

type
  IUserInfo = interface
  ['22222222-2222-2222-2222-AAAAAAAAAAAA']
  //define your methods
  end;
1 голос
/ 27 января 2011

Надеюсь, я правильно понял ваш вопрос. Если вы хотите избавиться от запутанного (и ненужного) _UserInfo в библиотеке типов, вам не следует экспортировать свой CoClass, только интерфейс.

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("22222222-2222-2222-2222-AAAAAAAAAAAA")]
public interface IUserInfo
{
}

[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[Guid("22222222-2222-2222-2222-BBBBBBBBBBBB")]
public class UserInfo: IUserInfo
{
}

Обратите внимание, что первый атрибут CoClass установлен на ClassInterfaceType.None , таким образом DotNet не будет раскрывать сам CoClass для библиотеки типов. Тем не менее, вы можете создать экземпляр своего объекта в Delphi, как всегда:

var
  pUserInfo: IUserInfo;
begin
  pUserInfo := CoUserInfo.Create;
...