一、CPP和Csharp的交互概述
要提供脚本功能,将Mono运行时嵌入C/C++程序之后,只是在C/C++程序中调用C#中定义的方法显然还是不够的。脚本机制的最终目的还是希望能够在脚本语言中使用原生的代码。
为了使托管代码能够和非托管代码交互,我们需要在C#文件中引入命名空间System.Runtime.CompilerServices,同时需要提供一个IntPtr类型的句柄以便于托管代码和非托管代码之间引用数据。
(IntPtr 类型被设计成整数,其大小适用于特定平台。 即是说,此类型的实例在 32 位硬件和操作系统中将是 32 位,在 64 位硬件和操作系统上将是 64 位。IntPtr 对象常可用于保持句柄。 例如,IntPtr 的实例广泛地用在 System.IO.FileStream 类中来保持文件句柄。)
C#端主要目的是为游戏脚本提供相应的接口,而非具体逻辑的实现。将C#类对象的构建工作以及各种具体实现逻辑移交给非托管代码C++。
1.C#端不存储任何数据,除了IntPtr句柄。
2.C#端不负责任何实现,仅定义接口。
二、C#调用C++(内部调用)
返回值/参数类型传递:
类型传递见补充。
命名空间:
using System.Runtime.CompilerServices;
C#端方法签名:
[MethodImpl(MethodImplOptions.InternalCall)]
public extern static CSType CSMethod(...);
- 通常为静态方法,通常有一个IntPtr句柄参数传递对象地址。
C++端函数定义:
CPPType CPPMethod(){//……}
关联:
mono_add_internal_call("C#命名空间名.C#类名::C#方法名()", reinterpret_cast<void*>(CPPMethod));
- 方法名括号可省略。
- 对于不调用其他方法的属性实现,默认方法名称
get_属性名
。传递参数为C#对象(即CPP端的MonoObject*)。
补充:P/Invoke:
CRE:使用P/Invoke也可以实现C#调用C++。Mono扩充过的P/Invoke支持查找不在外部库而在与你程序相同地址空间的内部符号。
CRE:详见文档(http://docs.go-mono.com/index.aspx?link=xhtml%3adeploy%2fmono-api-embedding.html)。
三、C++调用C#
第一步:编写.cs代码并用mcs编译:
//Program.cs
namespace MonoCSharp
{
public class MainTest
{
public static void HelloWorld()
{
Console.WriteLine("Hello CSharp!");
Console.ReadLine();
}
}
}
第二步:在C++代码中调用:
//...
//获取MonoClass,类似于反射
MonoClass* main_class = mono_class_from_name(image, "MonoCSharp", "MainTest");
//获取要调用的MonoMethodDesc,主要调用过程
MonoMethodDesc* entry_point_method_desc = mono_method_desc_new("MonoCSharp.MainTest:HelloWorld()", true);
MonoMethod* entry_point_method = mono_method_desc_search_in_class(entry_point_method_desc,main_class);
mono_method_desc_free(entry_point_method_desc);
//调用方法
mono_runtime_invoke(entry_point_method, NULL, NULL, NULL);
//...
注意:mono_runtime_invoke()
第一个参数是方法。
第二个参数是对象,如果是静态方法,则为NULL。
第三个参数是参数(void*数组)。 第四个参数会传回调用时发生的exception。
C++调用Mono的Main函数入口:
CRE:可以调用控制台应用程序的Main函数,也可以调用Gtk#和WinForms的Main函数。
//Main调用
char* args = const_cast<char*>("arg0");
mono_jit_exec(domain, assembly_editor, 1, &args);