transfer interfaces from delphi dll to vb.net

2019-07-30 02:28发布

问题:

I have a Delphi written DLL that exposes some interfaces to a vb.net application. The interfaces inherit from IUnknown (but this could be changed if required), simplified example:

  IWindow = interface(IUnknown)
    ['{E9A11D0B-8A05-4CBA-83FA-C5CC6818DF6E}']
    function GetCaption(var Caption: PChar): HRESULT; stdcall;
  end;

Same interface in vb.net application:

<ComImport(), Guid("E9A11D0B-8A05-4CBA-83FA-C5CC6818DF6E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IWindow
    ReadOnly Property Caption() As <MarshalAs(UnmanagedType.LPWStr)> String
End Interface

This all works ok.

Now I want to transfer a collection of IWindow to vb.net, and in the vb.net application I want to be able to loop through it with a for in loop.

I read that it's possible using IEnumerable/IEnumerator but I don't quite understand how to implement them. Are there any good tutorials about this, specifically showing the declarations on both side? Example code would be great.

Please note that I prefer not create a com dll that should be registered and imported. Currently I export a function that enabled me to obtain an interface.

回答1:

I have got a solution working, but I am not entirely happy with it. I am placing it here as an answer, hopefully comments of other people may improve it or (even better) a better answer will be placed.

I found that the Delphi's declaration (Delphi 2010) of IEnumerator and IEnumerable are not using the stdcall calling convention so I declared them like this:

  IEnumerator = interface(IInterface)
  ['{496B0ABE-CDEE-11D3-88E8-00902754C43A}']
    function MoveNext: Boolean; safecall;
    function GetCurrent: IWindow; safecall;
    function Reset: HResult; stdcall;
  end;

  IEnumerable = interface(IInterface)
  ['{496B0ABE-CDEE-11D3-88E8-00902754C43A}']
    function GetEnumerator(var Enumerator: IEnumerator): HRESULT; stdcall;
  end;

My collection interface inherits from IEnumerable (but has it's own GUID):

  IWindows = interface(IEnumerable)
  ['{D9174D5A-4946-4E5A-95B1-2CD521C3BF73}']
  end;

The class implements IEnumerable, IEnumerator and IEnumVariant. I think that I can remove IEnumerator but I need to do more testing before I can confirm.

  TIWindows = class(TInterfacedObject, IWindows, IEnumerable, IEnumVariant)

On the VB.NET side it looks like this:

<ComImport(), Guid("D9174D5A-4946-4E5A-95B1-2CD521C3BF73"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IWindows
    Inherits IEnumerable
    Shadows Function GetEnumerator() As IEnumerator
End Interface

I had to override GetEnumerator because if I don't I get a null reference exception and on the Delphi side GetEnumerator() is never called (VMT offset problem?).

The calling code:

Dim Windows As IWindows
Dim Window As IWindow
Try
    Windows = Session.TopLevelWindows
    For Each Window In Windows
        TextBox1.Text = TextBox1.Text & Window.Caption
    Next
Catch ex As Exception
    ' Handle Exception
End Try

The Dim Windows as IWindow is required to make it work, without it I get the error: "Object reference not set to an instance of an object."

I tried to use generic in vb.net side:

Shadows Function GetEnumerator() As IEnumerator(Of IDNKWindow)

But it gives the error: Cannot marshal 'return value': Generic types cannot be marshaled.