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

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 IntPtr . 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 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 TypeManaged Data Type
Input ParameterOutput 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 TypesManaged Data Type
Supply DataReceive 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 TypeManaged Data Type
Supply DataReceive 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;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Delphi获取Windows版本号、描述等信息,可获取到主版本号、次版本号、系统描述、系统平台、构建号等,相关代码如下:   //设置版本信息结构的大小    GetVersionEx(OSVI);    //获取版本信息    is98orlater:=    //判断是否98或以后版本    (osvi.dwPlatformId=VER_PLATFORM_WIN32_WINDOWS) and    ((osvi.dwMajorVersion>4) or    ((osvi.dwMajorVersion=4) and (osvi.dwMinorVersion>0)));   //DOWNLOAD BY HTTP://WWW.CODEFANS.NET    //下面开始显示信息    case OSVI.dwPlatformId of    //根据OSVI.dwPlatformId的数值的不同显示具体的平台描述    VER_PLATFORM_WIN32s:    // Windows 3.1平台    s:='Windows 3.1';    VER_PLATFORM_WIN32_WINDOWS:    // Windows 95/98平台    if(is98orlater) then    //98    s:='Windows 98'    else    //95    s:='Windows 95';    VER_PLATFORM_WIN32_NT:    // Windows NT平台    s:='Windows NT/XP';    end;    Edit1.Text:=s;    Edit2.Text:=IntToStr(OSVI.dwMajorVersion);    Edit3.Text:=IntToStr(OSVI.dwMinorVersion);    case OSVI.dwPlatformId of    //根据平台的不同具体处理OSVI.dwBuildNumber信息    VER_PLATFORM_WIN32_WINDOWS:    // Windows 95/98平台则取OSVI.dwBuildNumber的低位字    Edit4.Text:=IntToStr(LOWORD(OSVI.dwBuildNumber));    VER_PLATFORM_WIN32_NT:    // Windows NT平台则取所有位的值    Edit4.Text:=IntToStr(OSVI.dwBuildNumber);    else    Edit4.Text:='';   // Windows 3.1平台此值位空    end;    Edit5.Text:=OSVI.szCSDVersion;   end;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值