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
ScriptDetail(const ScriptDetail&);
ScriptDetail& operator=(const ScriptDetail&);
// - private data
uint32 m_iMonoRefID;
};
// - inline function implement ---
//------------------------------------------------------------------------
inline MonoObject* ScriptDetail::GetMonoObject(void)
{
return mono_gchandle_get_target(m_iMonoRefID);
}
inline bool ScriptDetail::IsBindMonoObject(void)
{
if(c_iInvalidMonoRefID == m_iMonoRefID)
{
return false;
}
return (NULL != mono_gchandle_get_target(m_iMonoRefID));
}
// - bind
#define __ScriptBind \
public:\
void setMonoObject( MonoObject* pObj ) { this->m_ScriptDetail.SetMonoObject( pObj ); }\
MonoObject* getMonoObject( void ) { return this->m_ScriptDetail.GetMonoObject(); }\
bool isBindMonoObject( void ) { return this->m_ScriptDetail.IsBindMonoObject(); }\
private:\
ScriptDetail m_ScriptDetail;
class ClassA
{
__ScriptBind;
...
};
在类的定义中添加__ScriptBind就可以了,代码也很简单。m_ScriptDetail中初始化时为0,而正确的handle一定大于0,所以根据它来判断是否已经绑定了相应的c#对象。
主要的思想就是这样,剩下的就是定义相应的类,并把需要绑定的函数从类中提出来,再mono_add_internal_call,
然后在c#中定义相应的类和相应的函数。
例如
c++
void ICall_ClassA_createCpp(MonoObject* obj)
{
ClassA* t = new ClassA();
bindCppAndMono<ClassA>(obj,t); //绑定
}
void ICall_ClassA_setValue(MonoObject* obj,int value)
{
ClassA* tmp = getCppFromMono<ClassA>(obj);
tmp->setValue(value);
}
mono_add_internal_call("Test.ClassA::ICall_ClassA_setValue", &ICall_ClassA_setValue);
mono_add_internal_call("Test.ClassA::ICall_ClassA_createCpp", &ICall_ClassA_createCpp);
c#
using System.Runtime.CompilerServices;
class ClassA : Base
{
public ClassA()
{
ICall_ClassA_createCpp(this);
}
public void setValue(int value)
{
ICall_ClassA_setValue(this,value);
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static void ICall_ClassA_createCpp(ClassA self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static void ICall_ClassA_setValue(ClassA self, int value)
}
注意:添加[MethodImplAttribute(MethodImplOptions.InternalCall)] ,并注意 extern static 。
这里使用static的目的是更直观。
注意:为了在c#对象初始化的时候就初始化它的cppPointer,需要在它的构造函数中调用相应的函数来初始化
完整代码如下
ClassA.h
#pragma once
#include <string>
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/class.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-config.h>
#include <iostream>
#include <vector>
typedef unsigned int uint32;
const uint32 c_iInvalidMonoRefID = 0;
class ScriptDetail
{
public:
/// constructor
ScriptDetail()
: m_iMonoRefID(c_iInvalidMonoRefID)
{}
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
ScriptDetail(const ScriptDetail&);
ScriptDetail& operator=(const ScriptDetail&);
// - private data
uint32 m_iMonoRefID;
};
// - inline function implement ---
//------------------------------------------------------------------------
inline MonoObject* ScriptDetail::GetMonoObject(void)
{
return mono_gchandle_get_target(m_iMonoRefID);
}
inline bool ScriptDetail::IsBindMonoObject(void)
{
return (NULL != mono_gchandle_get_target(m_iMonoRefID));
}
// - bind
#define __ScriptBind \
public:\
void setMonoObject( MonoObject* pObj ) { this->m_ScriptDetail.SetMonoObject( pObj ); }\
MonoObject* getMonoObject( void ) { return this->m_ScriptDetail.GetMonoObject(); }\
bool isBindMonoObject( void ) { return this->m_ScriptDetail.IsBindMonoObject(); }\
private:\
ScriptDetail m_ScriptDetail;
class ClassB;
class ClassC;
class ClassD;
enum Direction
{
right, front, left, back
};
class ClassA
{
__ScriptBind;
public:
void ClassA::setInt(int intValue)
{
this->intValue = intValue;
}
int ClassA::getInt()
{
return intValue;
}
void ClassA::setFloat(float floatValue)
{
this->floatValue = floatValue;
}
float ClassA::getFloat()
{
return floatValue;
}
void ClassA::setString(std::string stringValue)
{
this->stringValue = stringValue;
}
std::string ClassA::getString()
{
return stringValue;
}
void setDirection(Direction dir)
{
this->dir = dir;
}
Direction getDirection()
{
return dir;
}
std::vector<ClassB*> getData()
{
return data;
}
void setData(std::vector<ClassB*> data)
{
this->data = data;
}
private:
int intValue;
float floatValue;
std::string stringValue;
Direction dir;
std::vector<ClassB*> data;
};
class ClassB
{
__ScriptBind
public:
int getValue()
{
return value;
}
void setValue(int value)
{
this->value = value;
}
private:
int value;
};
class ClassC : public ClassB
{
public:
float getFloat()
{
return Float;
}
void setFloat(float value)
{
this->Float = value;
}
private:
float Float;
};
class ClassD : public ClassB
{
public:
float getFF()
{
return ff;
}
void setFF(float ff)
{
this->ff = ff;
}
private:
float ff;
};
main.cpp
#include "ClassA.h"
#include <iostream>
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/class.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-config.h>
using namespace std;
MonoDomain* domain;
struct DataOnHead
{
void* _cppObjectPtr;
};
static const int MonoObjectMemoryOffset = sizeof(void*) * 2;
DataOnHead* getDataOnHead(MonoObject* monoObject)
{
return reinterpret_cast<DataOnHead*> (((char*)monoObject) + MonoObjectMemoryOffset);
}
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);
}
template<class T>
T* getCppFromMono(MonoObject* monoObject)
{
return static_cast<T*>(getDataOnHead(monoObject)->_cppObjectPtr);
}
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();
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;
}
template<class T>
static T* convert(MonoObject* self)
{
T* native_handle = getCppFromMono<T>(self);
//MonoClass* monoClass;
//monoClass = mono_object_get_class(self);
//monoClas = mono_class_from_name(image, "TestAllRuntime", "ClassA");
//MonoClassField* field;
//field = mono_class_get_field_from_name(monoClass, "native_handle");
//mono_field_get_value(self, field, (void*)&native_handle);
return native_handle;
}
template<class T>
static void Class_bind(MonoObject* self)
{
T* native_handle = new T();
//MonoClass* monoClass;
//monoClass=mono_object_get_class(self);
//monoClas = mono_class_from_name(image, "TestAllRuntime", "ClassA");
//MonoClassField* field;
//field = mono_class_get_field_from_name(monoClass, "native_handle");
//mono_field_set_value(self, field, (void*)&native_handle);
//T* handle=getCppFromMono<T>(self);
bindCppAndMono<T>(self,native_handle);
//handle = native_handle;
//native_handle->setMonoObject(self);
//bindCppAndMono(self,t);
}
static void ClassA_bind(MonoObject* self)
{
Class_bind<ClassA>(self);
}
static void ClassA_setInt(MonoObject* self, int intValue)
{
//ClassA* a = getCppFromMono<ClassA>(self);
ClassA* a = convert<ClassA>(self);
a->setInt(intValue);
//a->setInt(intValue);
}
static int ClassA_getInt(MonoObject* self)
{
//ClassA* a = getCppFromMono<ClassA>(self);
ClassA* a = convert<ClassA>(self);
return a->getInt();
//return a->getInt();
}
MonoImage* image;
static void ClassA_setFloat(MonoObject* self, float floatValue)
{
//ClassA* a = getCppFromMono<ClassA>(self);
ClassA* a = convert<ClassA>(self);
a->setFloat(floatValue);
//self->setFloat(floatValue);
}
static float ClassA_getFloat(MonoObject* self)
{
//ClassA* a = getCppFromMono<ClassA>(self);
ClassA* a = convert<ClassA>(self);
return a->getFloat();
//return self->getFloat();
}
static void ClassA_setString(MonoObject* self, MonoString* stringValue)
{
//ClassA* a = getCppFromMono<ClassA>(self);
ClassA* a =convert<ClassA>(self);
char* pStr = mono_string_to_utf8(stringValue);
std::string str = pStr;
mono_free(pStr);
//std::cout << str<<std::endl;
//str = "test gulugulu";
//std::string aaa = "tset aaaaab";
a->setString(str);
}
static MonoString* ClassA_getString(MonoObject* self)
{
//ClassA* a = getCppFromMono<ClassA>(self);
ClassA* a = convert<ClassA>(self);
std::string s = a->getString();
//s = "test hahahahahaha";
//cout <<"haha "<< s << endl;
//mono_string_new_wrapper
MonoString* str = mono_string_new(domain, s.c_str());
//MonoString* str = mono_string_new_wrapper(s.c_str());
//MonoString* str = mono_string_new(domain, s.c_str());
//mono_string_new_wrapper
return str;
}
static void ClassA_setDirection(MonoObject* self, Direction dir)
{
ClassA* t = convert<ClassA>(self);
t->setDirection(dir);
}
static void ClassA_setData(MonoObject* self, MonoArray* data)
{
std::vector<ClassB*> vec = MonoArrayToVector<ClassB>(data);
ClassA* tmp = convert<ClassA>(self);
tmp->setData(vec);
}
static MonoArray* ClassA_getData(MonoObject* self)
{
ClassA* tmp = convert<ClassA>(self);
std::vector<ClassB*> vec = tmp->getData();
MonoClass* cl = mono_class_from_name(image,"TestAllRuntime","ClassB");
return VectorToMonoArray<ClassB>(vec,cl);
}
static void ClassA_setCallback(MonoObject* self, MonoObject* object,MonoString* funcName)
{
MonoClass* c = mono_object_get_class(self);
MonoClass* b = mono_object_get_class(object);
char* pStr = mono_string_to_utf8(funcName);
std::string str = pStr;
mono_free(pStr);
MonoMethod* m= mono_class_get_method_from_name(b,str.c_str(),0);
mono_runtime_invoke(m,self,NULL,NULL);
}
void ClassB_bind(MonoObject* self)
{
Class_bind<ClassB>(self);
}
int ClassB_getValue(MonoObject* self)
{
ClassB* tmp = convert<ClassB>(self);
return tmp->getValue();
}
void ClassB_setValue(MonoObject* self, int value)
{
ClassB* tmp = convert<ClassB>(self);
tmp->setValue(value);
}
void ClassC_bind(MonoObject* self)
{
Class_bind<ClassC>(self);
}
float ClassC_getFloat(MonoObject* self)
{
ClassC* tmp = convert<ClassC>(self);
return tmp->getFloat();
}
void ClassC_setFloat(MonoObject* self,float value)
{
ClassC* tmp = convert<ClassC>(self);
tmp->setFloat(value);
}
void ClassD_bind(MonoObject* self)
{
Class_bind<ClassD>(self);
}
float ClassD_getFF(MonoObject* self)
{
ClassD* tmp = convert<ClassD>(self);
return tmp->getFF();
}
void ClassD_setFF(MonoObject* self,float ff)
{
ClassD* tmp = convert<ClassD>(self);
tmp->setFF(ff);
}
void main()
{
// 设置搜索路径,主要用于搜索mscorlib.dll
std::string MonoPath = "C:/Program Files (x86)/Mono";
std::string ManagedLibraryPath = "E:/vsPrjs/Dinky2D/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());
// Program.cs所编译dll所在的位置
const char* managed_binary_path = ManagedLibraryPath.c_str();
//获取应用域
domain = mono_jit_init("TestAllRuntime");
mono_config_parse(NULL);
//加载程序集ManagedLibrary.dll
MonoAssembly* assembly = mono_domain_assembly_open(domain, managed_binary_path);
image = mono_assembly_get_image(assembly);
//====================================================================
// -----------------------
// -- Base - Texture
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_bind",
reinterpret_cast<void*>(&ClassA_bind));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_setInt", reinterpret_cast<void*>(&ClassA_setInt));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_getInt", reinterpret_cast<void*>(&ClassA_getInt));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_setFloat", reinterpret_cast<void*>(&ClassA_setFloat));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_getFloat", reinterpret_cast<void*>(&ClassA_getFloat));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_setString", reinterpret_cast<void*>(&ClassA_setString));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_getString", reinterpret_cast<void*>(&ClassA_getString));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_setDirection", reinterpret_cast<void*>(&ClassA_setDirection));
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_setData",
reinterpret_cast<void*>(&ClassA_setData)
);
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_getData",
reinterpret_cast<void*>(&ClassA_getData)
);
mono_add_internal_call("TestAllRuntime.ClassA::ClassA_setCallback",
reinterpret_cast<void*>(&ClassA_setCallback)
);
mono_add_internal_call("TestAllRuntime.ClassB::ClassB_bind", reinterpret_cast<void*>(&ClassB_bind));
mono_add_internal_call("TestAllRuntime.ClassB::ClassB_getValue", reinterpret_cast<void*>(&ClassB_getValue));
mono_add_internal_call("TestAllRuntime.ClassB::ClassB_setValue", reinterpret_cast<void*>(&ClassB_setValue));
mono_add_internal_call("TestAllRuntime.ClassC::ClassC_bind", reinterpret_cast<void*>(&ClassC_bind));
mono_add_internal_call("TestAllRuntime.ClassC::ClassC_getFloat", reinterpret_cast<void*>(&ClassC_getFloat));
mono_add_internal_call("TestAllRuntime.ClassC::ClassC_setFloat", reinterpret_cast<void*>(&ClassC_setFloat));
mono_add_internal_call("TestAllRuntime.ClassD::ClassD_bind", reinterpret_cast<void*>(&ClassD_bind));
mono_add_internal_call("TestAllRuntime.ClassD::ClassD_getFF", reinterpret_cast<void*>(&ClassD_getFF));
mono_add_internal_call("TestAllRuntime.ClassD::ClassD_setFF", reinterpret_cast<void*>(&ClassD_setFF));
//===============================================================================
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);
mono_runtime_invoke(managed_method, NULL, NULL, NULL);
mono_jit_cleanup(domain);
}
ClassA.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Collections;
namespace TestAllRuntime
{
public enum Direction
{
right,front,left,back
}
public delegate void Callback();
public class ClassA : Base
{
public void print()
{
Console.WriteLine("print line");
}
public void setCallback(Callback func)
{
ClassB b = (ClassB)func.Target;
Console.WriteLine(b.Value);
ClassA_setCallback(this, b ,func.Method.Name);
}
public ClassA()
{
ClassA_bind(this);
}
public int intValue
{
get
{
return ClassA_getInt(this);
}
set
{
ClassA_setInt(this, value);
}
}
public float floatValue
{
get
{
return ClassA_getFloat(this);
}
set
{
ClassA_setFloat(this, value);
}
}
public string getString()
{
return ClassA_getString(this);
}
public void setString(String s)
{
ClassA_setString(this, s);
}
public string stringValue
{
get
{
return ClassA_getString(this);
}
set
{
ClassA_setString(this, value);
}
}
public Direction dir
{
get
{
return ClassA_getDirection(this);
}
set
{
ClassA_setDirection(this, value);
}
}
public ClassB[] data
{
get
{
return ClassA_getData(this);
}
set
{
ClassA_setData(this, value);
}
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static void ClassA_bind(ClassA self);
//private IntPtr native_handle=(IntPtr)0;
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static void ClassA_setInt(ClassA self, int intValue);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static int ClassA_getInt(ClassA self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static void ClassA_setFloat(ClassA self, float floatValue);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static float ClassA_getFloat(ClassA self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static void ClassA_setString(ClassA self, String stringValue);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static string ClassA_getString(ClassA self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static void ClassA_setDirection(ClassA self, Direction dir);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static Direction ClassA_getDirection(ClassA self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern ClassB[] ClassA_getData(ClassA self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassA_setData(ClassA self, ClassB[] data);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassA_setCallback(ClassA self, ClassB obj, string funcName);
}
public class ClassB : Base
{
public void printb()
{
Console.WriteLine("print b");
}
public ClassB()
{
ClassB_bind(this);
}
public int Value
{
get
{
return ClassB_getValue(this);
}
set
{
ClassB_setValue(this, value);
}
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassB_bind(ClassB self);
//private IntPtr native_handle = (IntPtr)0;
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassB_setValue(ClassB self, int value);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern int ClassB_getValue(ClassB self);
}
public class ClassC : ClassB
{
public ClassC()
{
ClassC_bind(this);
}
public float Float
{
get
{
return ClassC_getFloat(this);
}
set
{
ClassC_setFloat(this, value);
}
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassC_bind(ClassC self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassC_setFloat(ClassC self, float value);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern float ClassC_getFloat(ClassC self);
}
public class ClassD : ClassB
{
public ClassD()
{
ClassD_bind(this);
}
public float FF
{
get
{
return ClassD_getFF(this);
}
set
{
ClassD_setFF(this, value);
}
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassD_bind(ClassD self);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void ClassD_setFF(ClassD self, float ff);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern float ClassD_getFF(ClassD self);
}
}
Main.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace TestAllRuntime
{
class Main
{
public static void main()
{
// ClassA a = new ClassA();
// a.intValue = 5;
// a.floatValue = 10.3f;
//// Console.WriteLine(getMemory(a));
// string s = "hello word 哈哈哈";
// a.setString(s);
// a.dir = Direction.left;
// Console.WriteLine(a.intValue);
//Console.WriteLine(a.floatValue);
// Console.WriteLine(a.getString());
// Console.Read();
ClassB tt = new ClassB();
tt.Value = 10;
// ClassA a = new ClassA();
// a.setCallback(tt.printb);
Type typea = tt.GetType();
MethodInfo info = typea.GetMethod("printb");
Console.WriteLine(info.Name);
// 这是测试传递委托
Delegate del= Delegate.CreateDelegate(typeof(Callback), tt, info, false);
del.DynamicInvoke();
ClassC c1 = new ClassC();
c1.Float = 5;
c1.Value = 12;
Console.WriteLine(c1.Float);
Console.WriteLine(c1.Value);
ClassD d1 = new ClassD();
d1.FF = 25;
Console.WriteLine(d1.FF);
// ArrayList l1 = new ArrayList();
ClassB[] l1 = new ClassB[2];
ClassB b1 = new ClassB();
b1.Value = 11;
ClassB b2 = new ClassB();
b2.Value = 13;
l1[0] = c1;
l1[1] = d1;
ClassA a1 = new ClassA();
a1.data = l1;
//ArrayList l2 = a1.data;
ClassB[] l2 = a1.data;
foreach (var item in l2)
{
Type t=item.GetType();
if(t == typeof(ClassC))
{
ClassC c = (ClassC)item;
Console.WriteLine("ClassC {0}", c.Float);
}
else if(t == typeof(ClassD))
{
ClassD c = (ClassD)item;
Console.WriteLine("ClassD {0}", c.FF);
}
else
{
Console.WriteLine("ClassB");
}
}
Console.Read();
}
}
}
运行时注意设置dll的路径和名称以及命名空间的名称
到此为止已经可以在c#中快乐地使用c++的接口了。但是目前的c#的所有类都需要定义相应的InternalCall函数,而我们看unity中定义的脚本并没有这些,所以为了实现与unity类似的脚本,还需要做一些工作。
大体内容就是
1.在c#中定义所有脚本的基类ScriptBase,以后要添加的脚本都要继承来类,否则无法正确添加。
2.在c++中定义类ScriptInstance,它继承自Component类,之后所有的继承自ScriptBase的子类持有的cppPointer都是ScirptInstance类的指针,而ScriptInstance持有ScriptBase子类的对象。然后在ScriptInstance初始化的时候可以获得相应的C#对象,并通过mono的反射机制获取相应的函数,比如update函数,也可以获得它的公共字段,这样就可以实现类似unity的脚本机制了。具体可以参考Genesis3D的源代码。
这里主要参考了《Unity3D脚本编程:使用c#语言开发跨平台游戏》的第二章,作者就是慕容小匹夫。还有Genesis3D的源代码的4.App的ScriptBind和ScriptFeature以及 genesis3d\Engine\script 目录的文件。

本文介绍了如何利用Mono构建C#脚本运行环境,特别是涉及原生类型、值类型和引用类型的转换。通过环境初始化、绑定、调用和清理过程,实现了C++和C#之间的数据交互。示例代码展示了如何在C#中调用C++函数,并创建类似Unity的脚本系统。

824

被折叠的 条评论
为什么被折叠?



