Delphi2005学习笔记2——Using Platform Invoke with Delphi 2005

类别:Delphi 点击:0 评论:0 推荐:
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 declaration Unmanaged Data Type Managed Data Type Input Parameter Output Parameter Pointer 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. When 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; BufSize: 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 . The following examples show how to marshal data to send and retrieve strings using SendMessage:

procedure SetText(Window: HWND; Text: string); 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 * Marshal.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 the 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 don’t use pointer types, it might be necessary to create an additional overloaded version of the function with the parameter that can be NULL declared as IntPtr . Special 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 these 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.LPStruct)] 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 the StringBuilder class cannot be used in structures. For these cases 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 Type Managed Data Type Input Parameter Output Parameter Pointer 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[a..b] of Byte 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 declared 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 above declarations assume that the strings contain platform dependant TChar’s (as commonly used by the Win32 API). It is important to note that 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 structures, but not pointers to structures. For such cases an IntPtr must be declared, and the Marshal. StructureToPtr method used to move data from the managed structure into unmanaged memory. Note that 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 Rect: 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 Rect: TRect); 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 field using Marshal. OffsetOf and converting 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 are cases where custom processing 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(Length(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 scope (at the end of MyFunction in the example below), it is subject to garbage collection. Consider the following Delphi 7 code:

function MyFunction: Integer; begin ... RegisterCallback(@MyCallback); ... end; In order for this to work in a managed environment, the code needs to be changed 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 . The marshaler will free any memory it has allocated for unmanaged types when it returns from the function call. When using an 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 shows Unmanaged Data Types Managed Data Type Supply Data Receive Data Pointer (Object reference, user data) GCHandle GCHandle The GCHandle provides the primary means of passing an object references to unmanaged code, and ensuring garbage collection does not happen. A GCHandle needs to be allocated, and later freed when no longer needed. There are several types of GCHandle , GCHandleType.Normal being the most useful when an unmanaged client holds the only reference. In order pass a GCHandle to an API function once it is allocated, type cast it to IntPtr (and optionally onwards to LongInt, depending on the unmanaged declaration). The IntPtr can later be cast back to a GCHandle . Note that IsAllocated must be called before accessing the Target property, as shown below:

procedure MyProcedure; var Ptr: IntPtr; Handle: GCHandle; begin ... if Ptr <> nil then begin Handle := GCHandle(Ptr); if Handle.IsAllocated then DoSomething(Handle.Target); end; ... end; Advanced techniques The use of a GCHandle , although relatively easy, is fairly expensive in terms of performance. It also has the possibility of resource leaks if handles aren’t 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 the GetHashCode 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 follows in Delphi 7:

IAutoComplete = interface(IUnknown) ['{00bb2762-6a77-11d0-a535-00c04fd7d062}'] function Init(hwndEdit: 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'), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] IAutoComplete = interface function Init(hwndEdit: 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 Type Managed Data Type Supply Data Receive Data GUID System.Guid System.Guid IUnknown TObject TObject IDispatch TObject TObject Interface TObject TObject Variant TObject TObject SafeArray (of type) array of <type> array of <type> BSTR String String Using the MarshalAsAttribute custom attribute is required for some of the above uses of TObject , specifying the exact unmanaged type (such as UnmanagedType.IUnknown , UnmanagedType.IDispatch or 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 don’t have the ability to change the COM server that is providing the data). Consider the following declaration:

function AS_GetRecords(const ProviderName: WideString; Count: Integer; out 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 describe its type correctly) wrapped in a variant, we can change the declaration to the following:

type TSafeByteArrayData = packed record VType: Word; Reserved1: Word; Reserved2: Word; Reserved3: Word; VArray: IntPtr; { This is a pointer to the actual SafeArray } end; function AS_GetRecords(const ProviderName: WideString; Count: Integer; out RecsOut: Integer; Options: Integer; const CommandText: 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) // IAutoComplete methods function Init(hwndEdit: 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;

本文地址:http://com.8s8s.com/it/it4562.htm