Подтвердить что ты не робот

С++/cli pass (управляемый) делегировать неуправляемый код

Как передать указатель функции из управляемого С++ (С++/CLI) в неуправляемый метод? Я прочитал несколько статей, таких как этот из MSDN, но он описывает две разные сборки, в то время как я хочу только один.

Вот мой код:

1) Заголовок (MyInterop.ManagedCppLib.h):

#pragma once

using namespace System;

namespace MyInterop { namespace ManagedCppLib {

    public ref class MyManagedClass
    {
    public:
        void DoSomething();
    };
}}

2) Код CPP (MyInterop.ManagedCppLib.cpp)

#include "stdafx.h"
#include "MyInterop.ManagedCppLib.h"

#pragma unmanaged
void UnmanagedMethod(int a, int b, void (*sum)(const int))
{
    int result = a + b;
    sum(result);
}

#pragma managed
void MyInterop::ManagedCppLib::MyManagedClass::DoSomething()
{
    System::Console::WriteLine("hello from managed C++");
    UnmanagedMethod(3, 7, /* ANY IDEA??? */);
}

Я попытался создать мой управляемый делегат, а затем попытался использовать метод Marshal::GetFunctionPointerForDelegate, но я не смог его скомпилировать.

4b9b3361

Ответ 1

Да, вы хотите маршал:: GetFunctionPointerForDelegate(). В вашем фрагменте кода отсутствует управляемая функция, которую вы хотите вызвать, я только что сделал ее. Вам также нужно будет объявить управляемый тип делегата и создать его экземпляр, прежде чем вы сможете получить указатель на функцию. Это сработало хорошо:

#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;
}

Ответ 2

Вот еще один способ сделать это, основываясь на моем опыте реализации оболочки .NET в C++/CLI вокруг библиотеки рендеринга карт CartoType C++. Это проверенный и рабочий код.

API C++ имеет асинхронную функцию поиска, которая принимает обратный вызов:

TResult CartoType::CFramework::FindAsync(FindAsyncCallBack aCallBack,const TFindParam& aFindParam,bool aOverride = false);

Обратный вызов - это функция этого типа:

using FindAsyncCallBack = std::function<void(std::unique_ptr<CMapObjectArray> aMapObjectArray)>;

Задача - предоставить оболочку .NET для этой функции, добавив код C++/CLI в существующую систему оболочек. Сначала я определяю подходящий тип делегата для моей функции .NET (эквивалент FindAsyncCallback в API C++):

public delegate void FindAsyncDelegate(MapObjectList^ aMapObjectList);

Подпись функции .NET выглядит следующим образом:

Result FindAsync(FindAsyncDelegate^ aDelegate,FindParam^ aFindParam,bool aOverride);

Основная проблема реализации, которая должна быть решена, заключается в том, как вызвать собственную функцию C++ и предоставить собственную функцию обратного вызова, которая затем может вызвать делегат, переданный вызывающей стороной функции .NET. Связанная задача состоит в том, чтобы поддерживать делегат и собственный объект функции обратного вызова живыми, пока поток асинхронной функции не выполнит свою работу. Вот как это делается.

Я определяю тип делегата C++/CLI, который совпадает с типом функции обратного вызова C++, и класс для хранения делегата, передаваемого вызывающей стороной в функцию .NET (типа FindAsyncDelegate), и делегат тип для передачи в C++ (типа 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;
    };

Идея состоит в том, что мы создаем объект FindAsyncHelper с двумя делегатами в нем, а затем вызываем собственную функцию FindAsync с собственным делегатом, организованным для вызова Handler(), который затем вызывает оригинальный делегат вызывающего абонента.

И вот как это реализовано:

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

Некоторые заметки:

Заявления

FindAsyncHelper^ h = gcnew FindAsyncHelper(this,aDelegate);
h->m_native_handler = gcnew NativeAsyncHandler(h,&FindAsyncHelper::Handler);

создать объект FindAsyncHandler и сохранить в нем собственный объект-обработчик; сохранение его здесь означает, что у нас есть только один объект для сохранения, FindAsyncHandler. Следующие утверждения:

IntPtr p = Marshal::GetFunctionPointerForDelegate(h->m_native_handler);
FIND_ASYNC_CALLBACK f = static_cast<FIND_ASYNC_CALLBACK>(p.ToPointer());

получить указатель на функцию, который можно передать в собственный код, и привести его к нужному типу указателя на функцию. Мы не можем привести его напрямую к типу std::function, используемому в FindAsyncCallback, поэтому необходим громоздкий дополнительный typedef.

Наконец, можно вызвать встроенную функцию FindAsync:

TResult error = m_framework->FindAsync(f,param,aOverride);

А затем, чтобы убедиться, что различные функции обратного вызова остаются в живых, FindAsyncHandler добавляется в список, принадлежащий основному объекту фреймворка:

m_find_async_helper_list->Add(h);

Он удаляется из списка, когда задача выполнена и вызывается FindAsyncHelper :: Handler.