Using Platform Invoke with Delphi 2005 This topic describes the basic techniques of using unmanaged APIs from Delphi 2005. Some of the common mistakes and pitfalls are pointed out, and a quick reference for translating Delphi data types is provided. This topic does not attempt to explain the basics of platform invoke or marshaling data. Please refer to the links at the end of this topic for more information on platform invoke and marshaling. Understanding attributes and how they are used is also highly recommended before reading this document. The Win32 API is used For SEVERAL EXAMPLES. For Further Details on the API Functions Mentioned, please See The Windows Platform SDK Documentation. The Following Topics Are Discussed in this section
Calling unmanaged functions Structures Callback functions Passing Object References Using COM Interfaces Calling Unmanaged Functions When calling unmanaged functions, a managed declaration of the function must be created that represents the unmanaged types. In many cases functions take pointers to data that can be of variable types. One Example of Such A Function Is The Win32 API Function SystemParametersInfo That Is Declared As Follows: Bool SystemParametersInfo
UINT UIAction, // System Parameter To Retrieve or Set
UINT UIPARAM, // Depends on action to be taken
Pvoid Pvparam, // Depends on action to be taken
Uint Fwinini // User Profile Update Option
);
Depending on the value of uiAction, pvParam can be one of dozens of different structures or simple data types. Since there is no way to represent this with one single managed declaration, multiple overloaded versions of the function must be declared (see Borland.Vcl. Windows.PAS), WHERE Each Overload Covers One Specific Case. The Parameter PvParam Can Also Be Given The Generic DeclarationintPtr
. This places the burden of marshaling on the caller, rather than the built in marshaler. Note that the data types used in a managed declaration of an unmanaged function must be types that the default marshaler supports. Otherwise, the caller must declare the parameter as
INTPTR
. And be responsible for marshaling the data Data Types Most data types do not need to be changed, except for pointer and string types The following table shows commonly used data types, and how to translate them for managed code.:
Unmanaged Data TypeManaged Data Type Input ParameterOutput ParameterPointer to string (PChar) String StringBuilder Untyped parameter / buffer TBytes TBytes Pointer to structure (PRect) const TRect var TRect Pointer to simple type (PByte) const Byte var Byte Pointer to array (PInteger) array of Integer Array of Integer Pointer to Pointer Type (^ pinteger)
INTPTR
INTPTR
INTPTR
Can Also Represent All Pointer and String Types, in which case you need to manually Marshal Data Using The
Marshal
Class. Working with functions That Receive a Text Buffer, The
StringBuilder
Class Provides The Easiest Solution. The Following Example Shows How To Use A
StringBuilder
To Receive A TEXT BUFFER: Function GetText (Window: hwnd; buffsize: integer = 1024): String;
VAR
Buffer: StringBuilder;
Begin
Buffer: = StringBuilder.create (bufsize); getWindowText (Window, Buffer, Buffer.capacity);
Result: = buffer.tostring;
END;
THE
StringBuilder
Class is Automatically Marshaled Into An Unmanaged Buffer and Back. in Some Cases It May Not Be Practical, OR POSSIBLE, TO USE A
StringBuilder
............................ ..
VAR
Buffer: intptr;
Begin
Buffer: = Marshal.StringTohglobalAuto (Text);
Try
Result: = SendMessage (Window, WM_SETTEXT, 0, BUFFER);
Finally
Marshal.Freehglobal (BUFFER);
END;
END;
An unmanaged buffer is allocated, and the string copied into it by calling StringToHGlobalAuto. The buffer must be freed once it's no longer needed. To marshal a pointer to a structure, use the Marshal.
StructureToptr
. Method to copy the contents of the structure into the unmanaged memory buffer The following example shows how to receive a text buffer and marshal the data into a string: function GetText (Window: HWND; BufSize: Integer = 1024): string;
VAR
Buffer: intptr;
Begin
Buffer: = Marshal.allochglobal (bufsize * mathal.systemdefaultcharsize);
Try
SendMessage (window, wm_gettext, bufsize, buffer);
Result: = Marshal.PTRTOStringAuto (BUFFER);
Finally
Marshal.Freehglobal (BUFFER);
END;
END;
IT Is Important To Ensure The Buffer Is Large Enough, And by Using To
SystemDefaultcharsize
method, the buffer is guaranteed to hold BufSize characters on any system. Advanced Techniques When working with unmanaged API's, it is common to pass parameters as either a pointer to something, or NULL. Since the managed API translations do not use pointer types, IT Might Be Necessary To Create An Additional Overloaded Version of The Function with The Parameter That Can Be Null Declared Asintptr
Spectial Cases There Are Cases Where A
StringBuilder
And Even the
Marshal
class will be unable to correctly handle the data that needs to be passed to an unmanaged function. An example of such a case is when the string you need to pass, or receive, contains multiple strings separated by NULL characters. Since the default marshaler will consider the first NULL to be the end of the string, the data will be truncated (this also applies to the StringToHGlobalXXX and PtrToStringXXX methods). in this situation TBytes can be used (using the PlatformStringOf and PlatformBytesOf functions in Borland.Delphi.System to Convert the byte array to / from a string). Note That the utility functions do not add or remove terminating null characters. when Working with com interfaces, the
UnmanagedType
ENUMERATION (Used by The Marshalasattribute Class) HAS A Special Value, LPStruct. This is Only Valid In Combination With a system.
GUID
. Class, causing the marshaler to convert the parameter into a Win32 GUID structure The function CoCreateInstance that is declared in Delphi 7 as: function CoCreateInstance ([MarshalAs (UnmanagedType.LPStruct)] clsid: TCLSID;
[Marshalas (unmanagedtype.iunknown)] unkouter: TOBJECT;
DWCLSCONTEXT: longint;
[MARSHALAS (UnmanagedType.lpstructure)] IID: TIID;
[Marshalas (UnmanagedType.Interface] OUT PV
: HRESULT;
This is currently the only documented use for UnmanagedType.LPStruct. Structures The biggest difference between calling unmanaged functions and passing structures to unmanaged functions is that the default marshaler has some major restrictions when working with structures. The most important are that dynamic arrays, arrays of Structures and theStringBuilder
Class Cannot Be Used in Structures. for these case
INTPTR
is required (although in some cases string paired with various marshaling attributes can be used for strings) Data Types The following table shows commonly used data types, and how to "translate" them for managed code.:
Unmanaged Data TypeManaged Data Type Input ParameterOutput ParameterPointer to string (PChar) String IntPtr Character array (array [a..b] of Char) String String Array of value type (array [a..b] of Byte) array [a .. B] of byte array [ARRAY DYNAMIC ARRAY (Array [0..0] of type) INTPTR INTPTR ARRAY OF STRUCT (Array [1..2] of trect) INTPTR OR FLATTEN INTPTR OR FLATTEN POINTER TO Structure (prect) INTPTR
INTPTR
Pointer to simple type (PByte) IntPtr IntPtr Pointer to array (PInteger) IntPtr IntPtr Pointer to pointer type (^ PInteger) IntPtr IntPtr When working with arrays and strings in structures, the MarshalAs attribute is used to describe additional information to the default marshaler about The data type. A Record Declared in Delphi 7, for Example: Type
TMYRECORD = Record
INTBUFFER: Array [0..31] of integer;
Charbuffer: array [0..127] of char;
LPSZINPUT: LPTSTR;
LPSZOUTPUT: LPTSTR;
END;
Would Be Decland As Follows in Delphi 2005: Type
[Structlayout (layoutkind.sequential, charset = charset.auto)]
TMYRECORD = Record
[Marshalas (UnmanagedType.ByValarray, SIZECONST = 32)]]
INTBUFFER: Array [0..31] of integer;
[Marshalas (UnmanagedType.Byvaltstr, SIZECONST = 128)]]]
Charbuffer: String;
[Marshalas (unmanagedType.lptstr)]
LPSZINPUT: STRING;
LPSZOUTPUT: INTPTR;
END;
The Strings Contain Platform Dependant Tchar's (As Commonly Used by The Win32 API). It is important to note what in Order To Receive Text in Lpszoutput, The Marshal.
Allochglobal
Method Needs to Be Called Before Passing The Structure To an API Function. A Structure CAN Contain Structure, But Not Such Cases AN
INTPTR
Must be declared, and the marshal.
StructureToptr
Method use to move data from the managed structure. Note That. Note That. INTO UNMANAGED MET
StructureToptr
Does Not Allocate The Memory Needed (this Must Be Done Separately). Be Sure To Use Marshal.
Sizeof
to determine the amount of memory required, as Delphi's SizeOf is not aware of the MarshalAs attribute (in the example above, CharBuffer would be 4 bytes using Delphi's SizeOf when it in fact should occupies 128 bytes on a single byte system). The following examples Show how to send Messages That Pass POINTERS TO A Structure: Procedure SetRect (Handle: hwnd; const: TRECT);
VAR
Buffer: intptr;
Begin
Buffer: = Marshal.allochglobal (Marshal.Sizeof (Typeof (TRECT)));
Try
Marshal.StructureToptr (Tobject (Rect), Buffer, False;
SendMessage (Handle, EM_SETRECT, 0, BUFFER);
Finally
Marshal.destroystructure (buffer, typeof (trect));
END;
END;
Procedure getRect (Handle: hwnd; var recepter);
VAR
Buffer: intptr;
Begin
Buffer: = Marshal.allochglobal (Marshal.Sizeof (Typeof (TRECT)));
Try
SendMessage (Handle, EM_GETRECT, 0, BUFFER);
RECT: = TRECT (Marshal.PTRTOStructure (Buffer, TypeOf (TRECT)); Finally
Marshal.destroystructure (buffer, typeof (trect));
END;
END;
IT Is Important to Call
Destroystructure
Rather Than
Freehglobal
If The Structure Contains Fields Where The Marshaling Layer Needs To Free Additional Buffers (See The Documentation for
Destroystructure
for more details). Advanced topics Working with unmanaged API's it is not uncommon to need to convert a byte array into a structure (or retrieve one or more fields from a structure held in a byte array), or vice versa. Although the
Marshal
class contains a method to retrieve the offset of a given field, it is extremely slow and should be avoided in most situations. Informal performance tests show that for a structure with eight or nine numeric fields, it is much faster to allocate a block of unmanaged Memory, Copy The byte array to the unmanaged memory and call
PTRTOStructure
Than Finding the position of just one far using marshal.
Offsetof
And communicating the data using the
BitConverter
Class. Borland.vcl.winutils Contains Helper Functions to Perform Conversions Between Byte Arrays and Structures (See
Structuretobytes
and
BYTESTOSTRUCTURE
). Special Cases There is Required, Such As Sending a Message with a Pointer to an Array of Integers. For Situations Like this, The
Marshal
class provides methods to copy data directly to the unmanaged buffer, at specified offsets (so you can construct an array of a custom data type after allocating a buffer). The following example shows how to send a message where the LParam is a pointer to an Array of Integer: Function SendaRrayMessage (Handle: hwnd; msg: uint; wparam: wparam;
LPARAM: TINTEGERDYNARRAY: LRESULT; VAR
Buffer: intptr;
Begin
Buffer: = Marshal.allochglobal (LPARAM) * SIZEOF (Integer);
Try
Marshal.copy (LParam, 0, Buffer, Length (lparam));
Result: = SendMessage (Handle, MSG, WPARAM, BUFFER);
Finally
Marshal.Freehglobal (BUFFER);
END;
END;
Callback Functions When passing a function pointer for a managed function to an unmanaged API, a reference must be maintained to the delegate or it will be garbage collected. If you pass a pointer to your managed function directly, a temporary delegate will be created, and As Soon As It Goes Out of Scow, IT Is Subject To Garbage Collection. Consider The Following Delphi 7 Code: Function Myfunction:
Begin
...
Registercallback; @MYCALLBACK;
...
END;
In Order for this to work in a managed environment, The code Needs to be change to the following: const
MyCallbackDelegate: tfnmycallback = @MYCALLBACK;
Function myfunction: integer;
Begin
...
RegisterCallback (mycallbackdelegate);
...
END;
This will ensure that the callback can be called as long as MyCallbackDelegate is in scope. Data types The same rules apply for callbacks as any other unmanaged API function. Special cases Any parameters used in an asynchronous process must be declared as
INTPTR
..........................................................................................................
INTPTR
, IT IS Your Responsibility to Free Any Memory That Has Been Allocated.
Passing Object References When working with for example the Windows API, object references are sometimes passed to the API where they are stored and later passed back to the application for processing usually associated with a given event. This can still be accomplished in .NET, but special care needs to be taken to ensure a reference is kept to all objects (otherwise they can and will be garbage collected). Data types The following table showsUnmanaged Data TypesManaged Data Type Supply DataReceive DataPointer (Object reference, user data)
Gchaldle
Gchaldle
THE
Gchaldle
PROVIDES The Primary Means of Passing An Object References To Unmanaged Code, And ENSURING GARBAGE COLLECTION DOES NOT HAPPEN. A
Gchaldle
Needs to be allocated, and later freed when no longer needed. There all several types of
Gchaldle
,
Gchandletype.normal
Being the Most Useful When An Unmanaged Client Holds The Only Reference. in ORDER Pass A
Gchaldle
To an API Function Once IT IS Allocated, Type Cast IT To
INTPTR
(AND Optionally onwards to longint, depending on the unmanaged decaration). The
INTPTR
Can Later Be Cast Back to A
Gchaldle
Note That
Isallocated
Must Be Called Before Accessing T
Target
Property, As Shown Below: procedure myprocedure;
VAR
PTR: INTPTR;
Handle: gchandle;
Begin
...
IF PTR <> nil dam
Begin
Handle: = Gchandle (PTR);
IF Handle.isallocated Then
DOSMETHING (HANDLE.TARGET);
END;
...
END;
Advanced Techniques The Use of A
Gchaldle
, Although relatively easy, is fairly expensive in terms of performance. It also has the possibility of resource leaks if handles are not freed correctly. If object references are maintained in the managed code, it is possible to pass a unique index, for example The Hash Code Returned by Thegethashcode
method, to the unmanaged API instead of an object reference. A hash table can be maintained on the managed side to facilitate retrieving an object instance from a hash value if needed. An example of using this technique can be found in the
TTREENODES
Class (in borland.vcl.comctrls).
Using COM Interfaces When using COM interfaces, a similar approach is taken as when using unmanaged API's The interface needs to be declared, using custom attributes to describe the type interface and the GUID Next the methods are declared;.. Using the same approach as for UNMANAGED API's. The Following Example Uses The iautocomplete interface, defined as stock in Delphi 7: ketocomplete = interface (iUnknown)
['{00BB2762-6A77-11D0-A535-00C04FD7D062}']
Function init (hwndit: hwnd; punkacl: iUnknown;
PwszregKeyPath: lpcwstr; PWSZQUICKCOMPLETE: LPCWSTR): HRESULT; stdcall;
Function enable (Fenable: bool): hResult; stdcall;
END;
In Delphi 2005 IT Is Declared As Follows: [Comimport, Guidattribute ('00bb2762-6a77-11d0-a535-00c04fd7d062'), InterfacePeattribute (CominterFaceType.Interfaceisiunknown)]
IAUTOCOMPLETE = Interface
Function init (hwndit: hwnd; punkacl: ienumstring;
PwszregKeyPath: INTPTR; PWSZQUICKCOMPLETE: INTPTR: HRESULT
Function enable (Fenable: bool): hResult;
END;
Note The Custom Attributes Used to Describe The Guid and Type of Interface. It is also essential to use the
ComimportAttribute
.. Class There are some important notes when importing COM interfaces You do not need to implement the IUnknown / IDispatch methods, and inheritance is not supported Data types The same rules as unmanaged functions apply for most data types, with the following additions:. Unmanaged Data TypeManaged Data type Supply DataReceive DataGUID System.Guid System.Guid IUnknown TObject TObject IDispatch TObject TObject Interface TObject TObject Variant TObject TObject SafeArray (of type) array of
Marshalasattribute
Custom Attribute Is Required for Some of The Above Uses of
TOBJECT
, Specifying The Exact Unmanaged Type (SUCH AS
UnmanagedType.iunknown
,
UnmanagedType.idispatch
oral
UnmanagedType.Interface
..) This is also true for certain array types An example of explicitly specifying the unmanaged type is the Next method of the IEnumString interface The Win32 API declares Next as follows:. HRESULT Next (
Ulong Celt,
LPolestr * Rgelt,
Ulong * PCELTFETCHED
);
In Delphi 2005 The Declaration Would Be: Function Next (CELT: Longint)
[out, marshalas (unmanagedtype.lparray, arraysubtype = unmanagedtype.lpwstr, sizeparamindex = 0)]
Rgelt: array of string;
Out PCELTFETCHED: Longint
: Integer;
Advanced techniques When working with safearrays, the marshal layer automatically converts (for example) an array of bytes into the corresponding safearray type. The marshal layer is very sensitive to type mismatches when converting safearrays. If the type of the safearray does not exactly match the type of the managed array, an exception is thrown. Some of the Win32 safearray API's do not set the type of the safearray correctly when the array is created, which will lead to a type mismatch in the marshal layer when used from .NET. The solutions are to either ensure that the safearray is created correctly, or to bypass the marshal layer's automatic conversion. The latter choice may be risky (but could be the only alternative if you do not have the ability to change the COM server that is providing THE DATA). CONSIDER THE FOLLOWING DECLATION: Function As_GetRecords (Const ProviderName: WideString; Count: Integer; Orth Recsout: Integer; Options: Integer; Const CommandText: WideString
Var params: olevariant; var ownerdata: olevariant: olevariant;
IF the return value is known to always be a saFearray (That Doesn't Described ITS TYPE CORRECTLY) Wrapped in a Variant, We can change the declaration to the FOLLLOWING: TYPE
TsafeByteArrayData = Packed Record
Vtype: Word;
RESERVED1: WORD1;
RESERVED2: WORD;
RESERVED3: WORD;
Varray: INTPTR; {this is a pointer to the actual saFeArray}
END;
Function as_getrecords (const provindername: widestring; count: integer;
Out Recsout: Integer; options: widexText: WideString
Var params: Olevariant; Var OwnerData: Olevariant: TsafebyteaRrayData;
Knowing that an OleVariant is a record, the TSafeByteArrayData record can be extracted from Delphi 7's TVarData (equivalent to the case where the data type is varArray). The record will provide access to the raw pointer to the safearray, from which data can be extracted . By using a structure instead of an OleVariant, the marshal layer will not try to interpret the type of data in the array. You will however be burdened with extracting the data from the actual safearray. Special cases Although it is preferred to use Activator. CreateInstance when creating an instance, it is not fully compatible with CoCreateInstanceEx. When working with remote servers, CreateInstance will always try to invoke the server locally, before attempting to invoke the server on the remote machine. Currently the only known work-around is to Use CocreateInstanceex. Since Inheritance Isn't Supported, A Descendant Interface Needs to Declare The Ancestor's Methods. Below Is The iautocomplete2 Interface, Which Extends IAutoComplete. [ComImport, GuidAttribute ( 'EAC04BC0-3791-11d2-BB95-0060977B464C'), InterfaceTypeAttribute (ComInterfaceType.InterfaceIsIUnknown)] IAutoComplete2 = interface (IAutoComplete)
// ilonetomplete method
Function init (hwndit: hwnd; punkacl: ienumstring;
PwszregKeyPath: INTPTR; PWSZQUICKCOMPLETE: INTPTR: HRESULT
Function enable (Fenable: bool): hResult;
//
Function SETOPTIONS (DWFLAG: DWORD): HRESULT
Function GetOptions (VAR DWFLAG: DWORD): HRESULT
END;