PInvoke,
short for
Platform Invocation Services, lets C#
access functions, structs, and callbacks in unmanaged DLLs. For
example, perhaps you wish to call the MessageBox
function in the Windows user32.dll
:
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCation, UINT uType);
To call this function, you can write a static
extern
method decorated with the
DllImport
attribute:
using System.Runtime.InteropServices; [DllImport("user32.dll")] static extern int MessageBox(int hWnd, string text, string caption, int type);
PInvoke then finds and loads the required Win32 DLLs and resolves the
entry point of the requested function. The CLR includes a marshaler
that knows how to convert parameters and return values between .NET
types and unmanaged types. In this example the int
parameters translate directly to four-byte integers that the function
expects, and the string
parameters are converted
to null-terminated arrays of characters using one-byte ANSI
characters under Win9x or two-byte Unicode characters under
WinNT/Win2K.
The
CLR marshaler is a .NET facility that
knows about the core types used by COM and the Windows API and
provides default translations to CLR types for you. The
bool
type, for instance, can be translated into a
two-byte Windows BOOL
type or a four-byte
Boolean
type. You can override a default
translation using the MarshalAs
attribute:
using System.Runtime.InteropServices; static extern int Foo([MarshalAs(UnmanagedType.LPStr)] string s);
In this case, the marshaler was told to use LPStr
,
so it will always use ANSI characters. Array classes and the
StringBuilder
class will copy the marshaled value
from an external function back to the managed value, as follows:
using System.Runtime.InteropServices; [DllImport("kernel32.dll")] static extern int GetWindowsDirectory(StringBuilder sb, int maxChars); class Test { static void Main( ) { StringBuilder s = new String(256); GetWindowsDirectory(s, 256); Console.WriteLine(s); } }
Passing
a
class or struct to a C function requires marking the struct or class
with the StructLayout
attribute:
using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] 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; } class Test { [DllImport("kernel32.dll")] static extern void GetSystemTime(SystemTime t); static void Main( ) { SystemTime t = new SystemTime( ); GetSystemTime(t); Console.WriteLine(t.wYear); } }
In both C and C#, fields in an object are located at
n number of bytes from the address of that
object. The difference is that a C# program finds this offset by
looking it up using the field name; C field names are compiled
directly into offsets. For instance, in C, wDay
is
just a token to represent whatever is at the address of a
SystemTime
instance plus 24 bytes.
For access speed and future widening of a datatype, these offsets are
usually in multiples of a minimum width, called the pack
size
.
For .NET types, the pack size is usually set at the discretion of the
runtime, but by using the StructLayout
attribute,
field offsets can be controlled. The default pack size when using
this attribute is 8 bytes, but it can be set to 1, 2, 4, 8, or 16
bytes, and there are also explicit options to control individual
field offsets. This lets a .NET type be passed to a C function.
The previous Test
example works if SystemTime
is a struct and
t
is a ref
parameter, but is
actually less efficient:
struct SystemTime {...} static extern void GetSystemTime(ref SystemTime t);
This is because the marshaler must always create fresh values for
external parameters, so the previous method copies
t
when going in
to the function
and then copies the marshaled t
when coming
out
of the function. By default, pass-by-value
parameters are copied in
, C#
ref
parameters are copied
in
/out
, and C#
out
parameters are copied out
,
but there are exceptions for the types that have custom conversions.
For instance, array classes and the StringBuilder
class require copying when coming out of a function, so they
are
in
/out
. It is
occasionally useful to override this behavior, with the
in
and out
attributes. For
example, if an array should be read-only, the in
modifier indicates to only copy the array going into the function,
and not coming out of it:
static extern void Foo([in] int[] array);
C#
can
not only call C functions but can also be called by C functions,
using callbacks. In C# a delegate
type is used in
place of a function pointer:
class Test { delegate bool CallBack(int hWnd, int lParam); [DllImport("user32.dll")] static extern int EnumWindows(CallBack hWnd, int lParam); static bool PrintWindow(int hWnd, int lParam) { Console.WriteLine(hWnd); return true; } static void Main( ) { CallBack e = new CallBack(PrintWindow); EnumWindows(e, 0); } }
The BCL provides a set of attributes you can use to mark up your objects with information that is used by the CLR marshaling services to alter their default marshaling behavior.
This section describes the most common attributes you will need when
interoperating with native Win32 DLLs. These attributes all exist in
the System.Runtime.InteropServices
namespace.
[DllImport (
dll-name
|
[, EntryPoint=
function-name
]?
|
[, CharSet=
charset-enum
]?
|
[, SetLastError=true|false]?
|
[, ExactSpelling=true|false]?
|
[, CallingConvention=
callconv-enum
]?)]
|
(for methods) |
The DllImport
attribute annotates an external
function that defines a DLL entry point. The parameters for this
attribute are:
dll-name
A string specifying the name of the DLL.
function-name
A string specifying the function name in the DLL. This is useful if you want the name of your C# function to be different from the name of the DLL function.
charset-enum
A CharSet
enum, specifying how to marshal strings.
The default value is CharSet.Auto
, which converts
strings to ANSI characters on Win98, and Unicode characters on WinNT.
SetLastError
If true
, preserves the Win32 error info. The
default is false
.
ExactSpelling
If true
, the EntryPoint
must
exactly match the function. If false
,
name-matching heuristics are used. The default is
false
.
callconv-enum
A CallingConvention
enum, specifying the mode to
use with the EntryPoint
. Default is
StdCall
.
[StructLayout(
layout-enum
|
[, Pack=
packing-size ]? |
[, CharSet=
charset-enum ]? |
[, CheckFastMarshal= [true |false])?]
|
(for classes, structs) |
The StructLayout
attribute specifies how the data
members of a class or struct should be laid out in memory. Although
this attribute is commonly used when declaring structures that are
passed to or returned from native DLLs, it can also define data
structures suited to file and network I/O. The parameters for this
attribute are:
layout-enum
A LayoutKind
enum, which can be 1)
sequential, which lays out fields one after the
next with a minimum pack size; 2) union, which
makes all fields have an offset of 0, so long as they are value
types; 3) explicit, which lets each field have a
custom offset.
packing-size
An int
specifying whether the packing size is 1,
2, 4, 8, or 16 bytes. The default value is 8.
charset-enum
A CharSet
enum, specifying how to marshal strings.
The default value is CharSet.Auto
, which converts
strings to ANSI characters on Win98, and Unicode characters on WinNT.
CheckFastMarshal
A Boolean value specifying whether a compile-time warning should be
generated if the type isn’t block transferable
(“blittable”). The default is false
.
[FieldOffset (
byte-offset
)] (for fields) |
The FieldOffset
attribute is used within a class
or struct that has explicit field layout. This attribute can be
applied to a field and specifies the field offset in bytes from the
start of the class or struct. Note that these offsets don’t
have to be strictly increasing and can overlap, thus creating a union
data structure.
[MarshalAs(
unmanaged-type
)
|
[, named-parameters ]?]
|
(for fields, parameters, return values) |
The
MarshalAs
attribute overrides the default
marshaling behavior the marshaler applies to a parameter or field.
The unmanaged-type
value is taken from the
UnmanagedType
enum; see the following list for
the permissible values:
For a detailed description of how and when to use each of these enum
values, as well as other legal
named-parameters
, see the .NET Framework
SDK documentation.
18.118.142.250