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

У меня есть устаревшее приложение, написанное на 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, создавая UserMain maintenance_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;

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

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

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


person Welton v3.60    schedule 18.10.2010    source источник


Ответы (2)


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

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

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

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

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

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

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

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

Если вы уже выполнили вызов TUserMainasted.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 и IUserMain maintenance, а также знать понятное имя класса ваших объектов, поскольку они были зарегистрированы в COM.

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

type
  IUserInfo = interface
  ['22222222-2222-2222-2222-AAAAAAAAAAAA']
  //define your methods
  end;
person SilverKnight    schedule 20.01.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;
person martinstoeckli    schedule 27.01.2011