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:
Post a Comment