Как адаптировать возвращаемое значение C ++ std :: future к C # System.Threading.Tasks.Task? - PullRequest
6 голосов
/ 15 мая 2019

Я упаковываю библиотеку C ++ для использования из .NET. В C ++ API есть функции, которые возвращают std::future. Я хочу, чтобы прокси-классы .NET возвращали System.Threading.Tasks.Task.

Я думал о добавлении метода замены на стороне C ++, который будет принимать указатель на функцию в дополнение к параметрам метода. Затем я мог бы запустить новый поток (используя, например, std::async) и подождать, пока std::future::get. Как только std::future::get вернулось, я мог вызвать переданный указатель на функцию. На стороне C # я бы передавал указатель на функцию, которая выполняла бы возвращенную задачу. Что-то вроде:

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);

(Да, у кода есть проблемы с управлением памятью. Этого должно быть достаточно, чтобы донести идею до конца)

1 Ответ

2 голосов
/ 15 мая 2019

Выделение асинхронного завершения или сбоя является относительно простой частью.Я бы лично использовал интерфейс COM, реализованный в C #, помеченный [InterfaceType( ComInterfaceType.InterfaceIsIUnknown )], с двумя методами, такими как setCompleted() и setFailed( HRESULT status ), и передавал его при запуске задачи, но это дело вкуса.

Проблема в том, что многопоточной реализации C ++ не хватает.

В этом отношении лучше Boost, у него future класс имеет then метод, который можно использовать для распространения состояния без блокировкиthread.

В настоящее время стандартные фьючерсы C ++ не предлагают ничего похожего.Я думаю, что вам не повезло.

Если вы можете изменить эту библиотеку C ++, измените API на что-то более понятное, то есть можете уведомлять о завершении неблокирующим образом.


Без COM управление временем жизни сложно.После того, как вы запустили асинхронную задачу, но до ее завершения, кто-то на стороне C # должен сохранить указатель на эту функцию.Вот возможное решение, обратите внимание, как он использует GCHandle, чтобы сделать ответный вызов пережившим задачу:

[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();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...