.Net interop - using Delphi .Net assembly from Delphi Win32

.Net assembly

Now we will explore .Net assembly written in Delphi .Net. Source code (password "interop") for this example is located in directory ".Net interop - Delphi". Compile .Net assembly using Delphi 8. You will have DelphiAssembly.dll.

Let's explore this assembly. We will use InteropUtils.exe located in Tools directory. Export this assembly into type library and Delphi Win32 unit (with Delphi be carefull while exporting assembly into directory that contains its source - .pas files could be replaced with generated units) and register it for COM with codebase the same way as you did with C# assembly.

Now let's explore exported unit. It contains many classes that Delphi .Net has inserted into an assembly. All these classes are COM visible that is not really a good idea.

As with C# assembly, at first we need to correct exported unit. At first, delete all unneeded references to System_tlb.pas, System_Windows_Forms_tlb.pas, as well as OleServer, Classes and Graphics. mscorlib_tlb is better to take from trial (or even better, registered) version of Managed extensions for VCL. Now locate section "Declaration of structures, unions and aliases." and comment all types declarations there, otherwise you will have errors at compile time.

DelphiAssembly.dll reproduces code of CSharpAssembly.dll that was discussed on previous part and adds new code to demonstrates Delphi's interop features such as exporting functions and procedures with exports section.

Test Delphi Win32 application

All that was written for C# assembly is true for Delphi .Net assembly. But there are some differences. I could not find a way to mark class as private or internal in Delphi .Net. So all classes that do not have ComVisible attribute set to False are visible in type library (of course, if whole assembly has attribute ComVisible set to False only classes with ComVisible that is True are visible).

Some Delphi's data types are not completely compatible with corresponding .Net data types. For example, TDateTime is not DateTime class - it is converted into TDateTime in interfaces but does not work if used with dispinterface (I think that it is because its IConvertible.GetTypeCode() returns TypeCode.Object instead of TypeCode.DateTime).

Test application contains the same tests as for C# assembly. These tests are explained in previous part.

Using functions and procedures exported from Delphi .Net assembly

Delphi .Net provides very attractive interop feature - exported functions and procedures. These procedures work the same way as procedures exported from standard Win32 .dll.

Delphi .Net unit should contain {$UNSAFECODE ON} directive to include exports section. Exports section works the same way as in Delphi Win32 - just list functions you want to export. If this does not produce compiler errors, it is good to specify calling convention - stdcall seems to be the best choice and is used by default.

Let's test simple function:

function TestString(): string;		
		

It is in Delphi .Net. In Delphi Win32 it looks as: 

function TestString(): PChar; stdcall; external TestAssembly;
		

Native VCL Win32 types such as string could not be used - Delphi .Net can not use Delphi Win32 memory allocator and can not include ShareMem unit.

Let's test this function in Delphi Win32. It works. But returned string is allocated in unmanaged memory - who should release it? To check this, let's ask COM did it allocated memory for this string. COM uses its special allocator that is available as IMalloc interface pointer. Pass returned PChar to IMalloc.DidAlloc() and we see that it is IMalloc allocated this memory and so it should free it. We could use IMalloc.Free() or CoTaskMemFree() function to free this memory.

.Net uses internally unicode strings, is it? But returned string is encoded according Windows locale, non-english readers could test it by returning string with national characters. So is it possible to use unicode in interop? Of cause, yes. String type in controlled with MarshalAs attribute. I could not find a way to set this attribute on return value but if attribute is set to function's string parameter - the same value is applied to return value.  This is demonstrated in handler for "Test WideString" button. Returned string is actually BSTR that should correspond to WideString in Delphi but casting to WideString produces errors. However such string (with MarshalAs set to UnmanagedType.BSTR should be free as WideString - using SysFreeString()).

Return parameters for exported functions are implemented with some bugs so it is a good idea to use return parameters only for integers or error codes. WideString as procedure's parameter (not return value) works great - example is handler for "Test WideString 2" button.

Exported functions could work with different types of arrays, not only SafeArray. This is demostrated in handler for "Test array" button for array of PChar. .Net procedure does not change array, so strings (PChar) could be allocated by any allocator, including Delphi Win32 allocator. But if this array could be changed in .Net procedure, strings (PChar) should be allocated using IMalloc or CoTaskMemAlloc - .Net framework will use CoTaskMemFree() to free strings when needed.

Next test shows use of TDateTime output parameter. As you could see, TDateTime works and could be used as well as System.DateTime.

Interfaces and objects exported from .Net assembly

Now we will explore the most interesting part of exports functionality - use of .Net interfaces and classes. At first, try return interface pointer from .Net assembly - example in handler for "Test interface" button. As you could see, nil is returned if optimization in compiler settings is off and application shuts down - if optimization is on. It is again a problem with return parameter. Trying to get interface pointer as output parameter works and interface is returned.

However trying to just copy interface definition from Delphi .Net to Delphi Win32 and use it will fail. That is because interface definition should be rewritten according interop rules. At first, specifying Guid in its usual place (next line after interface keyword) does not set this Guid for interface - it should be set also as Guid attribute.

Exported interface in Win32 will inherit from IUnknown or IDispatch depending on value of InterfaceType attribute (by default from IDispatch) and not on ancestor interface specified in .Net. If in .Net interface inherits from another interface, methods of ancestor interfaces will not be shown - Win32 application should query ancestor interface to use its methods. The simplest way to get .Net interface definition in Win32 is to export .Net assembly into COM type library and import this type library into Delphi Win32 unit. This could be done using InteropUtils.exe that is included in examples source code (password "interop") in Tools directory. When correct declarations are done, interface pointer could be used from unmanaged code.

.Net objects could be passed to unmanaged code directly. One way is to define .Net object with ClassInterface attribute set to ClassInterfaceType.AutoDual and use default interface that could be obtained using InteropUtils.exe or use dispinterface and OleVariant. However the best way is to use interface - this will ensure that code will be the same after changes in .Net assembly.

Conclusion

If you have any questions or comments, you are welcome in our forums.