文章

IL2CPP - 始末

IL2CPP - 始末

什么是IL2CPP?

替代

IL2CPP的技术有两个不同的部分。

  • An ahead-of-time (AOT) compiler
  • A runtime library to support the virtual machine

AOT编译器将中间语言(IL)(.NET编译器的低层输出)转换为C ++源代码。运行时库提供服务和抽象,如垃圾收集器,对线程和文件的独立于平台的访问,以及内部调用的实现(直接修改托管数据结构的本机代码)。

AOT 编译器

IL2CPP AOT编译器名为il2cpp.exe.

工具链图示

运行时库

主要以C++支持虚拟机的运行时库 -> libil2cpp.a 静态库。

运行时库的 GC 使用的是 libgc

il2cpp.exe 是如何执行的?

使用 mono 执行 il2cpp.exe

1
2
"%UNITY_INSTALL_PATH%\Editor\Data\MonoBleedingEdge\bin\mono.exe" \
"%UNITY_INSTALL_PATH%\Editor\Data\il2cpp\build\il2cpp.exe" \

参数列表参考 IL2CPP.exe参数详解

il2cpp.exe 在内部解析程序集时,只需明确那些“根节点”形式的程序集(没有被任何其他程序集引用的程序集)

调试异常

il2cpp_codegen_raise_exception 函数上设置断点,显式抛出托管异常的任何地方会触发该断点。

生成的代码是如何运作的

函数 使用Metadata的形式存储uint32_t作为key值,

  • 安卓上:assets\bin\Data\Managed\Metadata\global-metadata.dat作为函数表

代码生成

C# 源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

class BasePlayer
{
    public virtual void MoveTo(int x, int y){}
}

interface Attacker
{
    void DoAttack();
}

class Player: BasePlayer, Attacker
{
    private int m_id;
    private string m_name;
    private int m_x;
    private int m_y;
    
    public Player() { m_id = ms_inc_id++; }
    public void SetName(string name) { m_name = name; }
    public override void MoveTo(int x, int y) { m_x = x; m_y = y; }
    public void DoAttack() { }

    public static int ms_inc_id;
    public static int GetMaxIncId() { return ms_inc_id; }
}

public class Test : MonoBehaviour
{
    public void Awake()
    {
        Player player = new Player();
        player.SetName("hello");
        int max_id = Player.GetMaxIncId();

        player.MoveTo(10, 20);

        BasePlayer base_player = player;
        base_player.MoveTo(10, 20);

        player.DoAttack();

        Attacker attacker = player;
        attacker.DoAttack();
    }
}

IL2Cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Player类的成员变量相关部分,包括变量定义与访问函数
struct  Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873  : public BasePlayer_t493E7E63F7E4F4B95A6F7F7469A98DC4875D9759
{
public:
	// System.Int32 Player::m_id
	int32_t ___m_id_0;
	// System.String Player::m_name
	String_t* ___m_name_1;
	// System.Int32 Player::m_x
	int32_t ___m_x_2;
	// System.Int32 Player::m_y
	int32_t ___m_y_3;

public:
	inline static int32_t get_offset_of_m_id_0() { return static_cast<int32_t>(offsetof(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873, ___m_id_0)); }
	inline int32_t get_m_id_0() const { return ___m_id_0; }
	inline int32_t* get_address_of_m_id_0() { return &___m_id_0; }
	inline void set_m_id_0(int32_t value)
	{
		___m_id_0 = value;
	}

	inline static int32_t get_offset_of_m_name_1() { return static_cast<int32_t> (offsetof(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873, ___m_name_1)); }
	inline String_t* get_m_name_1() const { return ___m_name_1; }
	inline String_t** get_address_of_m_name_1() { return &___m_name_1; }
	inline void set_m_name_1(String_t* value)
	{
		___m_name_1 = value;
		Il2CppCodeGenWriteBarrier((&___m_name_1), value);
	}

	inline static int32_t get_offset_of_m_x_2() { return static_cast<int32_t>(offsetof(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873, ___m_x_2)); }
	inline int32_t get_m_x_2() const { return ___m_x_2; }
	inline int32_t* get_address_of_m_x_2() { return &___m_x_2; }
	inline void set_m_x_2(int32_t value)
	{
		___m_x_2 = value;
	}

	inline static int32_t get_offset_of_m_y_3() { return static_cast<int32_t>(offsetof(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873, ___m_y_3)); }
	inline int32_t get_m_y_3() const { return ___m_y_3; }
	inline int32_t* get_address_of_m_y_3() { return &___m_y_3; }
	inline void set_m_y_3(int32_t value)
	{
		___m_y_3 = value;
	}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Player的静态字段,静态成员方法
struct Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_StaticFields
{
public:
	// System.Int32 Player::ms_inc_id
	int32_t ___ms_inc_id_4;

public:
	inline static int32_t get_offset_of_ms_inc_id_4() { return static_cast<int32_t>(offsetof(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_StaticFields, ___ms_inc_id_4)); }
	inline int32_t get_ms_inc_id_4() const { return ___ms_inc_id_4; }
	inline int32_t* get_address_of_ms_inc_id_4() { return &___ms_inc_id_4; }
	inline void set_ms_inc_id_4(int32_t value)
	{
		___ms_inc_id_4 = value;
	}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//Player的成员方法,都转换为普通的C语言函数
// System.Void Player::.ctor()
extern "C" IL2CPP_METHOD_ATTR void Player__ctor_m8F4AB650C5E2DE406B3C65EA8F662013458D85E2 (Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * __this, const RuntimeMethod* method)
{
	static bool s_Il2CppMethodInitialized;
	if (!s_Il2CppMethodInitialized)
	{
		il2cpp_codegen_initialize_method (Player__ctor_m8F4AB650C5E2DE406B3C65EA8F662013458D85E2_MetadataUsageId);
		s_Il2CppMethodInitialized = true;
	}
	{
		BasePlayer__ctor_m98D4D3463D7B8AD3FF7C174A78D04319DC62D6B8(__this, /*hidden argument*/NULL);
		int32_t L_0 = ((Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_StaticFields*)il2cpp_codegen_static_fields_for(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_il2cpp_TypeInfo_var))->get_ms_inc_id_4();
		int32_t L_1 = L_0;
		((Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_StaticFields*)il2cpp_codegen_static_fields_for(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_il2cpp_TypeInfo_var))->set_ms_inc_id_4(((int32_t)il2cpp_codegen_add((int32_t)L_1, (int32_t)1)));
		__this->set_m_id_0(L_1);
		return;
	}
}
// System.Void Player::SetName(System.String)
extern "C" IL2CPP_METHOD_ATTR void Player_SetName_mDD640E96CE739C5662738AA4B4F862A124FF20C7 (Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * __this, String_t* ___name0, const RuntimeMethod* method)
{
	{
		String_t* L_0 = ___name0;
		__this->set_m_name_1(L_0);
		return;
	}
}
// System.Void Player::MoveTo(System.Int32,System.Int32)
extern "C" IL2CPP_METHOD_ATTR void Player_MoveTo_mD20C7209EFFD10FC3E0FF58ADF33C20CEF5E1401 (Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * __this, int32_t ___x0, int32_t ___y1, const RuntimeMethod* method)
{
	{
		int32_t L_0 = ___x0;
		__this->set_m_x_2(L_0);
		int32_t L_1 = ___y1;
		__this->set_m_y_3(L_1);
		return;
	}
}
// System.Void Player::DoAttack()
extern "C" IL2CPP_METHOD_ATTR void Player_DoAttack_m61F072879C649B4021D4A874D10BDD0312E2927E (Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * __this, const RuntimeMethod* method)
{
	{
		return;
	}
}
// System.Int32 Player::GetMaxIncId()
extern "C" IL2CPP_METHOD_ATTR int32_t Player_GetMaxIncId_m1A786DC568EA4BA165609184EF3D4EF69CB2F106 (const RuntimeMethod* method)
{
	static bool s_Il2CppMethodInitialized;
	if (!s_Il2CppMethodInitialized)
	{
		il2cpp_codegen_initialize_method (Player_GetMaxIncId_m1A786DC568EA4BA165609184EF3D4EF69CB2F106_MetadataUsageId);
		s_Il2CppMethodInitialized = true;
	}
	{
		int32_t L_0 = ((Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_StaticFields*)il2cpp_codegen_static_fields_for(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_il2cpp_TypeInfo_var))->get_ms_inc_id_4();
		return L_0;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// System.Void Test::Awake()
extern "C" IL2CPP_METHOD_ATTR void Test_Awake_m34445407AE2A9CD5BF518DF7EC7DCCF2BCE08B3C (Test_tD59136436184CD9997A7B05E8FCAF0CB36B7193E * __this, const RuntimeMethod* method)
{
	static bool s_Il2CppMethodInitialized;
	if (!s_Il2CppMethodInitialized)
	{
        //在il2cpp转换时,它将收集每个函数所需Class类型信息,方法函数信息,字符串等,并通过一个ID记录在静态表中。
        //在函数首次执行时,通过ID查找并初始化这些信息,以支撑下面函数代码的执行。
		il2cpp_codegen_initialize_method (Test_Awake_m34445407AE2A9CD5BF518DF7EC7DCCF2BCE08B3C_MetadataUsageId);
		s_Il2CppMethodInitialized = true;
	}
	{
        //传入Class类信息,创建Player对象实例
		Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * L_0 = (Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 *)il2cpp_codegen_object_new(Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873_il2cpp_TypeInfo_var);
        //执行Player对象实例的构造函数
		Player__ctor_m8F4AB650C5E2DE406B3C65EA8F662013458D85E2(L_0, /*hidden argument*/NULL);

//调用player.SetName("Hello");
		Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * L_1 = L_0;
		NullCheck(L_1);
		Player_SetName_mDD640E96CE739C5662738AA4B4F862A124FF20C7(L_1, _stringLiteralAAF4C61DDCC5E8A2DABEDE0F3B482CD9AEA9434D, /*hidden argument*/NULL);
  
//调用静态成员函数Player.GetMaxIncId();
		Player_GetMaxIncId_m1A786DC568EA4BA165609184EF3D4EF69CB2F106(/*hidden argument*/NULL);

        //调用函方法player.MoveTo(10, 20);
		Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * L_2 = L_1;
		NullCheck(L_2);
		VirtActionInvoker2< int32_t, int32_t >::Invoke(4 /* System.Void BasePlayer::MoveTo(System.Int32,System.Int32) */, L_2, ((int32_t)10), ((int32_t)20));

//从基类调用函方法player.MoveTo(10, 20);
		Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * L_3 = L_2;
		NullCheck(L_3);
		VirtActionInvoker2< int32_t, int32_t >::Invoke(4 /* System.Void BasePlayer::MoveTo(System.Int32,System.Int32) */, L_3, ((int32_t)10), ((int32_t)20));
        
//从Player类调用DoAttack()
		Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * L_4 = L_3;
		NullCheck(L_4);
		Player_DoAttack_m61F072879C649B4021D4A874D10BDD0312E2927E(L_4, /*hidden argument*/NULL);
		NullCheck(L_4);

        //从Attacker接口调用DoAttack()
		InterfaceActionInvoker0::Invoke(0 /* System.Void Attacker::DoAttack() */, Attacker_t9A07C7A5F8499E6710C114E5B410A2E1437B1AEF_il2cpp_TypeInfo_var, L_4);
		return;
	}
}

1. C#类转换

  • C#类在通过IL2CPP转换后,会生成一个对应的struct的,这个struct继承于RuntimeObject(Il2CppObject)
  • 在这个struct内定义了C#类中的成员变量和访问内联函数(get, set)
  • 如果C#类中有静态成员变量,那么转换之后,会有一个专门的struct来对应这些静态成员变量

在运行时又是怎样调用和获取静态成员变量?

静态strcut对象是记录在Class信息中的,klass就能获得。

值类型对象(用struct定义的),在转换之后的C++代码也只是一个单纯的struct,不继承RuntimeObject。

1
2
3
4
5
6
7
8
9
typedef struct Il2CppObject
{
    union
    {
        Il2CppClass *klass;
        Il2CppVTable *vtable;
    };
    MonitorData *monitor;
} Il2CppObject;
1
2
3
4
5
//获取类的静态成员对象实例
inline void* il2cpp_codegen_static_fields_for(RuntimeClass* klass)
{
    return klass->static_fields;
}

2. C#对象创建

创建一个C#类对象时,调用il2cpp_codegen_object_new(RuntimeClass *klass)函数,并传入Class类型信息即可,它会根据 klass.instance_size ,用IL2CPP的BoehmGC内存分配器中去分配一个内存空间,然后再调用对应的构造函数做初始化。

1
2
3
4
inline RuntimeObject* il2cpp_codegen_object_new(RuntimeClass *klass)
{
    return il2cpp::vm::Object::New(klass);
}

值类型的对象创建,就是直接在函数中定义一个struct的局部变量,在函数结束时生命周期终止。

3. 函数调用

普通函数调用

从上面转换后的代码,我们可以看出普通的成员函数将转换为C语言函数,在调用时传入对象实例即可,如果是静态成员函数就直接传入NULL。

虚成员函数调用

如果是虚成员函数,那么需要通过Class信息查虚函数表的方式完成,这个虚函数表是在Class初始化时加载和装配完成的,在调用时直接通过下标索引去查找函数地址。

1
2
3
4
5
FORCE_INLINE const VirtualInvokeData& il2cpp_codegen_get_virtual_invoke_data(Il2CppMethodSlot slot, const RuntimeObject* obj)
{
    Assert(slot != kInvalidIl2CppMethodSlot && "il2cpp_codegen_get_virtual_invoke_data got called on a non-virtual method");
    return obj->klass->vtable[slot];
}

接口的成员函数调用

接口的成员函数调用,是先传入接口的Class类型参数,去Player的Class类型信息中查找接口类型的函数偏移下标,然后从vtable中取出函数指针。

Class类型信息包含了它继承的所有接口的类型信息和每个接口的虚成员函数起始offset,在调用时遍历接口信息数组,找到匹配的接口,取出offset。

offset+slot就是最终调用函数的位置。

因为要遍历数组,所以IL2CPP通过接口调用函数的效率会稍低一些,尤其是类的实现接口比较多的情况下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        static FORCE_INLINE const VirtualInvokeData& GetInterfaceInvokeDataFromVTable(const Il2CppObject* obj, const Il2CppClass* itf, Il2CppMethodSlot slot)
        {
            const Il2CppClass* klass = obj->klass;
            IL2CPP_ASSERT(klass->initialized);
            IL2CPP_ASSERT(slot < itf->method_count);

            for (uint16_t i = 0; i < klass->interface_offsets_count; i++)
            {
                if (klass->interfaceOffsets[i].interfaceType == itf)
                {
                    int32_t offset = klass->interfaceOffsets[i].offset;
                    IL2CPP_ASSERT(offset != -1);
                    IL2CPP_ASSERT(offset + slot < klass->vtable_count);
                    return klass->vtable[offset + slot];
                }
            }

            return GetInterfaceInvokeDataFromVTableSlowPath(obj, itf, slot);
        }

4. 数组转换

所有的数组定义在转换后都会生成一个继承于RuntimeArray(Il2CppArray)的数组struct。

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct Il2CppArray : public Il2CppObject
{
#else
typedef struct Il2CppArray
{
    Il2CppObject obj;
#endif //__cplusplus
    /* bounds is NULL for szarrays */
    Il2CppArrayBounds *bounds;
    /* total number of elements of the array */
    il2cpp_array_size_t max_length;
} Il2CppArray;

生成的Player[]数组对于的C++定义如下

  • 对于元素是引用类型的,在这个struct中定义了一个C++指针数组用于指向每个对象元素,另外还提供了访问数组元素的成员函数。因为转换后数组struct都是一样的成员变量分布
  • 对于引用类型,可以直接实现C#中关于Object[] = Player[]的转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Player[]
struct PlayerU5BU5D_tB894FDF24E92B3AF2659FE883E1CF369BF663E2A  : public RuntimeArray
{
public:
	ALIGN_FIELD (8) Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * m_Items[1];

public:
	inline Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * GetAt(il2cpp_array_size_t index) const
	{
		IL2CPP_ARRAY_BOUNDS_CHECK(index, (uint32_t)(this)->max_length);
		return m_Items[index];
	}
	inline Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 ** GetAddressAt(il2cpp_array_size_t index)
	{
		IL2CPP_ARRAY_BOUNDS_CHECK(index, (uint32_t)(this)->max_length);
		return m_Items + index;
	}
	inline void SetAt(il2cpp_array_size_t index, Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * value)
	{
		IL2CPP_ARRAY_BOUNDS_CHECK(index, (uint32_t)(this)->max_length);
		m_Items[index] = value;
		Il2CppCodeGenWriteBarrier(m_Items + index, value);
	}
	inline Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * GetAtUnchecked(il2cpp_array_size_t index) const
	{
		return m_Items[index];
	}
	inline Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 ** GetAddressAtUnchecked(il2cpp_array_size_t index)
	{
		return m_Items + index;
	}
	inline void SetAtUnchecked(il2cpp_array_size_t index, Player_t8321F4671F549F5A7793BB8BA33D32CCCD538873 * value)
	{
		m_Items[index] = value;
		Il2CppCodeGenWriteBarrier(m_Items + index, value);
	}
};

5. 泛型转换

对于Class<T>泛型,

  • T值类型,那么会将泛型的代码做类型特化展开
    • 例如List, List会展开两份相关的成员函数代码,与C++模版类似。
  • T是引用类型,将会采用泛型共享的方式,所有的成员函数会生成一份基于System.Object的函数代码。
    • 如果在代码中涉及到特定类型的使用,将会通过Class元数据去查找特化类型实例参数。(这些实例参数是在转换中由il2cpp.exe收集生成的。)
    • il2cpp通过泛型共享的机制,达成了不会因为使用大量泛型而导致代码量爆炸的效果。
1
2
3
4
// System.Void MyList`1<System.Int32>::.ctor()
extern "C" IL2CPP_METHOD_ATTR void MyList_1__ctor_mCC45431375EEB5D049F182589C7586E9480A8A9B_gshared (MyList_1_t7956D4B2967540F845A681F98213B9D311056BAE * __this, const RuntimeMethod* method)
{
}
1
2
3
4
// System.Void MyList`1<System.Int64>::.ctor()
extern "C" IL2CPP_METHOD_ATTR void MyList_1__ctor_mDFDDB0BA3C102F28A9AD2894F0B928D86EA2D492_gshared (MyList_1_tD62186FCDCC59607A7DBFC478502654A261E7601 * __this, const RuntimeMethod* method)
{
}

对于如下的C#泛型类

1
2
3
4
5
6
7
8
9
public class MyList<T>
{
    T[] m_list;

    public void Init(int size)
    {
        m_list = new T[size];
    }
}

生成的init函数的C++代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// System.Void MyList`1<System.Object>::Init(System.Int32)
extern "C" IL2CPP_METHOD_ATTR void MyList_1_Init_m4F5130C7AB23CAA44AAD888A231F0B97E4E79F55_gshared (MyList_1_tFCB06FD3CD5A6E86CCA6B14E6F7387622058C594 * __this, int32_t ___size0, const RuntimeMethod* method)
{
	{
		// m_list = new T[size];
		int32_t L_0 = ___size0;
		ObjectU5BU5D_t3C9242B5C88A48B2A5BD9FDA6CD0024E792AF08A* L_1 = (ObjectU5BU5D_t3C9242B5C88A48B2A5BD9FDA6CD0024E792AF08A*)SZArrayNew(IL2CPP_RGCTX_DATA(method->klass->rgctx_data, 0), (uint32_t)L_0);
		__this->set_m_list_0(L_1);
		// }
		return;
	}
}

通过类反射信息中的泛型实例参数(SZArrayNew(IL2CPP_RGCTX_DATA(method->klass->rgctx_data, 0), (uint32_t)L_0))实现了new T[size]的作用。

本文由作者按照 CC BY 4.0 进行授权