从C#注入回调函数到C++编写的Dll中网上可以看到很多方法,但是反过来就麻烦多了。
回调函数的本质就是传递一个函数地址给相应的函数进行处理。所以C++往C#中注入回调函数,所需要解决的问题就是,C++的函数要怎么传到C#中。
从网上找到的资料来看,虽然都是从C#往C++ DLL中传入回调,但是可以得到的一点启示就是,C#的委托可以用来跟C++进行通信。 所以我们可以利用委托来实现这个操作。
C#的编写如下
namespace CSharp
{
public class program
{
public delegate void ProgressCallback(int num);
public void registerCallBackFuncktion(ProgressCallback func)
{
func(0);
}
}
}
这里我们所声明的委托,就是用来进行通信用的。registerCallBackFuncktion
用来给外面进行回调函数的注册。当然在项目中,并不会在注册函数内直接调用回调,都是在别处调用,此处无伤大雅。
比较有趣的就是C++
中的写法了。
typedef void (*funcType) (int);
public ref class CFunc{
public:
CFunc(funcType f) : f(f) {};
void func(int num) {
f(num);
}
private:
funcType f;
};
void registerCallBackFuncktion(funcType ft) {
CFunc ^cf = gcnew CFunc(ft);
program::ProgressCallback ^func = gcnew program::ProgressCallback(cf, &CFunc::func);
program ^pro = gcnew program();
pro->registerCallBackFuncktion(func);
}
如上述代码所见,我们不能直接将函数指针传递给C#的委托,需要用一个类封起来。
对于这个用来封装函数指针的类,还需要记住,需要使用关键字ref
修辞,这样才是一个可以被托管的类。至于ref
加上这一关键词的类与原先的类的区别,可以查看这篇文章。
将其封装好了之后,我们需要在注入的地方前,将我们的函数gcnew
出来,再与封装好的成员函数,一起传递给C#
的委托,这里类似于构造函数参数。
这是最简单的一种情况了,如果C#
用到了泛型的话,其实也并没有麻烦多少。
public delegate T3 CallBackText<in T1, in T2, out T3>(T1 arg1, T2 arg2);
public void Generic(CallBackText<string, string, string> func)
{
Console.WriteLine(func("arg1", "arg2"));
}
我们在C#
中,需要回调这么一个泛型委托。
在C++/CLI
中我们可以这么实现调用
typedef std::string (*FuncType) (std::string arg1, std::string arg2);
ref class RFunc {
public:
RFunc(FuncType f) : f(f) {};
System::String^ func(System::String ^arg1, System::String ^arg2) {
const char* fir = (const char*)(Marshal::StringToHGlobalAnsi(arg1)).ToPointer();
const char* sec = (const char*)(Marshal::StringToHGlobalAnsi(arg2)).ToPointer();
std::string res = f(std::string(fir), std::string(sec));
return gcnew String(res.c_str());
}
private:
FuncType f;
};
std::string test() {
RFunc ^rf = gcnew RFunc(func);
Func<String ^, String ^, String ^> ^action = gcnew Func<String ^, String ^, String ^>(rf, &RFunc::func);
func(action);
}
一切就跟C#
一样顺其自然。需要额外注意的就是gcnew
出来的东西,需要指定类型就可以了。这里我们可以用一切在C#
中可以使用的类型。