Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to adapt a C++ std::future return value to a C# System.Threading.Tasks.Task?

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)

like image 961
shmuelie Avatar asked May 15 '19 15:05

shmuelie


1 Answers

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();
    }
}
like image 91
Soonts Avatar answered Nov 15 '22 05:11

Soonts