Max
Max

Reputation: 20004

Accessing managed object in a c# callback passed to a native dll

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

Answers (2)

David Heffernan
David Heffernan

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

Medinoc
Medinoc

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

Related Questions