Monday, October 01, 2007

DN4DP#22: .NET only: P/Invoke magic

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we're explored an undocumented corner of the language. This time we'll explore the exotic (and cryptic?) world of P/Invoke.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"P/Invoke magic

Moving into the slightly more esoteric topics, Delphi for .NET supports two Platform Invoke (or P/Invoke) technologies called Reverse P/Invoke (or Unmanaged Exports) and Dynamic P/Invoke (or Virtual Library Interfaces).

Reverse P/Invoke lets you write a .NET DLL that can be used like any other DLL from Win32 code. It is a quick way of introducing .NET functionality into a Win32 application without performing a complete porting process or hosting the CLR explicitly.

Unmanaged exports must reside within a library project and generate thunks of unmanaged code, so you must turn {$UNSAFECODE ON}. The syntax is the same exports declaration as is used in Win32 Delphi. Only global-level routines can be exported, not class methods.

library ReversePInvoke;
procedure Foo(const S: string);
function Bar: integer;
function Greeting(Name: string): string;
//...
{$UNSAFECODE ON}
exports
Foo,
Bar,
Greeting;

On the Win32 side you import these routines just like you would import any other DLL, using external declarations.

const
LibName = 'ReversePInvoke.DLL';

procedure Foo(const S: string); stdcall; external LibName;
function Bar: integer; stdcall; external LibName;
function Greeting(Name: string): PChar; stdcall; external LibName;


Caution: Not all managed types can be used as parameters in exported routines. Generally you can use simple types and strings. String input parameters map to Win32 AnsiString, string results and output parameters map to PChar.


Virtual Library Interfaces uses Dynamic P/Invoke to import a Win32 DLL by using an interface to specify what routines to import. The DLL can be seen as a singleton object that implements the interface. The advantage is that you can use the Supports function from the Borland.Delphi.Win32 unit to check if the DLL and all the methods are available.

uses
Win32;
type
IMyInterface = interface
procedure Foo(const S: string);
function Bar: integer;
function Greeting(const Name: string): string;
end;

procedure Test;
var
MyInterface: IMyInterface;
begin
if Supports('Win32NativeDLL.DLL', TypeOf(IMyInterface), MyInterface) then
begin
Writeln('.NET App dynamically calling into Win32 DLL');
Writeln('The Answer is ', MyInterface.Bar);
MyInterface.Foo('.NET client');
Writeln(MyInterface.Greeting('Ida'));
end
else
Writeln('Cannot find Win32NativeDLL.DLL!');
end;

In effect you are dynamically loading the DLL if and only if it is available. If not, the application can continue running, but with reduced functionality. It also allows the application to control the folder the DLL is loaded from.


Tip: Use the LibraryInterface attribute to control calling convention and the wideness of string parameters. The defaults are CharSet.Auto (PChar on Win9x and PWideChar on WinNT) and CallingConvention.Winapi (or stdcall)."

No comments:



Copyright © 2004-2007 by Hallvard Vassbotn