c++/cli pass (managed) delegate to unmanaged code
Here's another way to do it based on my experiences implementing a .NET wrapper in C++/CLI around the CartoType C++ map rendering library. This is tested and working code.
The C++ API has an asynchronous Find function which takes a callback:
TResult CartoType::CFramework::FindAsync(FindAsyncCallBack aCallBack,const TFindParam& aFindParam,bool aOverride = false);
The callback is a function of this type:
using FindAsyncCallBack = std::function<void(std::unique_ptr<CMapObjectArray> aMapObjectArray)>;
The task is to provide a .NET wrapper for this function by adding C++/CLI code to the existing wrapper system. First I define a suitable delegate type for my .NET function (an equivalent to FindAsyncCallback in the C++ API):
public delegate void FindAsyncDelegate(MapObjectList^ aMapObjectList);
The signature of the .NET function is thus:
Result FindAsync(FindAsyncDelegate^ aDelegate,FindParam^ aFindParam,bool aOverride);
The main implementation problem to be solved is how to call the native C++ function and provide a native callback function which can then call the delegate passed in by the caller of the .NET function. An associated task is to keep the delegate and the native callback function object alive until the asynchronous function's thread has done its job. Here's how it's done.
I define a C++/CLI delegate type that's the same as the C++ callback function type, and a class to hold the delegate passed in by the caller to the .NET function (of type FindAsyncDelegate), and a delegate of the type to be passed to C++ (of type NativeAsyncHandler):
delegate void NativeAsyncHandler(std::unique_ptr<CMapObjectArray> aMapObjectArray);
ref class FindAsyncHelper
{
public:
FindAsyncHelper(Framework^ aFramework,FindAsyncDelegate^ aDelegate):
m_framework(aFramework),
m_delegate(aDelegate)
{
}
void Handler(std::unique_ptr<CMapObjectArray> aMapObjectArray)
{
MapObjectList^ o = gcnew MapObjectList;
SetMapObjectList(m_framework,o,*aMapObjectArray);
m_delegate(o);
// Remove this object from the list held by the framework so that it can be deleted.
m_framework->m_find_async_helper_list->Remove(this);
}
Framework^ m_framework;
FindAsyncDelegate^ m_delegate;
NativeAsyncHandler^ m_native_handler;
};
The idea is that we create a FindAsyncHelper object with the two delegates in it, then call the native FindAsync function with the native delegate, arranged to call Handler(), which then calls the original caller's delegate.
And here is how it's implemented:
typedef void(*FIND_ASYNC_CALLBACK)(std::unique_ptr<CMapObjectArray> aMapObjectArray);
Result Framework::FindAsync(FindAsyncDelegate^ aDelegate,FindParam^ aFindParam,bool aOverride)
{
if (aDelegate == nullptr || aFindParam == nullptr)
return Result::ErrorInvalidArgument;
TFindParam param;
SetFindParam(param,aFindParam);
FindAsyncHelper^ h = gcnew FindAsyncHelper(this,aDelegate);
h->m_native_handler = gcnew NativeAsyncHandler(h,&FindAsyncHelper::Handler);
IntPtr p = Marshal::GetFunctionPointerForDelegate(h->m_native_handler);
FIND_ASYNC_CALLBACK f = static_cast<FIND_ASYNC_CALLBACK>(p.ToPointer());
TResult error = m_framework->FindAsync(f,param,aOverride);
// Keep h alive by adding it to a list.
m_find_async_helper_list->Add(h);
return (Result)(int)error;
}
Some notes:
The statements
FindAsyncHelper^ h = gcnew FindAsyncHelper(this,aDelegate);
h->m_native_handler = gcnew NativeAsyncHandler(h,&FindAsyncHelper::Handler);
create a FindAsyncHandler object and store a native handler object in it; keeping it here means we only have one object to keep alive, the FindAsyncHandler. The next statements:
IntPtr p = Marshal::GetFunctionPointerForDelegate(h->m_native_handler);
FIND_ASYNC_CALLBACK f = static_cast<FIND_ASYNC_CALLBACK>(p.ToPointer());
get a function pointer that can be passed to native code, and cast it to the right function pointer type. We can't cast it directly to the std::function type used in FindAsyncCallback, so the cumbersome extra typedef is necessary.
At last the native FindAsync function can be called:
TResult error = m_framework->FindAsync(f,param,aOverride);
And then, to make sure the various callback functions stay alive, the FindAsyncHandler is added to a list owned by the main framework object:
m_find_async_helper_list->Add(h);
It is taken off the list when the task is completed and FindAsyncHelper::Handler is called.
Yes, you want Marshal::GetFunctionPointerForDelegate(). Your code snippet is missing the managed function you'd want to call, I just made one up. You will also have to declare the managed delegate type and create an instance of it before you can get a function pointer. This worked well:
#include "stdafx.h"
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma managed(push, off)
typedef void (* UnmanagedSummer)(int arg);
void UnmanagedMethod(int a, int b, UnmanagedSummer sum)
{
int result = a + b;
sum(result);
}
#pragma managed(pop)
ref class Test {
delegate void ManagedSummer(int arg);
public:
static void Run() {
Test^ t = gcnew Test();
ManagedSummer^ managed = gcnew ManagedSummer(t, &Sum);
IntPtr stubPointer = Marshal::GetFunctionPointerForDelegate(managed);
UnmanagedSummer functionPointer = static_cast<UnmanagedSummer>(stubPointer.ToPointer());
UnmanagedMethod(1, 2, functionPointer);
GC::KeepAlive(managed); // Important: ensure stub can't be collected while native code is running
System::Diagnostics::Debug::Assert(t->summed == 3);
}
void Sum(int arg) {
summed += arg;
}
int summed;
};
int main(array<System::String ^> ^args)
{
Test::Run();
return 0;
}