Reputation: 2015
Callbacks to C# from unmanaged C++ are tricky. I learned most of the required cruft from this MSDN article and this stackoverflow tip, and the result works fine in the debugger. But outside of the debugger it fails with "Object reference not set to an instance of an object".
Here's the (simplified) C# code:
class CSharpCode
{
delegate void CallbackDelegate();
void DoCSharp()
{
CallbackDelegate callbackDelegate = TheCallback;
IntPtr callbackDelegatePointer = Marshal.GetFunctionPointerForDelegate(callbackDelegate);
GCHandle gchCallbackDelegate = GCHandle.Alloc(callbackDelegatePointer);
GC.Collect(); // create max space for unmanaged allocations
CppCliCode.DoCppCli(callbackDelegatePointer);
}
public static void TheCallback()
{
MessageBox.Show("It worked");
}
}
And here's the C++ code:
#pragma managed
public ref class CppCliCode
{
static void DoCppCli(IntPtr^ callbackDelegatePointer)
{
callback theCallback = static_cast<callback>(callbackDelegatePointer->ToPointer());
DoCpp(theCallback);
}
}
#pragma unmanaged
typedef void (__stdcall *callback)();
void DoCpp(callback theCallback)
{
theCallback();
}
The error occurs somewhere between invoking theCallback()
and arriving at TheCallback()
. The error suggests that some invisible managed object has become null
.
If I remove the GC.Collect() the problem goes away. But that just means it will reappear someday as an intermittent mystery when a GC happens to occur at the wrong moment.
The GCHandle protects the delegate from being collected but allows it to be relocated. The MSDN article says "If a delegate is re-located by a garbage collection, it will not affect the underlaying managed callback, so Alloc is used to add a reference to the delegate, allowing relocation of the delegate, but preventing disposal. Using GCHandle instead of pin_ptr reduces fragmentation potential of the managed heap."
What's wrong?
Upvotes: 7
Views: 4959
Reputation: 4863
You must allocate the delegate itself, not its IntPtr
. Also you must free the GCHandle
when you are done with CSharpCode
instance.
class CSharpCode : IDisposible
{
delegate void CallbackDelegate();
GCHandle gchCallbackDelegate;
void DoCSharp()
{
CallbackDelegate callbackDelegate = TheCallback;
IntPtr callbackDelegatePointer = Marshal.GetFunctionPointerForDelegate(callbackDelegate);
gchCallbackDelegate = GCHandle.Alloc(callbackDelegate); // !!!!
GC.Collect(); // create max space for unmanaged allocations
CppCliCode.DoCppCli(callbackDelegatePointer);
}
public void Dispose()
{
CleanUp();
}
~CSharpCode()
{
CleanUp();
}
CleanUp()
{
if(gchCallbackDelegate.IsAllocated)
gchCallbackDelegate.Free();
}
}
By the way I hope you have more powerful naming system of yours. Names like DoCSharp
, TheCallBack
, theCallBack
etc. gave me a hard time to understand the question.
Upvotes: 7