Reverse P/Invoke - Part 2

Posted by Hugh Ang at 10/14/2008 12:29:00 PM

In my last post, I described a loosely-coupled pattern for native code to call into managed code. That approach requires control of source code of the native library, although only minor change would be made. There are scenarios where native code is not available and we just can not make any changes to it. For example, I have a Canon Rebel XTi camera connected to my PC and I need to notify my Winforms application of a new picture just taken so that the application can download and display it. The Canon SDK is in native library and has exported functions for callback function pointers to be registered. So what do we do? Scenarios like this require us to call native API from managed code, passing delegates marshaled as function pointers.

The solution is surprisingly straightforward. To demonstrate the technique, here is the native library code - note the definitions of data structure and callback function prototype:


#define NATIVELIB_API __declspec(dllexport)

 

// data structure for the callback function

struct EventData

{

    int I;

    TCHAR* Message;

};

 

// callback function prototype

typedef void (*FPCallBack)(EventData data);

 

// exported API

NATIVELIB_API void fnNativeAPI(int i, FPCallBack callBack)

{

    EventData data;

    data.I = i;

    data.Message = L"Hello from native code!";

 

    // invoke the callback function

    callBack(data);

}



And here is my corresponding interop code in C#. Notice that in the P/Invoke definition of the fnNativeAPI call, I added the MarshalAs(UnmanagedType.FunctionPtr) attribute in front of the CallBackDelegate parameter to instruct the runtime to marshal the delegate as a function pointer from managed code to native code.


[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

public struct EventData

{

    public int I;

    public string Message;

}

 

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]

public delegate void CallBackDelegate(EventData data);

 

public class Native

{

    [DllImport("NativeLib.dll")]

    public static extern void fnNativeAPI(int i, [MarshalAs(UnmanagedType.FunctionPtr)] CallBackDelegate callBack);

}



Here is the C# code of the application:


public partial class Form1 : Form

{

    private CallBackDelegate _cb;

 

    public Form1()

    {

        InitializeComponent();

        _cb = new CallBackDelegate(Foo);

    }

 

    private void Form1_Load(object sender, EventArgs e)

    {

        // call the native API, passing our .NET delegate

        int i = 200;

        Native.fnNativeAPI(i, _cb);

    }

 

    // this is our callback function

    private void Foo(EventData data)

    {

        Debug.WriteLine(data.I);

        Debug.WriteLine(data.Message);

    }

}



Careful readers may have noticed that I have kept the delegate as a class field. This is a best practice to prevent the delegate from being garbage-collected. It would be really bad if the native code holds an invalid function pointer and tries to invoke it.

For VB programmers, here is the interop code in VB.NET:


<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _

Public Structure EventData

    Public I As Integer

    Public Message As String

End Structure

 

<UnmanagedFunctionPointer(CallingConvention.Cdecl)> _

Public Delegate Sub CallBackDelegate(ByVal data As EventData)

 

Public Class Native

    <DllImport("NativeLib.dll")> _

    Public Shared Sub fnNativeAPI(ByVal i As Integer, _

                                  <MarshalAs(UnmanagedType.FunctionPtr)> ByVal callBack As CallBackDelegate)

    End Sub

End Class



And the application code:


Public Class Form1

    Dim _cb As CallBackDelegate

 

    Public Sub New()

 

        ' This call is required by the Windows Form Designer.

        InitializeComponent()

 

        ' Add any initialization after the InitializeComponent() call.

        _cb = New CallBackDelegate(AddressOf Me.Foo)

    End Sub

 

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Dim i As Integer = 200

        'call the native API, passing our .NET delegate

        Native.fnNativeAPI(i, _cb)

    End Sub

 

    ' this is our callback function

    Private Sub Foo(ByVal data As EventData)

        Debug.WriteLine(data.I)

        Debug.WriteLine(data.Message)

    End Sub

End Class

0 comments: