Making PInvoke Easy

Making PInvoke Easy

  • Comments 12

Jared here again. 

I very excited to announce we recently released a tool I've been working on to MSDN that will greatly help with using PInvoke in managed code.  The tool is called the "PInvoke Interop Assistant" and is included as part of a MSDN article on marshalling data for PInvoke and Reverse PInvoke scenarios. 

Here is a link to the article and tool

The motivation behind this tool is writing PInvoke is a hard and often tedious task. There are many rules you must obey and many exceptions that must be taken into account.  Anything beyond simple data structures gets very involved and subtle semantics of C can greatly change the needed signature.  Incorrect translations often result in obscure exceptions or crashes.

In short, it's not any fun.

The tool works in several different ways to make PInvoke generation an easier process.  The goal is to make generating managed code for structs, unions, enums, constants, functions, typedefs , etc ... as easy as possible. The resulting code can be generated in both VB and C#.

The GUI version of the tool operates in 3 modes.  

  1. SigImp Search: Search for a commonly used function and translate it into managed code.
  2. SigImp Translate Snippet: Directly translate C code into managed PInvoke signatures.
  3. SigExp: Convert managed binaries into C++ Reverse PInvoke scenarios

The first two are the parts I worked on and represent the PInvoke scenarios.  The third part was written by Ladi Prosek and will be covered in a different article. We chose the names SigImp and SigExp to mirror the tblimp/tlbexp tool base since they have similar functions.

Directly translating C code into PInvoke Signatures

Most adventures in PInvoke start with a developer having a small set of C code they would like to use from a managed binary.  Typically it's one or two functions with several supporting C structs.  Before, all of this would be hand translated into managed code from scratch.  With this tool all you must do is paste the code into the tool and it will generate the interop signature for you. 

For instance assume you wanted to translate the following C code into VB. 

struct S1
{
  int a;
  char[10] b;
};

float CalculateData(S1* p);

Start up the tool and switch to the "SigImp Translate Snippet" tab.  Then paste the code in and then hit the Generate button. 

PInvoke1 

You can also set click the "Auto Generate" box and watch the code update as you type. 

This translation is not limited to built-in C types.  It will also resolve most commonly used windows types such as HANDLE, DWORD all the way up to complex structs such as WIN32_FIND_DATA

Searching for a commonly used function

Often developers want to use C functions familiar to them in managed code.  This can be a tedious task as well because if the signature is not already available you are back to coding from scratch.  Even adding a constant value can be tricky if you don't know which header file to look in. 

The tool also provides a database of many commonly used functions, structs, constants, etc ... It is essentially anything that is included from windows.h.  Switch to the SigImp search tab, type the name of what you are looking for and hit generate.  For example if I want to see the value for WM_PAINT just type it in. 

Pinvoke2

In addition this part of the tool will also do dependency calculation.  For instance if choose a method which has a parameter that is a C structure it will automatically generate the structure with the function.  For instance if you choose the function FindFirstFile it will determine that the function depends on the WIN32_FIND_DATA structure.  Furthermore it will notice that WIN32_FIND_DATA depends on FILETIME and generate both in addition to the method.

<System.Runtime.InteropServices.StructLayoutAttribute( _
    System.Runtime.InteropServices.LayoutKind.Sequential, _
    CharSet:=System.Runtime.InteropServices.CharSet.[Unicode])> _
Public Structure WIN32_FIND_DATAW
    '''DWORD->unsigned int
    Public dwFileAttributes As UInteger
    '''FILETIME->_FILETIME
    Public ftCreationTime As FILETIME
    '''FILETIME->_FILETIME
    Public ftLastAccessTime As FILETIME
    '''FILETIME->_FILETIME
    Public ftLastWriteTime As FILETIME
    '''DWORD->unsigned int
    Public nFileSizeHigh As UInteger
    '''DWORD->unsigned int
    Public nFileSizeLow As UInteger
    '''DWORD->unsigned int
    Public dwReserved0 As UInteger
    '''DWORD->unsigned int
    Public dwReserved1 As UInteger
    '''WCHAR[260]
    <System.Runtime.InteropServices.MarshalAsAttribute( _
        System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=260)> _
    Public cFileName As String
    '''WCHAR[14]
    <System.Runtime.InteropServices.MarshalAsAttribute( _
        System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=14)> _
    Public cAlternateFileName As String
End Structure

<System.Runtime.InteropServices.StructLayoutAttribute( _
    System.Runtime.InteropServices.LayoutKind.Sequential)> _
Public Structure FILETIME
    '''DWORD->unsigned int
    Public dwLowDateTime As UInteger
    '''DWORD->unsigned int
    Public dwHighDateTime As UInteger
End Structure

Partial Public Class NativeMethods
    '''Return Type: HANDLE->void*
    '''lpFileName: LPCWSTR->WCHAR*
    '''lpFindFileData: LPWIN32_FIND_DATAW->_WIN32_FIND_DATAW*
    <System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint:="FindFirstFileW")> _
    Public Shared Function FindFirstFileW( _
        <System.Runtime.InteropServices.InAttribute(), _
            System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            ByVal lpFileName As String, _
        <System.Runtime.InteropServices.OutAttribute()> _
        ByRef lpFindFileData As WIN32_FIND_DATAW) As System.IntPtr
    End Function
End Class

Translating Large Code bases

The snippet translator works well for small snippets of code.  If you are trying to translate a much larger code base, say several interdependent header files the small snippet dialog won't work well.  To work with larger code bases you should use the command line version of the tool;  sigimp.exe.  It is designed to process several header files and produce a mass output. 

Wrapping Up

This tool started out as a pet project of mine some time ago.  I'm extremely excited that customers are now going to be able to take advantage of it and I greatly look forward to any feedback you have.  I will post a couple more articles in the future detailing how this tool works under the hood. 

Jared Parsons

http://blogs.msdn.com/jaredpar

Leave a Comment
  • Please add 2 and 1 and type the answer here:
  • Post
  • One of the first things I really hammered with .NET 1.0 back in late 2000 was P/Invoke. I was used to

  • Suggest listing a .NET framework to Win32 call equivalent and then greatly reducing the number of Win32 calls that do not have a .NET equivalent or a MS generated PInvoke wrapper.  

    I've been through it in C# with calling the SQL Server backup APIs.  The best suggestion I've seen in to write simple C++ wrappers around the Win32 calls that the wrappers just translate to/from the C++ paramter types of those calls.  Much easier than using an IntPtr and calculating offsets, etc in C#.

    This could be a lot nicer when we get most of Win32 into .NET and then retire the win32 apis by using a .NET framework THUNK layer to emulate win32 for legacy applications that need Win32.

  • Στον κόσμο του .NET framework υπάρχουν πολλές φορές περιπτώσεις όπου είναι απαραίτητο ο managed κώδικας

  • Well finally, thanks!

    @Greg: You sound like PDC '03: "Longhorn is gonna be like, all managed code man, and like, everything new is in managed code and it's first class and everything."

    Fast forward to Vista RTM and I'm not sure if there were any managed APIs (although TONS of unmanaged stuff around).

    I wouldn't keep my hopes up of the Windows team getting this fixed. Seems like too many hardcore leet people who think C/C++ rocks.

    But hey, at least SQL already did (backup in SQL 2005? SMO makes it about 5 lines of code).

  • The .NET framework is largely complete enough to replace win32 above the device driver level.  Creating Thunk modules to replace different win32 API sets could be done over the next year or two.  We'd be largely win32 free at that point.

    We have lots of problems finding decent C++ talent and the schools are not graduating any sizable numbers of new C++ developers.  This  is forcing us to replace our C++ systems.

  • Greg, I definately agree with you. It's just not going to happen unless the Windows team replaces a lot of people. They started somewhat down that path with Vista, then scrapped that idea and started over. Take a look at this:

    http://www.grimes.demon.co.uk/dotnet/vistaAndDotnet.htm

    So, yes, I agree (not on replacing Win32, but migrating towards .NET parity, at least). But no, Windows is not heading in that direction at all, and I doubt the Windows team ever will. Other teams seem much more on the ball here (SQL, everything under ScottGu, etc.)

    But, with Vista's poor reception, perhaps they'll have a change of heart :).

  • 再び Jared です。 私が作業に携わってきたツールが、最近 MSDN でリリースされました。マネージ コードでの PInvoke の使用に役立つ PInvoke Interop Assistant

  • I'm happy to announce that the PInvoke Interop Assistant tool is now available on CodePlex. This includes

  • Great tool.

    I'd only suggest the (optional?) omitting the System.Runtime.InteropServices namespace from the generated code...

  • @Anton

    Thanks for the suggestion.  I've heard this multiple times and I'm looking for a way to do this.  Unfortunately though, I rely on the CodeDom to spit code out and by default it spits out fully qualified names.  

  • Amazing tool. Really saved me a lot of time at work converting some calls to a C++ DLL that return structs.

  • Save my life. Definitevely a must have. Thanks for sharing this.

Page 1 of 1 (12 items)