Reputation: 20004
I'm trying to pass a C# callback function to a native dll. It works fine, but I couldn't find a way to access the parent object of the callback method. Here's a code which demonstrates what I want to do:
class MyForm: Form {
public delegate void CallbackDelegate(IntPtr thisPtr);
[DllImport("mylib.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void Test(CallbackDelegate callback);
int Field;
static void Callback(IntPtr thisPtr)
{
// I need to reference class field Field here.
MyForm thisObject = ?MagicMethod?(thisPtr);
thisObject.Field = 10;
}
void CallExternalMethod()
{
Test(Callback);
}
}
I tried getting the pointer of this
but I got the following exception: "Object contains non-primitive or non-blittable data.". I should probably mention that the parent object is a WindowsForms form.
UPDATE
The dll is written in Delphi and the signature of the Test function is the following:
type
TCallback = procedure of object; stdcall;
procedure Test(Callback: TCallback); stdcall;
I received the above error message when I tried to get the pointer to the class with the following code:
var ptr = GCHandle.Alloc(this, GCHandleType.Pinned).AddrOfPinnedObject();
Upvotes: 2
Views: 868
Reputation: 612993
You are over thinking this. The C# marshalling will create a thunk around your delegate. You must not attempt to do so.
On the Delphi side write it like this:
type
TCallback = procedure; stdcall;
Simply remove the of object
.
On the C# side your delegate is:
public delegate void CallbackDelegate();
The method that you use for your delegate looks like this:
void Callback()
{
// an instance method, so *this* is available
}
And then you call into the native code like this:
void CallExternalMethod()
{
Test(Callback);
}
So, let's put it all together. On the Delphi side you can write:
library MyLib;
type
TCallback = procedure; stdcall;
procedure Test(Callback: TCallback); stdcall;
begin
Callback;
end;
exports
Test;
begin
end.
And on the C# side:
class MyForm: Form {
public delegate void CallbackDelegate();
[DllImport("mylib.dll")]
public static extern void Test(CallbackDelegate callback);
int Field;
static void Callback()
{
Field = 10;
}
void CallExternalMethod()
{
Field = 0;
Test(Callback);
Debug.Assert(Field == 10);
}
}
Upvotes: 4
Reputation: 6608
You're supposed to use the System.Runtime.InteropServices.GCHandle
class, its MSDN documentation has examples. It involves using its static methods to convert from C# reference to and from an unmanaged value.
I have no quick way to check, but something like this should work:
static void Callback(IntPtr thisPtr)
{
MyForm thisObject = (MyForm)GCHandle.FromIntPtr(thisPtr).Target;
thisObject.Field = 10;
}
The IntPtr
should be obtained like this:
IntPtr thisPtr = GCHandle.ToIntPtr(GCHandle.Alloc(thisObject)));
PS: Note that at some point you have to free the handle, either in the callee or in the caller.
Upvotes: 0