如何指定从 C# 到 C++/CLI 的回调

How to specify a callback from C# to C++/CLI

我想将一个函数指针(或类似的)作为回调函数传递给从 C++/CLI 调用的 C# class 的构造函数。 C# class 是一个子模块; C++端是主程序。我收到 Visual Studio 2017 报告的错误,我无法计算出要使用的正确语法。 (我是一名 C++ 程序员,但对 CLI 和 C# 的经验几乎为零。)我找到了很多关于如何以相反方式设置回调的示例,但是从 C# 到 C++/CLI,我发现的信息很少。

有人可以告诉我正确的语法是什么,或者如果这个语法存在根本性缺陷,可以展示一种不同的方法来实现相同的目标吗?

C# 代码(看起来不错):

namespace MyNamespace
{
    public class MyCSharpClass
    {
        private Action<string> m_logger;

        public MyCSharpClass(Action<string> logger) => m_logger = logger;

        public void logSomething()
        {
            m_logger("Hello world!");
        }
    }
}

C++/CLI 代码(错误在带有 System::Action 的第二行 gcnew 中):

#pragma once
#pragma managed

#include <vcclr.h>

class ILBridge_MyCSharpClass
{

public:

    ILBridge_MyCSharpClass(ManagedDll_MyCSharpClass* pManagedDll_MyCSharpClass)
        : m_pManagedDll_MyCSharpClass(pManagedDll_MyCSharpClass)
    {
        m_pImpl = gcnew MyCSharpClass::MyCSharpClass(
            gcnew System::Action<System::String^>^(this, &ILBridge_MyCSharpClass::log)
        );
    }

    void log(System::String^ message) const
    {
        // ...
    }
}

报告的错误:

error C3698: 'System::Action<System::String ^> ^': cannot use this type as argument of 'gcnew'
note: did you mean 'System::Action<System::String ^>' (without the top-level '^')?
error C3364: 'System::Action<System::String ^>': invalid argument for delegate constructor; delegate target needs to be a pointer to a member function

如果我按照建议删除“^”,C3698 错误就会消失,但 C3364 错误仍然存​​在。

我遵循此处建议的设计模式,但不使用代码生成:http://blogs.microsoft.co.il/sasha/2008/02/16/net-to-c-bridge/

您不能像函数指针一样使用 C# 委托。但是你可以创建调用 c# 方法的不安全的 c++ cli 方法。

编辑:基本解决方案

C++ CLI 中的 Action 可以从函数(不是成员函数但自由或 static)或托管 ref class 的成员函数创建。

为了从 Action 调用本机成员函数,本机成员调用需要包装在托管成员函数中。

class NativeClassType;

ref class ManagedWrapper
{
    typedef void(NativeClassType::*MemberFunc)(System::String^);
    NativeClassType* nativeObject;
    MemberFunc memberFunction;

public:
    ManagedWrapper(NativeClassType* obj, MemberFunc wrappedFunction)
        : nativeObject(obj), memberFunction(wrappedFunction)
    {
        // Action that can be used in other managed classes to effectively invoke the member function from NativeClassType
        auto actionObject = gcnew System::Action<System::String^>(this, &ManagedWrapper::CallWrapped);
    }

    void CallWrapped(System::String^ msg)
    {
        // forward the call
        (nativeObject->*memberFunction)(msg);
    }
};

原始答案和完整示例

我试了一下,据我所知,您有时需要使用本机成员函数指针处理才能回调本机成员函数...

下面的示例代码提供了一个用于静态函数回调的托管 (ref) class 和另一个用于成员函数回调的代码。本机 class NativeManaged 使用两个桥 classes 来演示不同的回调。

ref class ILBridge_Logger
{
private:
    System::Action<System::String^>^ loggerCallback;

public:

    ILBridge_Logger(void (*logFn)(System::String^))
    {
        loggerCallback = gcnew System::Action<System::String^>(logFn);
    }
    ILBridge_Logger(System::Action<System::String^>^ logFn)
    {
        loggerCallback = logFn;
    }



    void Test(System::String^ msgIn)
    {
        log(msgIn);
    }

    void log(System::String^ message)
    {
        loggerCallback(message);
    }
};


template<typename CallbackObject>
ref class ILBridge_MemberLogger : public ILBridge_Logger
{
    CallbackObject* o;
    void (CallbackObject::*logFn)(System::String^);
public:

    ILBridge_MemberLogger(CallbackObject* o, void (CallbackObject::*logFn)(System::String^))
        : ILBridge_Logger(gcnew System::Action<System::String^>(this, &ILBridge_MemberLogger::logMember)), o(o), logFn(logFn)
    {
    }

    // translate from native member function call to managed
    void logMember(System::String^ message)
    {
        (o->*logFn)(message);
    }
};


class NativeManaged
{
    gcroot<ILBridge_Logger^> Impl1;
    gcroot<ILBridge_Logger^> Impl2;
public:
    NativeManaged()
    {
        Impl1 = gcnew ILBridge_Logger(gcnew System::Action<System::String^>(log1));
        Impl2 = gcnew ILBridge_MemberLogger<NativeManaged>(this, &NativeManaged::log2);
    }

    void Test(System::String^ msgIn)
    {
        Impl1->Test(msgIn);
        Impl2->Test(msgIn);
    }

    // static logger callback
    static void log1(System::String^ message)
    {
        System::Console::WriteLine(L"Static Log: {0}", message);
    }

    // member logger callback
    void log2(System::String^ message)
    {
        System::Console::WriteLine(L"Member Log: {0}", message);
    }
};


int main(array<System::String ^> ^args)
{
    NativeManaged c;
    c.Test(L"Hello World");
    return 0;
}

注意:可能有更优雅的方法来处理我不知道的具有 C++11/14/17 功能的成员函数指针。

作为参考,这是我最终得到的解决方案。 C# 代码与 OP 中的代码相同。正如 grek40 在他的回答中所建议的那样,需要一个托管的 (ref) class。为了通过我的托管 DLL 使用托管 class,仍然需要原始的 IL Bridge class。

#pragma once
#pragma managed

#include <vcclr.h>

class ManagedDll_MyCSharpClass;
class ILBridge_MyCSharpClass;

ref class Managed_MyCSharpClass
{
    ILBridge_MyCSharpClass* m_pILBridge_MyCSharpClass;
    void (ILBridge_MyCSharpClass::*m_logger)(System::String^);
    MyCSharpClass::MyCSharpClass^ m_pImpl;

public:
    Managed_MyCSharpClass(ILBridge_MyCSharpClass* pILBridge_MyCSharpClass, void (ILBridge_MyCSharpClass::*logger)(System::String^))
        : m_pILBridge_MyCSharpClass(pILBridge_MyCSharpClass)
        , m_logger(logger)
    {
        m_pImpl = gcnew MyNamespace::MyCSharpClass(
            gcnew System::Action<System::String^>(this, &Managed_MyCSharpClass::log)
        );
    }

    void log(System::String^ message)
    {
        (m_pILBridge_MyCSharpClass->*m_logger)(message);
    }
};

class ILBridge_MyCSharpClass
{
public:

    ILBridge_MyCSharpClass(ManagedDll_MyCSharpClass* pManagedDll_MyCSharpClass)
        : m_pManagedDll_MyCSharpClass(pManagedDll_MyCSharpClass)
    {
        m_pManaged_MyCSharpClass = gcnew Managed_MyCSharpClass(this, &ILBridge_MyCSharpClass::log);
    }

    void log(System::String^ message)
    {
        // ...
    }

private:
    ManagedDll_MyCSharpClass* m_pManagedDll_MyCSharpClass;
    gcroot<Managed_MyCSharpClass^> m_pManaged_MyCSharpClass;
};