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). 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;