Managed code calls native code through platform invoke (P/Invoke): a method marked pinvokeimpl has no IL body; the runtime loads a library, finds the function and converts (marshals) the arguments. The opposite direction, native code calling managed methods, uses vtable fixups and exports. This page covers the syntax (ECMA-335 II.15.5, II.7.4 and II.23.4).
pinvokeimpl
pinvokeimpl("library" [as "function"] options) goes among the method attributes. The first string names the library, the optional second one the function in it if that differs from the method’s name; the library name may be left out of the parentheses. The method then has no instructions, only braces.
| Option | Meaning |
|---|---|
ansi, unicode, autochar | the character set strings are marshalled in (the last one wins) |
cdecl, stdcall, thiscall, fastcall, winapi | the native calling convention; winapi is the default of the platform (the standard calls it platformapi) |
nomangle | look the function up under exactly the name given (no character-set suffix) |
lasterr | the runtime keeps the last error code of the call for GetLastError-style queries |
bestfit:on, bestfit:off | whether characters that have no ANSI equivalent are mapped to a best fit |
charmaperror:on, charmaperror:off | whether an unmappable character throws an exception |
flags(n) | all the bits as a number |
.method public hidebysig static pinvokeimpl("user32.dll" unicode winapi lasterr)
int32 MessageBoxW(native int hWnd, string text, string caption, uint32 type)
cil managed preservesig
{
}
MessageBoxW; the compilers emit methods of this shape for the DllImport attribute. The standard’s own example (II.15.5.2) puts native unmanaged after the parameters instead of cil managed and allows pinvokeimpl on global methods only, while real assemblies hold such methods inside classes.The marshal clause
A parameter, a return type or a field can say how its managed type is converted: marshal(native type), placed after the type. marshal({ hex bytes }) gives the descriptor blob itself (II.23.4). An empty marshal() is accepted.
| Native type | Meaning |
|---|---|
bool, variant bool | a 4-byte Boolean (any non-zero value is true); the 2-byte VARIANT_BOOL |
int8, int16, int32, int64, unsigned int8 … unsigned int64 (or uint8 … uint64) | fixed-size integers |
int, unsigned int, uint | an integer the size of a pointer |
float32, float64 | IEEE single and double |
lpstr, lpwstr, lptstr | a pointer to a null-terminated ANSI, Unicode or platform-character string |
bstr, ansi bstr, tbstr | a COM BSTR (length-prefixed), in Unicode, ANSI or platform characters |
byvalstr, fixed sysstring [n] | a string stored in place in a structure: the first as ilasm’s byvalstr word, the second as a fixed-size string of n characters |
[], [n], [+p], [n+p] after a type (bool[5]) | a native array: the size is taken from the actual array, or is n elements, or comes from parameter p (counted from 0), or is n plus the value of parameter p; only for methods when p is used |
fixed array [n] type | an array of n elements stored in place in a structure |
iunknown, idispatch, interface, each with an optional (iidparam = n) | a COM interface pointer: IUnknown, IDispatch, or a given interface; n names the parameter that holds the interface identifier |
safearray variant type [, "type name"] | a COM safe array of that element type, with an optional name of the managed element type |
struct, lpstruct | a structure copied in place; a pointer to a structure |
method | a function pointer |
as any | a value whose native type is decided at call time |
currency, error | a COM CURRENCY; a 32-bit error code |
custom("marshaler", "cookie") | a user-written marshaler: the name of its type and a string handed to it (the older four-string form is read with its first two strings ignored and a warning) |
variant, syschar, void, decimal, date, objectref, nested struct, and a trailing * | older native types; ilasm accepts them and warns that they are deprecated |
For safearray (and the older variant types) the element type is one of the COM variant types: null variant currency void bool int8 to int64 and their unsigned forms float32 float64 decimal date bstr lpstr lpwstr iunknown idispatch safearray int unsigned int error hresult carray userdefined record filetime blob stream storage streamed_object stored_object blob_object cf clsid, and the modifiers * (a pointer), [] (an array), vector and & (by reference) after a type.
.method int32 M1(int32 marshal(int32), bool[] marshal(bool[5]))
.method int32 M3(int32 marshal(int32), bool[] marshal(bool[7+1]))
.method void Names([in] string marshal(lpwstr) text, object marshal(iunknown(iidparam = 2)) com, int32 marshal(safearray bstr, "System.String") list)
M1 and M3 of ECMA-335 II.7.4; the third uses the COM forms.Calling managed code from native code
.vtfixup [count] flags at datalabel (top level only) declares that a table of metadata tokens sits at the data label and that the runtime must turn each token into a method pointer when the file is loaded. The flags are int32 or int64 (the width of a slot; 32 is the default), fromunmanaged (generate a thunk that converts an unmanaged call into a managed one and marshals the arguments), and, in ilasm, retainappdomain and callmostderived. The table itself is written with .data (usually as bytearray of the tokens). Inside a method, .export [n] [as name] gives the method an export ordinal (and an alias) and .vtentry entry : slot says which table entry and slot hold it. .vtable = ( … ) is the deprecated form of the table, and .class extern and .export at the top level are about exported types (Assemblies and modules).
.data VT_01 = int32(0x06000001)
.vtfixup [1] int32 fromunmanaged at VT_01
.method public static void Entry() cil managed
{
.vtentry 1 : 1
.export [1] as Entry
ret
}
Mixed-mode (C++/CLI) assemblies are the usual users of these directives; the standard notes that the mechanism is not part of the Kernel Profile.
Not described
Known, not yet covered
- Reference: the encoding of each native type in a marshalling descriptor (II.23.4) and the values of
System.Runtime.InteropServices.UnmanagedTypeare not listed, and the exact runtime meaning of the older COM-oriented native types (byvalstr,objectref,lpstruct,as any) is only named, not explained: they come from the grammar and the standard’s short table. - Reference: the meaning of
retainappdomainandcallmostderived(flags of ilasm’s.vtfixupthat the standard does not list) was not found in any documentation read.