COM interop - using unmanaged code from Delphi .Net

Using COM objects from Delphi .Net

Using COM objects from .Net is easy and is described good in .Net SDK help. All that is written for C# is true for Delphi .Net. At first, start InteropUtils.exe from examples source code (password "interop"), enter path to COM library (.dll or .tlb), generate new strong key file using "Generate key" button and enter path to this file into "Key file" edit box (required for primary interop assemblies), enter namespace and version (optional), check "Primary Interop" checkbox if needed and press "Import" button. In "Output directory" you could enter directory where output assembly will be created or filename of assembly (must have .dll extension). Imported assembly should be placed into project directory or in GAC.

In example application, located in "COM interop - Delphi\Test" directory, code to create MS Jet (MS Access) database using MS ADOX is shown. Import msadox.dll (usually in "C:\Program Files\Common Files\System\ado\msADOX.dll") into msadox.dll in project directory, select "Project\Add reference" in IDE menu and add reference to imported msadox assembly. Add msADOX (namespace in imported assembly) in uses clause and you could use ADOX objects as native .Net objects.

Using Win32 .dlls from Delphi .Net

If you did not yet, download source code. Example is in "COM interop - Delphi" directory.

Functions and procedures exported from Win32 dll should be defined in Delphi .Net as well as in Delphi Win32 - with external keyword. Data types, especially strings, should be marshalled to be represented correctly in .Net.

At first, try to pass string to Win32 dll. StringLength function waits for PChar, so marshal it to UnmanagedType.LPStr. When Win32 dll waits for WideString (BSTR), string should be marshaled to UnmanagedType.BStr. For .Net, in both cases parameter is string.

Now let's look at records. Record should be declared in .Net to correspond Win32 declaration. So record layout should be controlled explicitely - record's fields should be at the same offset as in Win32. To do this, apply StructLayout attribute and set LayoutKind to Sequential. Our record uses ANSI strings, so set CharSet to Ansi. Procedure for handler of "Test record with chars" shows this technique.

Now let's look how to pass arrays from .Net to unmanaged code. Arrays could be passes as SafeArrays and this technique is shown in parts of this article for .Net interop. Here we will test passing arrays as pointer to the first element and number of elements. In this case .Net runtime will allocate unmanaged memory using IMalloc (CoTaskMemAlloc()) and pass array to unmanaged code. Example in handler for "Test array of integers" button shows that array could be not only processed but also changed in unmanaged code. Example in handler for "Test array of strings" shows how to allocate memory for string elements in array to pass result back to the .Net.

Examples in handlers for "Test array of records" and "Test array of records 2" show how to use and change arrays of records.

Another interesting feature of interop is ability to call .Net functions from unmanaged code. .Net uses delegates for this and delegates to static methods (global procedures and functions in Delphi .Net) could be called from Win32. To do this, .Net will create temporary delegate that will be collected when Win32 function returns special delegate to callback procedure should be maintained for all time when callback could be called. Delegate could be created in Delphi .Net with @ operator. The easest way to maintain delegate is to add constant that will hold it:

const    MyCallback: TCallbackFunction = @Callback;
		

Examples in handlers for "Test callback" and "Test callback 2" shows simple use of callbacks, however this is very powerfull technique. If you need callback to be called for object instance, the best way is to declare callback interface, implement it in .Net object and pass pointer to this interface to Win32 code.

Many functions in Win32 return HRESULT that indicates error code and it would be convenient if .Net runtime raised exceptions when error is returned - similar to safecall calling convention in Delphi Win32. This could be done using PreserveSig field of DllImport attribute. To use properties of DllImport attribute, function should be declared in .Net using this attribute, in this case keyword external is used without providing dll name (it is already passed to DllImport attribute). .Net runtime will raise exception basing on returned HRESULT, in example E_NOTIMPL is mapped to System.NotImplementedException.

Links

If you want to use Win32 dlls from Delphi .Net, PInvoke.net provides declarations for many functions and procedures. All declarations are for C# but could be easily converted into Delphi .Net.

Conclusion

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