Tuesday 26 January 2010

C# Interop

I write image processing algorithms for a (so-called) living, which explains why my posts are so badly written and incomprehensible.  I write quite a few libraries in C/C++ for various industrial tasks and supply them as a trusty old windows dll.   Providing an interface for calling my libraries from a C# application front-end is something I have to do quite a bit of.  C++ delivers the performance needed for image processing and C# gives the quick and easy GUI. 



In order to call a regular 'C' dll from C#, you need to use something in .NET called P/Invoke.  This mechanism defines a function that is callable from C# which maps to a dll function call.  In the definition of the dll function, you can specify things like character sets for string passing, calling conversions etc.  As an example, if you wanted to import the windows kernel32 function Beep into C# using P/Invoke it looks something like:



[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);



In the beep example, the integer values passed from managed to unmanaged dll are so-called blittable types and will be passed directly.  Passing arrays is not quite as simple, since they have to be converted (marshalled) by the framework before they are passed to the unmanged dll.  Normally, you don't use unsafe code in your C# GUI, so you don't have pointers to data lying around handy.  Of course, most C libraries for image processing expect a pointer to some image data to be passed in somewhere, not a managed array object.  So somebody has to do some work to turn a managed array into a pointer, without totally screwing up the safe part of C# and all the other stuff going on like the garbage collector.  This is job of P/Invoke.


For example, if we have a C# array declared as:

 float[] CalTgtX = { 58,198,340, 58,198,340, 58,198,340};


which we want to pass into a C++ function that looks like this:

extern "C" __declspec(dllexport) void __stdcall CalibrateProjection(float *pTargetX)

Then we need to carefully define how C# should carry out this conversion.  Heres how to define the function in C# so that we can pass (marshal) that float array object from C# to the C dll function:


[DllImport("MyLib.dll", EntryPoint = "CalibrateProjection", CallingConvention = CallingConvention.StdCall)]
public static extern RETCODE CalibrateProjection([MarshalAs(UnmanagedType.LPArray, SizeConst = 9)] float[] pTargetX)

The first line tells the C# compiler to import a function.  DllImport tells c# we are importing a function from a Dll.  EntryPoint tells c# what the function stub is named. CallingConvention should match that used by the Dll - here it was __stdcall.  
The second line defines the function as it will appear to C#.  The key to converting the float array object to a pointer is in the MarshalAs attribute.  This will involve a copy to an unsafe array on the heap so can be slow for large arrays... very slow.


All the different flavours of managed types and structs can be marshalled this way.  More information on PInvoke can be found at http://msdn.microsoft.com/en-us/library/aa288468%28VS.71%29.aspx

 

Vision Experts

No comments:

Post a Comment