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.
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.