【问题标题】:c++/cli pass (managed) delegate to unmanaged codec++/cli 将(托管)委托传递给非托管代码
【发布时间】:2011-02-27 16:25:13
【问题描述】:

如何将函数指针从托管 C++ (C++/CLI) 传递到非托管方法?我读了几篇文章,比如this one from 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 方法,但无法编译。

【问题讨论】:

  • 能否请您发布您使用 GetFunctionPointerForDelegate 的代码?

标签: delegates c++-cli interop function-pointers


【解决方案1】:

是的,你想要 Marshal::GetFunctionPointerForDelegate()。您的代码 sn-p 缺少您要调用的托管函数,我只是做了一个。您还必须声明托管委托类型并创建它的实例,然后才能获得函数指针。这很好用:

#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】:

根据我在 C++/CLI 中围绕 CartoType C++ 地图渲染库实现 .NET 包装器的经验,这是另一种方法。这是经过测试和工作的代码。

C++ API 有一个异步 Find 函数,它接受回调:

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

回调是这种类型的函数:

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

任务是通过将 C++/CLI 代码添加到现有的包装器系统来为此函数提供 .NET 包装器。首先,我为我的 .NET 函数定义了一个合适的委托类型(相当于 C++ API 中的 FindAsyncCallback):

public delegate void FindAsyncDelegate(MapObjectList^ aMapObjectList);

.NET 函数的签名如下:

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

要解决的主要实现问题是如何调用原生C++函数,并提供一个原生回调函数,然后可以调用.NET函数的调用者传入的委托。一个相关的任务是保持委托和本机回调函数对象处于活动状态,直到异步函数的线程完成其工作。这是它的完成方式。

我定义了一个与 C++ 回调函数类型相同的 C++/CLI 委托类型,以及一个用于保存调用者传递给 .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());

获取一个可以传递给本机代码的函数指针,并将其转换为正确的函数指针类型。我们不能直接将其强制转换为 FindAsyncCallback 中使用的 std::function 类型,所以需要额外的繁琐 typedef。

终于可以调用原生的 FindAsync 函数了:

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

然后,为了确保各种回调函数保持活动状态,将 FindAsyncHandler 添加到主框架对象拥有的列表中:

m_find_async_helper_list->Add(h);

当任务完成并调用 FindAsyncHelper::Handler 时将其从列表中删除。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-12
    • 1970-01-01
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多