I am wrapping a C++ library to be used from .NET. There are functions in the C++ API that return std::future
. I want to have the .NET proxy classes return System.Threading.Tasks.Task
.
I thought of adding a replacement method on the C++ side that would take in a function pointer in addition to the methods parameters. I could then start up a new thread (using std::async
for example) and have that wait on std::future::get
. Once std::future::get
returned I could call the passed in function pointer. On the C# side I would pass the a pointer to a function that would complete the returned Task. Something like:
Task CallCpp(int a, int b) {
TaskCompletionSource<int> tcs;
Action callback = () => tcs.SetResult(0);
IntPtr callbackPtr = Marshal.GetFunctionPointerForDelegate(callback);
CppMethod(callbackPtr, a, b);
return tcs.Task;
}
[DllExport]
external void CppMethod(IntPtr callback, int a, int b);
void CppMethod(void (*callback)(), int a, int b) {
std::future f = realmethod(a, b);
std::async([&]{ f.get; callback(); });
}
std::future realmethod(int a, int b);
(Yes, the code has memory management issues. It should be enough to get the idea across though)
Marshalling asynchronous completion or failure is relatively easy part. I would personally use a COM interface implemented in C#, marked [InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
, with 2 methods like setCompleted()
and setFailed( HRESULT status )
, and pass it when I start the task, but that’s a matter of taste.
The problem is that C++ multithreading implementation is lacking.
Boost is better in that regard, it’s future
class has then
method that can be used to propagate the status without blocking the thread.
Currently, standard C++ futures don’t offer anything comparable. I think you're out of luck.
If you can modify that C++ library, change the API to something that makes more sense, i.e. can notify about the completion in non-blocking manner.
Without COM, lifetime management is complicated. After you’ve launched the async task but before it completes, someone on C# side needs to retain that function pointer. Here’s a possible solution, note how it uses GCHandle to make callback outlive the task:
[UnmanagedFunctionPointer( CallingConvention.Cdecl )]
delegate void pfnTaskCallback( int hResult );
[DllImport( "my.dll" )]
static extern void launch( [MarshalAs( UnmanagedType.FunctionPtr )] pfnTaskCallback completed );
public static async Task runAsync()
{
var tcs = new TaskCompletionSource<bool>();
pfnTaskCallback pfn = delegate ( int hr )
{
if( hr >= 0 ) // SUCEEDED
tcs.SetResult( true );
else
tcs.SetException( Marshal.GetExceptionForHR( hr ) );
};
var gch = GCHandle.Alloc( pfn, GCHandleType.Normal );
try
{
launch( pfn );
await tcs.Task;
}
finally
{
gch.Free();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With