Delphi2005 Learning Notes 2 - Using Platform Invoke with Delphi 2005

xiaoxiao2021-03-06  63

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 array of 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

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;

转载请注明原文地址:https://www.9cbs.com/read-85690.html

New Post(0)