Unity引擎使用mono作为脚本的运行环境,本文主要使用mono的internal call来进行c++与c#的互操作
1.数据格式
进行互操作是需要进行数据传递,这里主要分为三类,原生类型,值类型,以及引用类型
1.1 原生类型
常用如下
c++ c# mono
int int int
float float int
char* string MonoString*
c#的string和MonoString*会在使用是自动转换,所以需要进行MonoString* 与char*的转换,这里直接使用了stl的std::string.
std::string MonoStringToString(MonoString* monoString)
{
char* pStr = mono_string_to_utf8(monoString);
std::string str = pStr;
mono_free(pStr);
return str;
}
MonoString* StringToMonoString(std::string str)
{
MonoDomain* domain = mono_domain_get();
MonoString* monoString = mono_string_new(domain, str.c_str());
return monoString;
}
数组对象分别是c++的数组,c#的数组,以及Mono的MnonoArray*
转换如下
c++
template<class T>
MonoArray* VectorToMonoArray(std::vector<T*> vec, MonoClass* monoClass)
{
MonoArray* pMonoArr = NULL;
//MonoClass* pMonoClass = mono_object_get_class(T::getTypeNameStatic());
MonoDomain* pDomain = mono_domain_get();
MonoType* t = mono_class_get_type(monoClass);
size_t size = vec.size();
// - create a ubyte array
pMonoArr = mono_array_new(pDomain, monoClass, size);
if (NULL == pMonoArr)
{
return NULL;
}
// - convert string into mono string and add them to a mono byte array
for (uint32 ii = 0; ii<size; ii++)
{
MonoObject* tp = vec[ii]->getMonoObject(); //这里已经绑定好了c#对象,具体代码往下看
mono_array_set(pMonoArr, MonoObject*, ii, tp);
}
return pMonoArr;
}
template<class T>
std::vector<T*> MonoArrayToVector(MonoArray* monoArray)
{
MonoObject* pStr = NULL;
size_t size = mono_array_length(monoArray);
std::vector<T*> arr;
for (uint32 ii = 0; ii<size; ii++)
{
pStr = mono_array_get(monoArray, MonoObject*, ii);
T* tmp = convert<T>(pStr);
arr.push_back(tmp);
}
return arr;
}
1.2 值类型
值类型是指在c#中使用struct进行定义的类型,和enum定义的类型。对于值类型,只需要在c++和c#中分别定义相应的成员变量即可,例如
c++
class Vector2
{
public:
float x,y;
};
c#
struct Vector2
{
public:
float x,y;
}
又或者
c++
enum Direction
{
front,back,left,right
};
c#
enum Direction
{
front,back,left,right
}
注意,这里的顺序需要保持一致。这里要注意,自己定义的值类型不能作为绑定函数的返回值,否则会出现异常的错误。所以为了正确地从c++中获取值,需要使用引用。例子如下
c++
void ICall_ClassA_getPosition(MonoObject* self,Vector2& pos)
{
...
}
c#
private extern static void ICall_ClassA_getPosition(ClassA self, out Vector2 pos);
1.3 引用类型
在mono中统一转换为MonoObject*
2.环境初始化,绑定,调用,清理
// 设置搜索路径,主要用于搜索mscorlib.dll
std::string MonoPath = "C:/Program Files (x86)/Mono";
std::string ManagedLibraryPath = "E:/TestAllRuntime/TestAllRuntime.dll";
std::string monoPath = MonoPath;
std::string monoLibPath = monoPath + "/lib";
std::string monoEtcPath = monoPath + "/etc";
mono_set_dirs(monoLibPath.c_str(),monoEtcPath.c_str());
// dll所在的位置
const char* managed_binary_path = ManagedLibraryPath.c_str();
//获取应用域
domain = mono_jit_init("TestAllRuntime"); // TestAllRuntime可以随意取
mono_config_parse(NULL);
//加载程序集ManagedLibrary.dll
MonoAssembly* assembly = mono_domain_assembly_open(domain, managed_binary_path);
image = mono_assembly_get_image(assembly);
// 注册函数,这里是以后主要添加的地方 ,所有的函数都是通过该方法绑定的
// TestAllRuntime 是c#中ClassA的namespace
// ClassA 是 类的名称
// ClassA_bind 是c#中需要绑定的方法
// &ClassA_bind 的ClassA_bind 是c++中需要绑定的方法
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_bind",
reinterpret_cast<void*>(&ClassA_bind));
MonoClass* main_class = mono_class_from_name(image, "TestAllRuntime", "Main");
const bool include_namespace = true; //是否包含命名空间
MonoMethodDesc* managed_method_desc = mono_method_desc_new("TestAllRuntime.Main:main()", include_namespace);
MonoMethod* managed_method = mono_method_desc_search_in_class(managed_method_desc, main_class);
mono_method_desc_free(managed_method_desc);
//调用刚才获取的TestAllRuntime.Main:main(),第二个参数为NULL,表示调用静态函数。如果函数不是静态的,那么就需要传相应的MonoObject
mono_runtime_invoke(managed_method, NULL, NULL, NULL);
//清理mono环境
mono_jit_cleanup(domain);
主要的运行流程如上述代码所示
3.实现脚本功能
为了实现脚本功能,需要在c++中持有c#对象的handle,再通过handle就可以获得相应的c#对象。在c#中也需要持有c++对象的pointer。为了更快地从c#对象中获取c++对象的指针,可以通过内存地址偏移来获得,具体实现如下。这里参考的是Genesis3d引擎中如何进行绑定的。代码如下
c#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace TestAllRuntime
{
[StructLayout(LayoutKind.Sequential)]
public abstract class Base
{
private IntPtr cppPointer; //会在c++中直接通过偏移获取该字段
}
}
这里先定义一个Base的虚基类,并使用[StructLayout(LayoutKind.Sequential)]来保证IntPtr在内存中的位置,具体的解释可以百度或谷歌。其他所有的类都继承Base。
在c++中就可以使用如下代码获取c#保存的 c++ pointer
c++
struct DataOnHead
{
void* _cppObjectPtr;
};
static const int MonoObjectMemoryOffset = sizeof(void*) * 2;
DataOnHead* getDataOnHead(MonoObject* monoObject)
{
return reinterpret_cast<DataOnHead*> (((char*)monoObject) + MonoObjectMemoryOffset);
}
template<class T>
T* getCppFromMono(MonoObject* monoObject)
{
return static_cast<T*>(getDataOnHead(monoObject)->_cppObjectPtr);
}
void bindCppToMono(void* cppPtr, MonoObject* monoObject)
{
getDataOnHead(monoObject)->_cppObjectPtr = cppPtr;
}
template<class T>
void bindMonoToCpp(MonoObject* monoObject, T* cppPtr)
{
cppPtr->setMonoObject(monoObject);
}
template<class T>
void bindCppAndMono(MonoObject* monoObject, T* cppPtr)
{
bindCppToMono(cppPtr, monoObject);
bindMonoToCpp(monoObject, cppPtr);
}
这里MonoObjectMemoryOffset为什么是sizeof(void*)*2 可以从源代码中或者api文档中看到
typedef struct {
MonoVTable *vtable;
MonoThreadsSync *synchronisation;
} MonoObject;
现在已经可以方便的获得c#的cppPointer了,那么同样,在c++中可以定义变量和函数来保存c#对象的handle,代码如下
c++
typedef unsigned int uint32;
const uint32 c_iInvalidMonoRefID = 0;
class ScriptDetail
{
public:
/// constructor
ScriptDetail()
: m_iMonoRefID(c_iInvalidMonoRefID)
//, m_pScriptObj( NULL )
{}
ScriptDetail::~ScriptDetail()
{
if (c_iInvalidMonoRefID != m_iMonoRefID)
{
m_iMonoRefID = c_iInvalidMonoRefID;
}
}
void ScriptDetail::SetMonoObject(MonoObject* pObj)
{
if (NULL != pObj)
{
if (m_iMonoRefID != c_iInvalidMonoRefID)
{
mono_gchandle_free(m_iMonoRefID);
m_iMonoRefID = c_iInvalidMonoRefID;
}
// - the second param should be 0, means this handle will not resurrection when invkoing finalisers.
m_iMonoRefID = mono_gchandle_new_weakref(pObj, 0);
}
else
{
// - must be set before
std::cout << "ScriptDetail::SetMonoObject,Deadly error!this prarm can't be null!\n" << std::endl;
}
}
/// Get m_pScriptObj
MonoObject* GetMonoObject(void);
/// to see if this cpp object is binding a mono object
bool IsBindMonoObject(void);
private:
// - do not allow copy