
Value Types Used in Platform Invoke
In the following example the Point and Rect types provide member layout information using the StructLayoutAttribute.
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
Public x As Integer
Public y As Integer
End Structure
<StructLayout(LayoutKind.Explicit)> Public Structure Rect
<FieldOffset(0)> Public left As Integer
<FieldOffset(4)> Public top As Integer
<FieldOffset(8)> Public right As Integer
<FieldOffset(12)> Public bottom As Integer
End Structure
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit)]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}
When marshaled to unmanaged code, these formatted types are marshaled as C-style structures. This provides an easy way of calling an unmanaged API that has structure arguments. For example, the POINT and RECT structures can be passed to the Microsoft Win32 API PtInRect function as follows:
BOOL PtInRect(const RECT *lprc, POINT pt);
You can pass structures using the following platform invoke definition:
Class Win32API
Declare Auto Function PtInRect Lib "User32.dll" _
(ByRef r As Rect, p As Point) As Boolean
End Class
class Win32API {
[DllImport("User32.dll")]
public static extern Bool PtInRect(ref Rect r, Point p);
}
The Rect value type must be passed by reference because the unmanaged API is expecting a pointer to a RECT to be passed to the function. The Point value type is passed by value because the unmanaged API expects the POINT to be passed on the stack. This subtle difference is very important. References are passed to unmanaged code as pointers. Values are passed to unmanaged code on the stack.
Note: |
|---|
When a formatted type is marshaled as a structure, only the fields within the type are accessible. If the type has methods, properties, or events, they are inaccessible from unmanaged code.
|
Classes can also be marshaled to unmanaged code as C-style structures, provided they have fixed member layout. The member layout information for a class is also provided with the StructLayoutAttribute attribute. The main difference between value types with fixed layout and classes with fixed layout is the way in which they are marshaled to unmanaged code. Value types are passed by value (on the stack) and consequently any changes made to the members of the type by the callee are not seen by the caller. Reference types are passed by reference (a reference to the type is passed on the stack); consequently, all changes made to blittable-type members of a type by the callee are seen by the caller.
Note: |
|---|
If a reference type has members of non-blittable types, conversion is required twice: the first time when an argument is passed to the unmanaged side and the second time on return from the call. Due to this added overhead, In/Out parameters must be explicitly applied to an argument if the caller wants to see changes made by the callee.
|
In the following example, the SystemTime class has sequential member layout and can be passed to the Win32 API GetSystemTime function.
<StructLayout(LayoutKind.Sequential)> Public Class SystemTime
Public wYear As System.UInt16
Public wMonth As System.UInt16
Public wDayOfWeek As System.UInt16
Public wDay As System.UInt16
Public wHour As System.UInt16
Public wMinute As System.UInt16
Public wSecond As System.UInt16
Public wMilliseconds As System.UInt16
End Class
[StructLayout(LayoutKind.Sequential)]
public class SystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
The GetSystemTime function is defined as follows:
void GetSystemTime(SYSTEMTIME* SystemTime);
The equivalent platform invoke definition for GetSystemTime is as follows:
Public Class Win32
Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (ByVal sysTime _
As SystemTime)
End Class
class Win32API {
[DllImport("Kernel32.dll", CharSet=CharSet.Auto)]
public static extern void GetSystemTime(SystemTime st);
}
Notice that the SystemTime argument is not typed as a reference argument because SystemTime is a class, not a value type. Unlike value types, classes are always passed by reference.
The following code example shows a different Point class that has a method called SetXY. Because the type has sequential layout, it can be passed to unmanaged code and marshaled as a structure. However, the SetXY member is not callable from unmanaged code, even though the object is passed by reference.
<StructLayout(LayoutKind.Sequential)> Public Class Point
Private x, y As Integer
Public Sub SetXY(x As Integer, y As Integer)
Me.x = x
Me.y = y
End Sub
End Class
[StructLayout(LayoutKind.Sequential)]
public class Point {
int x, y;
public void SetXY(int x, int y){
this.x = x;
this.y = y;
}
}