如何指定从 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;
};
我想将一个函数指针(或类似的)作为回调函数传递给从 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;
};