Copyright © 2001, 2002 Southern Storm Software, Pty Ltd.
Permission to distribute unmodified copies of this work is hereby granted.
Implementations of the Common Language Runtime (CLR) on Win32 provide a large number of tools for converting between the well-defined world of CLR conventions, and the ill-defined world of Win32 API conventions. Attributes are added to method declarations that identify the DLL that contains the function, and the mechanism to use to convert CLR types into parameter types for the function. These attributes are converted into flags and token structures within the metadata of the program.
Implementations of the CLR on Unix need a similar set of tools. However, the problem is exacerbated by the variations in CPU types, API type definitions, and calling conventions that are common in the Unix community.
Because of this variation, it is difficult to build a comprehensive set of PInvoke tools that "just work". So, we have instead focused on defining a set of basic building blocks for marshalling parameters and return values. The programmer can build more complex marshalling rules on top of these building blocks using wrapper methods written in C# or C.
DllImport
" attribute:
[DllImport("libc")]
extern int puts(String s);
This ensures that a PInvoke record is created in the metadata with the
module reference set appropriately. All such functions are assumed to
have C calling conventions and a fixed number of arguments (i.e. no
variable argument lists).
However, things are not quite that simple. Different versions of Unix
may have the function in a different shared object. For example,
"socket
" appears in "libsocket.so
"
on Solaris 2.x, "libc.so.6
" on GNU/Linux, and
"libc.so
" on most other systems. The
"DllImportMap
" attribute can be used to specify
platform-specific mappings:
[DllImportMap("*-solaris2*", "cygwin1.dll", "libsocket.so")]
[DllImportMap("*-linux-*", "cygwin1.dll", "libc.so.6")]
[DllImportMap("std-shared-object", "cygwin1.dll", "libc.so")]
internal sealed class Native
{
[DllImport("cygwin1.dll")]
extern public static int socket(int domain, int type, int protocol);
}
Here, we have placed the Win32 name of the library (cygwin1.dll
)
into the "DllImport
" attribute. At runtime, the CLR will
use this value to look through the "DllImportMap
" specifications
for a platform-specific override.
The "DllImportMap
" attribute has three parameters: platform
key, original name, and new name. The engine searches for a platform key and
original name match, and uses the new name if a match is found. If no
match is found, it will fall back to whatever "DllImport
"
specifies. The attributes are searched in order of declaration.
The platform keys use the same host naming conventions as GNU autotools.
Asterisk wildcards can be used to match any sequence of characters.
Two special platform keys exist: "std-shared-object
" and
"std-win32-dll
". These provide fallbacks for Unix and Win32
respectiviely.
As a matter of coding style, the Win32 name should be specified in
"DllImport
" if it is available. This improves interoperability
with pre-existing CLR's.
Because the "DllImportMap
" attribute is not currently part
of the ECMA standard, the programmer must declare it themselves in their
application:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple=true, Inherited=false)]
internal sealed class DllImportMapAttribute : Attribute
{
public DllImportMapAttribute
(String platform, String origName, String newName) {}
}
The class can be declared in any namespace: the CLR ignores the namespace
when looking for the attribute.
C# Type | Parameter | Return |
bool |
int8 |
int8 |
sbyte |
int8 |
int8 |
byte |
uint8 |
uint8 |
short |
int16 |
int16 |
ushort |
uint16 |
uint16 |
char |
uint16 |
uint16 |
int |
int32 |
int32 |
uint |
uint32 |
uint32 |
long |
int64 |
int64 |
ulong |
uint64 |
uint64 |
float |
float32 |
float32 |
double |
float64 |
float64 |
System.IntPtr |
int32/int64 1 |
int32/int64 1 |
System.UIntPtr |
uint32/uint64 1 |
uint32/uint64 1 |
System.Object |
Object handle | Object handle |
System.String |
const char * 2 | const char * 3 |
System.Enum |
Underlying type 4 | Underlying type 4 |
System.ValueType |
Struct passed by value | Struct returned by value |
type * |
type * |
type * |
type[] |
type * 5 |
Object handle |
delegate |
Function pointer 6 | Object handle |
System.IntPtr
and System.UIntPtr
are
passed as either 32-bit or 64-bit integers, depending upon the
underlying platform. These types are guaranteed to be the
same size as a C pointer value.const char *
". The engine
uses either the current locale's character set or UTF-8,
depending upon the PInvoke character set settings.
If the string is null
, then NULL
will be passed to the underlying function.NULL
, then the null
string will be returned.null
,
then the function pointer will be NULL
.
[DllImport("libput")]
extern int PutString([MarshalAs(UnmanagedType.Interface)] String s);
The unmanaged type code "Interface
" is always recognised
on parameters and return values to disable special marshalling behaviours.