3

in my C++ code, a callback function is represented as a std::function() obj, not as the more common function pointer construct.

typedef std::function<void()> MYCALLBACK;

The callback function in C++ is set via:

MYCALLBACK myCBFunc; // some member variable
void SetCallbackFunction(MYCALLBACK cbFunc)
{
  myCBFunc = cbFunc;
}

in C#:

delegate void MyCallbackFunc(); // delegate matching the c++ callback sig

// method matching the call back sig
void foo()
{ }

[DllImport("mydll.dll")]
static extern SetCallbackFunction(MyCallbackFunc cbfunc);

// set the callback
MyCallbackFunc cb = foo;
SetCallbackFunction(cb); // crash here

Compiles OK, but crashes with AccessViolationException when run. I initially thought this is because MYCALLBACK obj is on the stack, and need to pass by reference and changed the signature to match but it still crashes i.e.

MYCALLBACK myCBFunc; // some member variable
void SetCallbackFunction(MYCALLBACK& cbFunc)
{
  myCBFunc = cbFunc;
}

[DllImport("mydll.dll")]
static extern SetCallbackFunction(ref MyCallbackFunc cbfunc);

// set the callback
MyCallbackFunc cb = foo;
SetCallbackFunction(ref cb); // crash here

how do i get this to work with std::function()? No problems using plain old function pointers.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
cks2k2
  • 231
  • 1
  • 3
  • 9
  • 1
    This is unmanaged C++ right? Passing a reference of a managed object to unmanaged code is a bad idea. The garbage collector sees no reference to the delegate object and will eventually collect it. – helb Sep 27 '13 at 07:05
  • yes, normal C++. but having a delegate variable should circumvent that problem right? – cks2k2 Sep 27 '13 at 07:07
  • 1
    no, you must ensure that some other managed object has a reference to it as long as the C++ library can call the callback. If the variable goes out of scope, the object may be collected. – helb Sep 27 '13 at 07:14
  • but i have no problems at all when MYCALLBACK was defined as a function pointer: typedef void (*MYCALLBACK)() – cks2k2 Sep 27 '13 at 07:19
  • Have a look at the [MarshalAs] attribute and try to set the marshalling type for the cbfunc parameter of your imported SetCallbackFunction (not sure if this will help). You can also try to put an [UnmanagedFunctionPointer] attribute on both the delegate and the imported function. – helb Sep 27 '13 at 08:50
  • @helb A delegate variable is a reference to the delegate. Although you may need a keep alive if this is a local variable: http://stackoverflow.com/questions/5465060/do-i-need-to-pin-an-anonymous-delegate – David Heffernan Sep 27 '13 at 09:05
  • 1
    @cks2k2 You've asked 7 questions here now, never voted, and never accepted an answer. I think it's time for you to learn about these aspects of the site. – David Heffernan Sep 27 '13 at 09:55

1 Answers1

2

std::function<void()> is a class template. It looks like a function because the class overloads operator(). So, std::function<void()> being a class means that it is simply not binary compatible with an unmanaged function pointer.

You'll need to use:

typedef void (*MYCALLBACK)();

Note that this function is cdecl. Either switch it to stdcall in your C++, or declare your C# delegate as being cdecl. I expect you know how to do the former. Here's an example of the latter:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void MyCallbackFunc();

Your pinvoke is also wrong in that it should not pass the callback as a ref parameter. It should be:

[DllImport("mydll.dll")]
static extern SetCallbackFunction(MyCallbackFunc cbfunc);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490