# PInvoke Conventions for Unix

Rhys Weatherley, rweather@southern-storm.com.au.
Last Modified: $Date: 2002/05/27 04:03:35 $

Copyright © 2001, 2002 Southern Storm Software, Pty Ltd.
Permission to distribute unmodified copies of this work is hereby granted.

## 1. Introduction

The ECMA Common Language Infrastructure (CLI) specification defines a mechanism that can be used to invoke functions that are written in native code, as opposed to IL bytecode. This mechanism is typically referred to as "PInvoke" or "It Just Works".

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.

## 2. Importing functions from shared objects

Functions are imported from Unix shared objects using the "`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.

## 3. Marshalling rules for types

The following table defines all of the C# type categories and their C counterparts when used as parameters or return values:

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

Notes:

1. `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.
2. String parameters are converted into a character buffer and passed as "`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.
3. Character buffers that are returned from the underlying function are converted back into C# string objects. The character set is dependent upon the PInvoke settings. If the function returns `NULL`, then the `null` string will be returned.
4. Enumerated values are converted into the underlying integer type and then passed using the same conventions as the integer type.
5. When passed as parameters, arrays are converted into a pointer to their first element.
6. When passed as parameters, delegates are wrapped in a native closure to turn them into a function pointer suitable for calling from C code. If the delegate is `null`, then the function pointer will be `NULL`.

The conversions that are performed on strings, arrays, and delegates can be disabled by specifying an explicit marshalling type:

> `[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.

Copyright © 2001, 2002 Southern Storm Software, Pty Ltd.
Permission to distribute unmodified copies of this work is hereby granted.