Article: P/Invoke, part 1

Home Page


Consultancy

  • Service Vouchers
  • Escrow Service

Shop



Programming
  • Articles
  • Tools
  • Links

Search

 

Contact

 

Chess Puzzles




DWHS

Valid XHTML 1.0 Transitional
Valid CSS!
Mobile-friendly!

Platform Invoke for calling native code from C# or VB.NET

category 'KB', language C# and VB.NET, created 20-Sep-2009, version V1.2 (17-Dec-2011), by Luc Pattyn


Work In Progress

This article isn't finished yet, it is "Work In Progress". It may still change a lot in the near future, it probably still needs clarifications, additions and corrections. It might also be split into more than one article, and/or get a different URL; it might even be removed after all.


License: The author hereby grants you a worldwide, non-exclusive license to use and redistribute the files and the source code in the article in any way you see fit, provided you keep the copyright notice in place; when code modifications are applied, the notice must reflect that. The author retains copyright to the article, you may not republish or otherwise make available the article, in whole or in part, without the prior written consent of the author.

Disclaimer: This work is provided “as is”, without any express or implied warranties or conditions or guarantees. You, the user, assume all risk in its use. In no event will the author be liable to you on any legal theory for any special, incidental, consequential, punitive or exemplary damages arising out of this license or the use of the work or otherwise.


In the Microsoft .NET Framework, Platform Invoke or P/Invoke is the name of the technology one would use to call unmanaged code (e.g. native C) from managed code languages such as C# and VB.NET. This article will attempt to provide the basic information on this very vast subject. It will not cover C++/CLI, the .NET managed C++ dialect, as I am unfamiliar with it, and its way of interacting with native code is or can be quite different.

Table of Content

Part 1

Part 2

Introduction
A simple example
Function parameters

Simple Value Types, Structs, Classes, Strings, Arrays, Delegates

Function return values
DllImport (C#, VB.NET) and Declare (VB.NET)
Win32 API functions

SendMessage & PostMessage, GetLastError

Two-side logging
Typical Exceptions
Various comments
Demo program
Conclusion

Structs

Introduction

This article is not complete; new information gets added when a need arises, however completeness will never be achieved since the subject is vast.

There are two typical situations where the managed/unmanaged boundary needs crossing:

  • a .NET application may need to call existing unmanaged code, for instance a legacy library or some Windows API; in this case the interface (function names, and parameter and return types) is fixed by the existing unmanaged code;
  • an application may be partitioned in two parts: one part consisting of managed code, to take advantage of the .NET Framework, and one part consisting of unmanaged code, maybe for reasons of available code, or for higher performance (e.g. using MMX/SSE instructions). Here the interface can be adapted to make the interoperation easier to implement and achieve better performance.

The overall approach is "Keep it Simple". Simple interop jobs can be solved with a few keywords, without ressorting to special tricks, such as the use of "unsafe code". Nevertheless we will have to touch a lot of keywords, attributes and flags to cover some of the more complex jobs.

Here is the official MSDN P/Invoke tutorial.

Note: a lot of hyperlinks are included; the web pages or web sites they refer to may have various quality levels. Don't shoot the messenger.

A simple example

Calling the Win32 function GetDiskFreeSpaceEx which takes one input parameter and provides three output values:

// this is the C API, copied from the documentation
    BOOL WINAPI GetDiskFreeSpaceEx(
      __in_opt   LPCTSTR lpDirectoryName,
      __out_opt  PULARGE_INTEGER lpFreeBytesAvailable,
      __out_opt  PULARGE_INTEGER lpTotalNumberOfBytes,
      __out_opt  PULARGE_INTEGER lpTotalNumberOfFreeBytes
    );
// managed code (C#)

using System.Runtime.InteropServices;     // in C# this statement is always required for P/Invoke

class Test {
    public void Test1() {
        // getting disk space information for C:
        string name=@"C:\";
        long lpFreeBytesAvailable;
        long lpTotalNumberOfBytes;
        long lpTotalNumberOfFreeBytes;
        GetDiskFreeSpaceEx(name, out lpFreeBytesAvailable, out lpTotalNumberOfBytes, out lpTotalNumberOfFreeBytes);
        Console.WriteLine("lpFreeBytesAvailable = "+lpFreeBytesAvailable+" bytes");
    }

    // this is the prototype
    [DllImport("kernel32.dll")]
    public static extern int GetDiskFreeSpaceEx(string rootPathName,
        out long lpFreeBytesAvailable, out long lpTotalNumberOfBytes, out long lpTotalNumberOfFreeBytes);
}
' managed code (VB.NET)

class Test
    Public Sub Test1() 
        Dim name As String = "C:\"
        Dim lpFreeBytesAvailable As Long
        Dim lpTotalNumberOfBytes As Long
        Dim lpTotalNumberOfFreeBytes As Long
        GetDiskFreeSpaceEx(name, lpFreeBytesAvailable, lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes)
        Console.WriteLine("lpFreeBytesAvailable = " & lpFreeBytesAvailable & " bytes")
    End Sub

    ' this is the prototype
    <DllImport("kernel32.dll")> _
    Public Shared Sub GetDiskFreeSpaceEx(ByVal rootPathName As String, _
        ByRef lpFreeBytesAvailable As Long, ByRef lpTotalNumberOfBytes As Long, ByRef lpTotalNumberOfFreeBytes As Long)
    End Sub

    ' this is an alternative syntax for the same prototype
    Declare Sub GetDiskFreeSpaceEx Lib "kernel32.dll" (ByVal rootPathName As String, _
        ByRef lpFreeBytesAvailable As Long, ByRef lpTotalNumberOfBytes As Long, ByRef lpTotalNumberOfFreeBytes As Long)
End Class

The prototype acts like a declaration, it adds the method to the current class without needing an implementation; so it is similar to what a function declaration in a .h file would be in a C/C++ environment. In C# the DllImport attribute and the keywords static and extern are essential; in VB.NET there are two ways to provide prototype information, they are based on some combination of the DllImport attribute and the keywords Declare, Lib and Shared; the long form (with an empty Sub or Function) is very similar to the C# one, the short form is available only for simple cases; we will use the shorter form when it fits. The parameter list, the return type, and the scope attribute can be modified as required or appropriate.

Function parameters

The data types on both sides should match sufficiently; their size should match perfectly, and reals versus integers should be correct; however signed versus unsigned integer may be unimportant.

Simple value types

Here are the most important type relations for numeric data (mostly copied from here):

Native code (e.g. C)

Managed code (e.g. C#)

Description

LONGLONG, long long

System.Int64, long

64-bit signed integer

ULONGLONG, unsigned long long

System.UInt64, ulong

64-bit unsigned integer

INT, LONG, BOOL, int, long

System.Int32, int

32-bit signed integer

UINT, ULONG, DWORD,
unsigned int, unsigned long

System.UInt32, uint

32-bit unsigned integer

SHORT, short

System.Int16, short

16-bit signed integer

WORD, unsigned short

System.UInt16, ushort

16-bit uncsigned integer

BYTE, unsigned char

System.Byte, byte

8-bit unsigned integer

CHAR, char

System.Char, char

8-bit signed integer, needs CharSet.Ansi

DOUBLE, double

System.Double, double

64-bit real

FLOAT, float

System.Single, float

32-bit real

Warning: some keywords have a different meaning on both sides; some examples:

  • in C# and VB.NET a long takes 8 bytes; in unmanaged C/C++ long only takes 4 bytes;
  • in C# and VB.NET a char is Unicode and takes 2 bytes; in unmanaged C/C++ a char is ANSI and only takes 1 byte;
  • in unmanaged code, an integer is often used to represent a boolean value where 0 is false, and 1 (or -1) is used to represent true; however any non-zero value is considered true; it would be risky to map such unmanaged boolean onto a managed boolean type as values other than 0 and 1 might be misunderstood.

Pointers to a simple value type can be handled quite easily by applying the proper keyword: ref or out in C#, ByRef in VB.NET

Complex value types: structures

Structs will be the topic of part 2.

Reference types: classes

One should not pass class instances (i.e. objects) to the other side; object code and data is understood well on the side where it belongs, and not on the other side. With two exception: strings and arrays.

Strings

Here are the most important type relations for string data:

Native code (e.g. C)

Managed code (e.g. C#)

Description

LPSTR, char*

System.StringBuilder

needs CharSet.Ansi

LPCSTR, const char*

System.String

needs CharSet.Ansi

LPWSTR, wchar_t*

System.StringBuilder

needs CharSet.Unicode

LPCWSTR, const wchar_t*

System.String

needs CharSet.Unicode

A writeable string parameter (char* in C) should be passed as a StringBuilder with sufficient capacity, see this GetVolumeInformation example:

// get the file system name
public static string GetFileSystemName(string rootPathName) {
    StringBuilder volName=new StringBuilder(256);
    StringBuilder fsName=new StringBuilder(256);
    int OK=GetVolumeInformation(rootPathName, volName, volName.Capacity,
        0, 0, 0, fsName, fsName.Capacity);
    if (OK!=0) return fsName.ToString();
    return null;
}

[DllImport("kernel32.dll")]
public static extern int GetVolumeInformation(  // BOOL
    string rootPathName,                        // LPCTSTR root directory
    StringBuilder lpVolumeNameBuffer,           // LPTSTR volume name buffer
    int nVolumeNameSize,                        // DWORD length of name buffer
    int lpVolumeSerialNumber,                   // LPDWORD volume serial number
    int lpMaximumComponentLength,               // LPDWORD maximum file name length
    int lpFileSystemFlags,                      // LPDWORD file system options
    StringBuilder lpFileSystemNameBuffer,       // LPTSTR file system name buffer
    int nFileSystemNameSize);                   // DWORD length of file system name buffer
' get the file system name
Private Function GetFileSystemName(ByVal name As String) As String
    Dim volName As StringBuilder = New StringBuilder(256)
    Dim fsName As StringBuilder = New StringBuilder(256)
    Dim OK As Boolean = Kernel32.GetVolumeInformation(name, volName, volName.Capacity, _
        0, 0, 0, fsName, fsName.Capacity)
    If (OK) Return fsName.ToString()
    Return Nothing
End Sub

Declare Auto Function GetVolumeInformation Lib "kernel32.dll" _
   (ByVal rootPathName As String, _
    ByVal lpVolumeNameBuffer As StringBuilder, _
    ByVal nVolumeNameSize As Integer, _
    ByVal lpVolumeSerialNumber As Integer, _
    ByVal lpMaximumComponentLength As Integer, _
    ByVal lpFileSystemFlags As Integer, _
    ByVal lpFileSystemNameBuffer As StringBuilder, _
    ByVal nFileSystemNameSize As Integer) As Boolean

Strings marshalling is controlled by the DllImport CharSet attribute in C#, and by the Auto, ANSI or Unicode keywords in VB.NET; it takes care of the ANSI-Unicode divide.

Arrays

I'm only considering one-dimensional arrays of some elementary value type allocated by managed code. Higher-dimensioned arrays, and arrays of objects, are to be avoided; and in a mixed world I strongly recommend objects get created by managed code, this avoids the need for unmanaged objects to be upgraded to managed objects.

The main concern here is not to copy the data, but really passing a pointer, and doing so in a safe way; remember, the managed world has a garbage collector which may run at any time, and might move objects around. We will present up to three ways for passing arrays. The first one pins the array automatically, the other two (only one in VB.NET) do it explicitly:

  1. in the automatic way, everything gets handled automatically by the marshaling logic; the array gets pinned so it can't be moved around, its pointer gets passed, and when the function returns the array gets unpinned. This approach is the preferred one; the others are more special-purpose. This is all that is needed:
    public int ArrayAutomatic() {
        int dim=1000;
        int[] numbers=new int[dim];
        ...
        int sum=SumArray(numbers, dim);
        return sum;
    }
    
    [DllImport("native.dll")]
    unsafe public static extern int SumArray(int[] numbers, int count);
    
    Public Function ArrayAutomatic() As Integer
        Dim dime As Integer = 1000
        Dim numbers(dime) As Integer
        ...
        Dim sum As Integer = SumArray(numbers, dime)
        Return sum
    End Function
    
    Declare Auto Function SumArray Lib "NativeC.dll" (ByVal numbers As Integer(), ByVal count As Integer) As Integer
    
  2. Using the fixed keyword (C# only): it requires the unsafe keyword, and the "allow unsafe code" compiler switch; it fixes the object, and allows you to get its pointer. The advantage is the pointer can be modified before being passed on, so it is possible to pass less than the full array to the native world. Here is a C# example (as VB.NET does not have pointers, this approach is not available in VB.NET):
    unsafe public int ArrayFixed() {
        int dim=1000;
        int[] numbers=new int[dim];
        ...
        int sum;
        fixed (int* pNumbers=&numbers[0]) {
            sum=SumArray(pNumbers, dim);
        }
        return sum;
    }
    
    [DllImport("native.dll")]
    unsafe public static extern int SumArray(int* pNumbers, int count);
    
  3. Using the GCHandle class: it does not require any unsafe stuff; one needs to pin the object, get the pointer, and when done to unpin the object; the advantage here is the GCHandle can be kept around much longer, e.g. when the native world wants to keep the pointer alive and use it after the function has returned, maybe to perform some asynchronous input/output. Here is a C# example:
    public int ArrayHandle() {
        int sum=0;
        int dim=10000;
        int[] numbers=new int[dim];
        ...
        GCHandle handle=GCHandle.Alloc(numbers, GCHandleType.Pinned);
        sum=SumArray(handle.AddrOfPinnedObject(), dim);
        handle.Free();
        return sum;
    }
    
    [DllImport("nativeC.dll")]
    public static extern int SumArray(IntPtr pNumbers, int count);
    
    Public Function ArrayHandle() As Integer
        Dim dime As Integer = 1000
        Dim numbers(dime) As Integer
        ...
        Dim handle As GCHandle = GCHandle.Alloc(numbers, GCHandleType.Pinned)
        Dim sum As Integer = SumArray(handle.AddrOfPinnedObject(), dime)
        handle.Free()
        Return sum
    End Function
    
    Declare Auto Function SumArray Lib "NativeC.dll" (ByVal pNumbers As IntPtr, ByVal count As Integer) As Integer
    

Note how the parameter types are different in each case. By the way, you can overload prototypes just like any other methods in .NET, so it is perfectly legal to provide more than one prototype for any given native function (the examples above will be calling the appropriate overload of one and the same function, where the first parameter is either an array, a pointer, or an IntPtr.

I tend to prefer the GCHandle method, as it does not require any unsafe keyword and switch; it also has the advantage one can keep the object pinned for as long as the pointer is in use in the native world (it may store the pointer for future reference, e.g. when asynchronous input/output is going to be performed). There obviously is a risk of forgetting to free the handle when done, which would limit the freedom the garbage collector has. For both methods: when too many objects get pinned down, your app may run into memory problems as there would no longer be a way to reshuffle the objects to consolidate the small free memory blocks in one large one.

Delegates

Once in a while you want the native code to call a managed method; well, you can pass a delegate and pick it up and use it as a function pointer. The two-side logging chapter offers one example. And here is another typical one, using the Win32 EnumWindows function which requires a "callback function":

BOOL EnumWindows(
    WNDENUMPROC lpEnumFunc,
    LPARAM lParam
);

BOOL CALLBACK EnumWindowsProc(
    HWND hwnd,
    LPARAM lParam
);

BOOL IsWindowVisible( 
    HWND hWnd
);
class DelegateExample {
    private List<IntPtr> windowList;

    public List<IntPtr> GetVisibleWindowHandles() {
        windowList=new List<IntPtr>();
        EnumWindows(new EnumWindowsProc(CollectVisibleWindows), IntPtr.Zero);
        Console.WriteLine("There are {0} visible windows", windowList.Count);
        return windowList;
    }

    private bool CollectVisibleWindows(IntPtr hWnd, IntPtr lParam) {
        if(IsWindowVisible(hWnd)) windowList.Add(hWnd);
        return true;  // please continue enumeration !
    }

    [DllImport("user32.dll")]
    private static extern int EnumWindows(LP_EnumWindowsProc ewp, IntPtr lParam); 

    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern bool IsWindowVisible(IntPtr hWnd);
}
Class DelegateExample
    Dim windowList As List(Of IntPtr)

    Public Function GetVisibleWindowHandles() As List(Of IntPtr)
        windowList = New List(Of IntPtr)
        EnumWindows(New EnumWindowsProc(AddressOf CollectVisibleWindows), IntPtr.Zero)
        Console.WriteLine("There are {0} visible windows", windowList.Count)
        Return windowList
    End Function

    Public Function CollectVisibleWindows(ByVal hWnd As IntPtr, ByVal lParam As IntPtr) As Boolean
        If IsWindowVisible(hWnd) Then windowList.Add(hWnd)
        Return True  ' please continue enumeration !
    End Function

    Public Delegate Function EnumWindowsProc(ByVal hWnd As IntPtr, ByVal lParam As IntPtr) As Boolean

    Declare Auto Function EnumWindows Lib "user32.dll" (ByVal ewp As User32.EnumWindowsProc, _
                                                        ByVal lParam As IntPtr) As Integer

    Declare Auto Function IsWindowVisible Lib "user32.dll" (ByVal hWnd As IntPtr) As Boolean
End Class

Some comments, repeating statements made elsewhere in the article:

  • the managed prototypes (EnumWindows, IsWindowVisible) must sufficiently match their native counterparts;
  • pointers that go from native to managed to simply pass them to native again (the hWnd handle here) are modeled with IntPtr;
  • the boolean return value of IsWindowVisible() was sufficiently trusted to make it a managed bool;
  • In this example the lParam parameter wasn't used.

Function return values

Return values mostly follow the same guidelines as function parameters. However I do not recommend some return types:

  • pointers: native objects cannot really be used by managed code, so it often does not make any sense to return a pointer; the main exception would be one native function returning a pointer that is going to be needed by another native function. In such event, use IntPtr for every occurrence.
  • strings: the easiest way to get a string value is by providing a StringBuilder with sufficient capacity, and have it filled by the native function, then applying a string s=stringBuilder.ToString() to it. If that is not practical, consider using Marshal.PtrToStringAnsi() or one of its siblings; be aware that these methods create a managed copy but don't free the original unmanaged string.
  • structs (and pointers to structs): see part 2.

DllImport (C#, VB.NET) and Declare (VB.NET)

In C# all prototypes need a DllImport attribute specifying the name of the DLL file; in VB.NET all prototypes can use a similar approach (however there is an alternative for simple cases). One normally specifies a simple filename without path; the extension is ".DLL" by default. DLL files are searched in the standard Windows way, i.e. in the directory of the EXE first, and in all the folders specified in the environment variable "PATH" next. It would be unwise to specify a path in the DllImport statement as that would hamper the usage of the code on another system.
Note 1: P/Invoke can only work on native code DLLs, so the GAC (Global Assembly Cache) is not involved whatsoever.
Note 2: the native DLL files are not used or checked during managed code compilation; at run-time, any DLL file not found, and any function not found, will result in an Exception being thrown.

Besides the mandatory file name, a prototype may contain more information, however all of that is optional. It gets explained here and here. I'll reproduce the essentials:

  • CharSet: controls how strings get marshalled and also how the function name get mangled (appending an 'A' or 'W'). The default value is CharSet.Ansi which means all strings automatically get marshalled from managed Unicode to native ANSI, which is fine if the native code specifies char* or const char*. Note: for non-ANSI characters an exception would be thrown. Use CharSet.Unicode to get the wide version of a Win32 function, as in
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern MessageBoxResult MessageBox(IntPtr hWnd, string text, string caption, MessageBoxOptions options);
    
    Declare Unicode Function MessageBox Lib "user32.dll" (ByVal hWnd as IntPtr, ByVal text As String, _
        ByVal caption As String, ByVal options As MessageBoxOptions) As MessageBoxResult
    
  • SetLastError: is described below.
  • CallingConvention: chooses how parameters get passed around, and who is responsible for cleaning up the stack; see here; the default value is CallingConvention.StdCall which is fine for Win32, and for native C code. While earlier versions of .NET show some tolerance to mistakes, starting with .NET 4.0 they'd better be declared accurately.
  • EntryPoint: specifies the exact name of the function inside the native DLL; this isn't really useful for a C-coded DLL, but it could be very useful to hide the name mangling that goes on in a C++ DLL, so you could use unmangled names in the managed world, and apply the mangling through the EntryPoint attribute.

There are some more DllImport options, but I have never felt a need for them.

VB.NET supports a simpler syntax (without DllImport) which is adequate when all that is required is the DLL file name and the charset information. It consists of the Declare and Lib keywords, optionally augmented by a Ansi, Auto or Unicode keyword. Most VB.NET examples in this article use the short version.

Win32 API functions

Obviously the first thing you should do is read the documentation on the functions you plan on using. MSDN or other sites should be helpful here.

It is a pity Microsoft never decided to provide all the Win32 prototypes in some class. The primary sources of information would be www.pinvoke.net; this site lists lots of Win32 functions and data structures; it is not complete, and there are some errors, so please take it with a grain of salt, and check it against the general principles as laid out in this article.

SendMessage & PostMessage

Popular Win32 functions are SendMessage and PostMessage, which send or post a window message to some window. They have two special parameters, known as wParam and lParam, which have different meaning depending on the message ID. When they are pointers, they should be modeled as IntPtr, and not as int or long; the reason is Win32 and Win64 use 4 and 8 bytes respectively, and IntPtr adapts to that automatically, whereas integer types don't. As wParam and/or lParam sometimes have numeric meaning, one can use IntPtr.Zero and new IntPtr(intValue), however it makes perfect sense to have several prototypes:

LRESULT SendMessage(      
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);
private const uint WM_COMMAND = 0x0111;
private const uint LV_VIEW_DETAILS=0x702c;

SendMessage(listviewHandle, WM_COMMAND, new IntPtr(LV_VIEW_DETAILS), IntPtr.Zero);
SendMessage(listviewHandle, WM_COMMAND, LV_VIEW_DETAILS, 0);

// general prototype, can be used for all messages
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr Hdc, uint Msg, IntPtr wParam, IntPtr lParam);

// specialised prototype, could be used for int-int messages, such as WM_COMMAND
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr Hdc, uint Msg, int wParam, int lParam);
TBD

GetLastError

This example shows one way to use the last error functionality. Windows keeps an error status for each thread; most Win32 functions either don't touch it, or set it when something goes wrong. The error code can be set (normally cleared) by calling SetLastError, and be read by calling GetLastError. The marshaling logic itself uses these functions, and offers its own error code handling; so managed code should rely on Marshal.GetLastWin32Error. It is essential to flag the Win32 functions that provide such error status , by adding SetLastError=true (BTW: in VB.NET the Declare keyword does this implicitly). And it seems LastWin32Error does not accumulate, i.e. Marshal.GetLastWin32Error returns the error of the last Win32 call, and not any previous ones.

private void GetPrinterInfo(string name) {
    IntPtr hPrinter;
    int sizeNeeded=0;
    bool OK=OpenPrinter(name, out hPrinter, 0);
    if (OK) GetPrinter(hPrinter, infoNumber, IntPtr.Zero, 0, ref sizeNeeded);
    if (OK) OK=GetPrinter(hPrinter, infoNumber, pAddr, sizeNeeded, ref sizeNeeded);
    if (OK) info2=(PRINTER_INFO_2)Marshal.PtrToStructure(pAddr, typeof(PRINTER_INFO_2));
    if (OK) ClosePrinter(hPrinter);
    if (!OK) {
        int errorCode=Marshal.GetLastWin32Error();
        log(FormatMessage(errorCode));
    }
}

[DllImport("winspool.drv", SetLastError=true)]
private static extern bool OpenPrinter(string printerName, out IntPtr hPrinter, int printerDefaults);

[DllImport("winspool.drv", SetLastError=true)]
private static extern bool ClosePrinter(IntPtr hPrinter);

[DllImport("winspool.drv", SetLastError=true)]
private static extern bool GetPrinter(IntPtr hPrinter, int level, IntPtr pi2,
    int bufSize, ref int sizeNeeded);	

// translate an error code to an error message
public static string FormatMessage(int errorCode) {
    int capacity=512;
    int FORMAT_MESSAGE_FROM_SYSTEM=0x00001000;
    StringBuilder sb=new StringBuilder(capacity);
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, errorCode, 0,
	    sb, sb.Capacity, IntPtr.Zero);
    int i=sb.Length;
    if (i>0 && sb[i - 1]==10) i--;	// remove final LF character
    if (i>0 && sb[i - 1]==13) i--;	// remove final CR character
    sb.Length=i;
    return sb.ToString();
}

[DllImport("kernel32.dll")]
public static extern int FormatMessage(int dwFlags, IntPtr lpSource, int dwMessageId,
    int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr Arguments);
TBD

Two-side logging

When in charge of the native and the managed code, the first thing I do is implement a single logging mechanism available on both sides. This is how it works:

  • On the managed side, I provide a log(string) or log(int, string) method which takes a one-line string (and an optional thread ID) and appends it to the log; this log can be a text file, or a ListBox, or whatever you choose to store and/or visualize a number of chronological messages for viewing and analyzing while debugging the application.
  • On the native side, I also provide a log(string) function, which offers the same functionality; technically the only thing it does is pass its parameter to the managed log method, by using a delegate. That is how I manage to intertwine native and managed log messages.

This could be a log output, the first and last lines are from the managed code (showing thread ID=09), the middle three (thread ID faked to zero) are from the native C code:

00:40:05.547 [09]     going to call C function that logs
00:40:05.547 [00]     NativeLoggingDemo message #12
00:40:05.547 [00]     NativeLoggingDemo message #13
00:40:05.547 [00]     NativeLoggingDemo message #14
00:40:05.547 [09]     returned from C function that logs

This is the actual code that would do so:

// native code (C example)
    // WARNING: the __stdcall keyword is essential here
    typedef void (__stdcall* LOGHANDLER) (int threadID, const char *msg);
    LOGHANDLER logHandler;	// this function pointer will hold the C# delegate

    // store a delegate for future use
    __declspec(dllexport) void NativeSetLogHandler(LOGHANDLER handler) {
        logHandler=handler;
    }
	
    // log one string using the managed delegate
    void log(const char* msg) {
        if (logHandler!=0) logHandler(0, msg);	// we choose to always use threadID zero for native code
    }

    __declspec(dllexport) void NativeLoggingDemo(int i) {
        char buf[80];
        sprintf_s(buf, 80, "NativeLoggingDemo message #%d", i);
        log(s);
    }

// managed code (C# example)
class LogDemoForm : Form {
    // define the logging delegate
    public delegate void LogHandler(int threadID, string s);

    // keep the delegate alive by storing it here
    LogHandler logHandler=new LogHandler(log);
	
    // pass the log delegate to the native code, so it too can use our logging mechanism
    protected void initialize() {
        NativeSetLogHandler(logHandler);
    }

    protected void demo() {
        log("going to call C function that logs");
        for(int i=12; i<15; i++) NativeLoggingDemo(i);
        log("returned from C function that logs");
    }

    // log one string; automatically include the ID of the current thread
    protected void log(string s) {
        log(Thread.CurrentThread.ManagedThreadId, s);
    }
	
    // log one string; insert current time and threadID
    // we use the Control.Invoke pattern so the bulk of it always executes on the main thread and can access GUI Controls
    protected void log(int threadID, string s) {
        if (this.InvokeRequired) {
            this.Invoke(logHandler, new object[]{threadID, s});
        } else {
            threadID=threadID & 0xFF;
            s=DateTime.Now.ToString("HH:mm:ss.fff")+" ["+threadID.ToString("X2")+"] "+s;
            Console.WriteLine(s);
            if (logFile!=null) logFile.WriteLine(s);
            if (listBox!=null) listbox.Items.Add(s); 
        } 
    }
	
    // prototype of the native function that stores our logging delegate
    [DllImport("nativeCode.dll")]
    private static extern void NativeSetLogHandler(LogHandler logHandler);
	
    // prototype of the native function that stores our logging delegate
    [DllImport("nativeCode.dll")]
    private static extern void NativeLoggingDemo(int i);
}
// managed code (C# example)
Class LogDemoForm
    Implements Form
    ' define the logging delegate
    Public Delegate Sub LogHandler(ByVal threadID As Integer, ByVal s As String)

    ' keep the delegate alive by storing it here
    Dim logHandler As NativeC.LogHandler

    ' pass the log delegate to the native code, so it too can use our logging mechanism
    protected Sub initialize()
        logHandler = New LogHandler(AddressOf log)
        NativeSetLogHandler(logHandler)
    End Sub

    protected Sub demo()
        log("going to call C function that logs")
        For i As Integer = 12 to 14 
            NativeLoggingDemo(i)
        Next
        log("returned from C function that logs")
    End Sub

    ' log one string; automatically include the ID of the current thread
    Protected Sub log(string s)
        log(Thread.CurrentThread.ManagedThreadId, s)
    End Sub

    ' log one string; insert current time and threadID
    ' we use the Control.Invoke pattern so the bulk of it always executes on the main thread and can access GUI Controls
    Private Sub log(ByVal threadID As Integer, ByVal s As String)
        If Me.InvokeRequired Then
            Me.Invoke(logHandler, New Object() {threadID, s})
        Else
            If s.Length > 0 Then
                threadID = threadID And 255
                Dim threadString As String = " [" & threadID.ToString("X2") & "] "
                s = DateTime.Now.ToString("HH:mm:ss.fff") & threadString & s
            End If
            Console.WriteLine(s)
            If logFile IsNot Nothing Then logFile.WriteLine(s)
            If listBox IsNot Nothing Then listbox.Items.Add(s) 
        End If
    End Sub

    ' prototype of the native function that stores our logging delegate
    Declare Auto Sub NativeSetLogHandler Lib "NativeC.dll" (ByVal logHandler As LogHandler)

    ' prototype of the native function that stores our logging delegate
    Declare Auto Sub NativeLoggingDemo Lib "NativeC.dll" (ByVal i As Integer)
End Class

Of course more elaborate schemes could be devised (e.g. using different colors on the Console) but the essence is to get a single list of all the messages from both the managed and native side.

Typical Exceptions

.NET uses exceptions to signal most problems that may occur, such as a NullReferenceException when a reference gets used before being assigned a value. When using P/Invoke, some additional problems may occur; they too generate exceptions, and the most popular ones are:

  • DllNotFoundException: while refering to a native function in a DLL file, the file can't be found; I expect this also to show up when the DLL was found but requires another DLL that can't be found.
  • EntryPointNotFoundException: while refering to a native function in a DLL file, the file was found but didn't contain the named function; possible causes: name mangling due to string type (append 'A' or 'W'), or C++ name mangling (C++ normally appends the parameter list signature). You could use a utility such as DUMPBIN to watch the list of exported functions.
  • AccessViolationException: a native code memory access has failed, probably because a pointer value was wrong. Lots of mistakes can cause this, e.g. parameter misalignment (e.g. confusing managed long and native long).

When the managed side needs to get some insight in what goes wrong on the native side, then Marshal.GetExceptionPointers might come to the rescue; for more information this could be a good starting place.

Various comments

  • I have shown function prototypes as part of the class in each of the examples; in practice I do create separate classes that contain them, so I have classes named LP_User32, LP_Kernel32, etc., as I don't want to distribute the P/Invoke stuff all across my code. The demo program illustrates this.
  • I have no experience with the .NET Compact Framework, where P/Invoke is available but slightly different; here is an article that deals with it.

Demo program

Still in the works: I have prepared a demo application, which is (not yet) available here. It consists of

  • a C project (some 50 lines of C code) yielding NativeC.dll;
  • a C# project (some 300 lines of C# code) yielding PInvoke1cs.exe;
  • a VB.NET project (some 300 lines of VB.NET code) yielding PInvoke1vb.exe.

Both executables have the same functionality; they demonstrate calls to some Win32 functions and calls to the functions provided by NativeC.dll; the three projects have been kept separate and can be built using the Express versions of Visual Studio (C++, C# and VB.NET respectively).

Conclusion

Managed code can call unmanaged code; this may be easy or not so easy depending on the data structures and classes involved. And it may be extremely performant when done properly; the best results can be obtained when you can freely choose the interface specifications, as opposed to being told exactly what the API and data types would be.



Perceler

Copyright © 2012, Luc Pattyn

Last Modified 02-Sep-2013