Unity3D Native 插件开发(3)— 理解 IL2CPP 机制
发表于2016-05-17
这篇文章的目的,主要是介绍 Unity中,C# 代码是如何调用 C 语言代码,包括内部的实现机制和原理
写在最前
Unity 4.0 使用的是 mono dl 机制来实现 C# 到 C 语言的调用,简单来说是如下的模式:
注册 C 语言函数名称(mono_dl_register_symbol)
mono_dl_open、mono_dl_symbol、mono_dl_close 来实现函数调用
需要说说明的是,不同于系统的dlopen、dlsym、dlclose函数是用来调用动态库(.so),mono调用的实际上是静态机器码
可以下载mono源码进行研究:https://github.com/mono/mono
Unity 5 对底层机制进行了一次革新,这篇文章主要基于 Unity 5.x 版本进行说明
基础知识
IL2CPP, 是将中间语言转换成C++语言的一种技术,执行过程如下:
Unity 脚本(C#、JavaScript)编译成中间语言(IL,动态库等)
利用IL2CPP程序,将IL转换成C++语言
与之前不同,编译到 XCode 之后不再包含动态库(.dll)文件
理论上讲,直接用C++编译运行机器码,要比 mono 运行环境代码快许多,不得不说 Unity 为了性能也是够拼了
想要了解跟多,可以查看:http://blogs.unity3d.com/2015/05/06/an-introduction-to-ilcpp-internals/
代码分析
在利用前一节的示例功能,在XCode工程中找到
char* IOS_GetUUID()
函数位置并断点在入口处,运行程序后分析调用堆栈:
找到调用函数如下
extern "C" String_t* UUID_IOS_GetUUID_m302711706 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
{
typedef char* (DEFAULT_CALL *PInvokeFunc) ();
static PInvokeFunc _il2cpp_pinvoke_func;
if (!_il2cpp_pinvoke_func)
{
_il2cpp_pinvoke_func = (PInvokeFunc)IOS_GetUUID;
if (_il2cpp_pinvoke_func == NULL)
{ IL2CPP_RAISE_MANAGED_EXCEPTION(il2cpp_codegen_get_not_supported_exception("Unable to find method for p/invoke: 'IOS_GetUUID'"));
}
}
// Native function invocation and marshaling of return value back from native representation
char* _return_value = _il2cpp_pinvoke_func();
String_t* __return_value_unmarshaled = NULL;
__return_value_unmarshaled = il2cpp_codegen_marshal_string_result(_return_value);
il2cpp_codegen_marshal_free(_return_value);
_return_value = NULL;
return __return_value_unmarshaled;
}
可以看到这个函数已经完整可以看出Unity的处理模式了:
1、定义一个无参数,返回值为 char* 的函数方法
typedef char* (DEFAULT_CALL *PInvokeFunc) ();
2、把我们编写的函数进行类型转换
_il2cpp_pinvoke_func = (PInvokeFunc)IOS_GetUUID;
3、调用函数,并获取返回值
char* _return_value = _il2cpp_pinvoke_func();
4、将结果保存到 C# 内部使用的结构
__return_value_unmarshaled = il2cpp_codegen_marshal_string_result(_return_value);
5、释放函数返回的 char* 指针
il2cpp_codegen_marshal_free(_return_value);
注意:到这里对内存进行了释放,那么在我们实现的函数结构体内不用担心内存泄露了
再深入一步
在同一个文件cpp中,可以找到下面的一些方法
extern "C" void UUID__ctor_m364224832 (UUID_t2616251 * __this, const MethodInfo* method)
extern "C" {char* DEFAULT_CALL IOS_GetUUID();}
extern "C" String_t* UUID_IOS_GetUUID_m302711706 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
包含了 C# 中 UUID 类的完整定义
另外,我在本次使用的测试脚本代码如下:
public class DLLCall : MonoBehaviour {
void Start() {
UUID uuid = new UUID ();
Debug.Log ("Get UUID : " + uuid.GetUUID ());
}
}
在cpp文件中,可以找到如下的一些方法
extern "C" void DLLCall__ctor_m2381644905 (DLLCall_t2468803906 * __this, const MethodInfo* method)
extern "C" void DLLCall_Start_m1328782697 (DLLCall_t2468803906 * __this, const MethodInfo* method)
所以,整个运行机制的轮廓就大致清楚了,正如前文 IL2CPP 描述的那样,C# 代码最终都被转换成了 C++ 代码
多说两句
我们知道,Unity 游戏最终其实运行在虚拟机中,这次 Unity 5 把虚拟机也变成了现在的 IL2CPP 模式的虚拟机,最终效率如何还不得而知