原文链接:https://blog.unity.com/engine-platform/il2cpp-internals-a-tour-of-generated-code

这是 IL2CPP Internals 系列的第二篇博文。在这篇文章中,我们将研究由 il2cpp.exe 生成的 C++ 代码。在此过程中,我们将了解托管类型在本地代码中的表现形式,查看用于支持 .NET 虚拟机的运行时检查,了解循环的生成过程等!

我们将讨论一些特定版本的代码,这些代码在 Unity 的后续版本中肯定会有所变化。不过,这些概念将保持不变。

Example project

在本例中,我将使用 Unity 的最新版本 5.0.1p1。与本系列第一篇文章一样,我将从一个空项目开始,并添加一个脚本文件。这次的内容如下:

using UnityEngine;

public class HelloWorld : MonoBehaviour {
	private class Important {
		public static int ClassIdentifier = 42;
		public int InstanceIdentifier;
	}

	void Start () {
		Debug.Log("Hello, IL2CPP!");
		
		Debug.LogFormat("Static field: {0}", Important.ClassIdentifier);
		
		var importantData = new [] {
			new Important { InstanceIdentifier = 0 },
			new Important { InstanceIdentifier = 1 } };
		
		Debug.LogFormat("First value: {0}", importantData[0].InstanceIdentifier);
		Debug.LogFormat("Second value: {0}", importantData[1].InstanceIdentifier);
		try {
			throw new InvalidOperationException("Don't panic");
		}
		catch (InvalidOperationException e) {
			Debug.Log(e.Message);
		}
		
		for (var i = 0; i < 3; ++i) {
			Debug.LogFormat("Loop iteration: {0}", i);
		}
	}
}

我将在 Windows 上运行 Unity 编辑器,为 WebGL 构建此项目。我在 "构建设置"(Build Settings)中选择了 "Development Player”选项,这样我们就能在生成的 C++ 代码中获得相对漂亮的名称。我还将 "WebGL 播放器设置 "中的 "启用异常 "选项设置为 "完全"。

Overview of the generated code

WebGL 生成完成后,生成的 C++ 代码会出现在我的项目目录下的 Temp\StagingArea\Data\il2cppOutput 目录中。编辑器关闭后,该目录将被删除。不过只要编辑器是打开的,这个目录就不会改变,所以我们可以检查它。

即使是这个小项目,il2cpp.exe 工具也生成了大量文件。我看到了 4625 个头文件和 89 个 C++ 源代码文件。为了处理所有这些代码,我喜欢使用与 Exuberant CTags 兼容的文本编辑器。CTags 通常会为这些代码快速生成一个标签文件,这样就更容易浏览了。

最初,您可以看到生成的许多 C++ 文件并非来自简单的脚本代码,而是标准库中代码的转换版本,如 mscorlib.dll。正如本系列第一篇文章所述,IL2CPP 脚本后台与 Mono 脚本后台使用相同的标准库代码。请注意,每次运行 il2cpp.exe 时,我们都会转换 mscorlib.dll 和其他标准库程序集中的代码。这似乎没有必要,因为这些代码不会改变。

然而,IL2CPP 脚本后台总是使用字节码剥离来减小可执行文件的大小。因此,即使是脚本代码中的微小改动,也会导致标准库代码的许多不同部分被使用或不被使用,这取决于具体情况。因此,我们每次都需要转换 mscorlib.dll 程序集。我们正在研究更好的增量构建方法,但目前还没有任何好的解决方案。

How managed code maps to generated C++ code

对于托管代码中的每个类型,il2cpp.exe 将生成一个头文件,用于该类型的 C++ 定义,另一个头文件用于该类型的方法声明。例如,让我们看看转换后的 UnityEngine.Vector3 类型的内容。该类型的头文件名为 UnityEngine_UnityEngine_Vector3.h。该文件名是根据程序集 UnityEngine.dll 的名称创建的,后面跟有命名空间和类型名称。代码如下:

// UnityEngine.Vector3
struct Vector3_t78
{
	// System.Single UnityEngine.Vector3::x
	float ___x_1;
	// System.Single UnityEngine.Vector3::y
	float ___y_2;
	// System.Single UnityEngine.Vector3::z
	float ___z_3;
};

il2cpp.exe 实用程序对三个实例字段进行了转换,并对名称进行了一些处理,以避免冲突和保留字。通过使用前导下划线,我们使用了 C++ 中的一些保留字,但到目前为止,我们还没有发现与 C++ 标准库代码有任何冲突。

UnityEngine_UnityEngine_Vector3MethodDeclarations.h 文件包含 Vector3 中所有方法的方法声明。例如,Vector3 覆盖了 Object.ToString 方法:

// System.String UnityEngine.Vector3::ToString()
extern "C" String_t* Vector3_ToString_m2315 (Vector3_t78 * __this, MethodInfo* method) IL2CPP_METHOD_ATTR

请注意注释,它指出了该本地声明所代表的托管方法。我经常发现,在输出文件中搜索这种格式的托管方法名称非常有用,尤其是对于名称常见的方法,如 ToString。

请注意 il2cpp.exe 转换的所有方法中的几个有趣之处: