Unity3D Native 插件开发(3)— 理解 IL2CPP 机制

发表于2016-05-17
评论1 1.49w浏览
这是一个系列教程,主要内容是如何开发 Unity 插件,在理解这个教程之前需要读者有一定的 Unity、iOS、Android 开发知识:
       

Unity3D Native 插件开发(1)— 原理解析


  这篇文章的目的,主要是介绍 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 模式的虚拟机,最终效率如何还不得而知

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引