精华内容
下载资源
问答
  • il2cpp
    2022-01-16 18:07:10

    英文原文:

    https://www.jacksondunstan.com/articles/3916

      我们使用 C# 编写代码,但这只是一个起点。我们的 C# 代码被编译为 DLL,然后转换为 C++,然后再次编译为机器代码。好消息是,这不是一个黑匣子!我最近一直在阅读 IL2CPP 输出的 C++ 代码并学到了很多东西。今天的文章是关于我遇到的一些惊喜以及如何更改 C# 代码以避免一些讨厌的陷阱。

    静态变量

      假设我们编写了一个使用静态变量的静态函数。这是基本圆几何:

    static class CircleFunctions
    {
    	private static readonly float Pi = 3.14f;
     
    	public static float Area(float radius)
    	{
    		return Pi * radius * radius;
    	}
    }
    

      请注意,Pi是一个静态变量,而不是一个常数。通常你会把它变成一个常数,但在这个例子中我们将使用一个静态变量。有很多时候你都不能使用常数。

      现在我们来看看IL2CPP为Area生成的C++代码。我在其中加入了内联注释和一些间距,解释了正在发生的事情。

    extern "C"  float CircleFunctions_Area_m4188038 (Il2CppObject * __this /* static, unused */, float ___radius0, const MethodInfo* method)
    {
    	// 仅限于此函数的静态变量
    	// 这用于对方法进行一次性初始化
    	static bool s_Il2CppMethodInitialized;
     
    	// 每次调用该函数时都会触发此If
    	if (!s_Il2CppMethodInitialized)
    	{
    		il2cpp_codegen_initialize_method (CircleFunctions_Area_m4188038_MetadataUsageId);
    		s_Il2CppMethodInitialized = true;
    	}
    	{
    		// This macro expands to this:
    		//   do {
    		//     if((klass)->has_cctor && !(klass)->cctor_finished)
    		//       il2cpp::vm::Runtime::ClassInit ((klass));
    		//   } while (0)
    		// 每次调用函数时都会发生这种情况
    		IL2CPP_RUNTIME_CLASS_INIT(CircleFunctions_t532702825_il2cpp_TypeInfo_var);
     
    		// 访问 Pi
    		float L_0 = ((CircleFunctions_t532702825_StaticFields*)CircleFunctions_t532702825_il2cpp_TypeInfo_var->static_fields)->get_Pi_0();
     
    		// 实际的工作
    		float L_1 = ___radius0;
    		float L_2 = ___radius0;
    		return ((float)((float)((float)((float)L_0*(float)L_1))*(float)L_2));
    	}
    }
    

      这应该是一个简单的功能,但变成了相当复杂的东西。 IL2CPP 增加了很多开销。现在让我们看一个调用它的函数:

    static void TestStaticFunctionUsingStaticVariable()
    {
    	float area = CircleFunctions.Area(3.0f);
    }
    

      这是来自 IL2CPP 的 C++:

    extern "C"  void TestScript_TestStaticFunctionUsingStaticVariable_m953893846 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
    {
    	// 用于一次性初始化的更多静态布尔值
    	static bool s_Il2CppMethodInitialized;
    	if (!s_Il2CppMethodInitialized)
    	{
    		il2cpp_codegen_initialize_method (TestScript_TestStaticFunctionUsingStaticVariable_m953893846_MetadataUsageId);
    		s_Il2CppMethodInitialized = true;
    	}
    	float V_0 = 0.0f;
    	{
    		// 更多类初始化 (same macro as above)
    		IL2CPP_RUNTIME_CLASS_INIT(CircleFunctions_t532702825_il2cpp_TypeInfo_var);
     
    		// 正真的工作:
    		float L_0 = CircleFunctions_Area_m4188038(NULL /*static, unused*/, (3.0f), /*hidden argument*/NULL);
    		V_0 = L_0;
    		return;
    	}
    }
    

      所以即使是这个函数的调用者也需要为静态变量付出代价。哎哟。

      现在让我们看看如果你避免使用静态变量会发生什么。在这种情况下,很容易只使用 const:

    static class CircleFunctionsConst
    {
    	private const float Pi = 3.14f;
     
    	public static float Area(float radius)
    	{
    		return Pi * radius * radius;
    	}
    }
    

    这是 C++:

    extern "C"  float CircleFunctionsConst_Area_m2838794717 (Il2CppObject * __this /* static, unused */, float ___radius0, const MethodInfo* method)
    {
    	{
    		float L_0 = ___radius0;
    		float L_1 = ___radius0;
    		return ((float)((float)((float)((float)(3.14f)*(float)L_0))*(float)L_1));
    	}
    }
    

    开销没了!只剩下实际的工作。那么调用者呢?

    static void TestStaticFunctionNotUsingStaticVariable()
    {
    	float area = CircleFunctionsConst.Area(3.0f);
    }
    
    extern "C"  void TestScript_TestStaticFunctionNotUsingStaticVariable_m2848480467 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
    {
    	float V_0 = 0.0f;
    	{
    		float L_0 = CircleFunctionsConst_Area_m2838794717(NULL /*static, unused*/, (3.0f), /*hidden argument*/NULL);
    		V_0 = L_0;
    		return;
    	}
    }
    

      所有令人讨厌的开销都完全消失了。

    建议:考虑使用常量和参数而不是静态变量。

    结构初始化

      现在让我们做一个简单的struct:

    struct MyVector3
    {
    	public float X;
    	public float Y;
    	public float Z;
     
    	public MyVector3(float x, float y, float z)
    	{
    		X = x;
    		Y = y;
    		Z = z;
    	}
    }
    

      让我们用默认构造函数初始化它,然后设置它的字段:

    static void TestDefaultStructConstructor()
    {
    	MyVector3 vec = new MyVector3();
    	vec.X = 1;
    	vec.Y = 2;
    	vec.Z = 3;
    }
    

    这是在 C++ 中的样子:

    extern "C"  void TestScript_TestDefaultStructConstructor_m1260349596 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
    {
    	// The static variable overhead is back!
    	static bool s_Il2CppMethodInitialized;
    	if (!s_Il2CppMethodInitialized)
    	{
    		il2cpp_codegen_initialize_method (TestScript_TestDefaultStructConstructor_m1260349596_MetadataUsageId);
    		s_Il2CppMethodInitialized = true;
    	}
     
    	MyVector3_t770449606  V_0;
     
    	// 该struct 默认为全零
    	memset(&V_0, 0, sizeof(V_0));
    	{
    		// Initobj 看起来像这样:
    		//   inline void Initobj(Il2CppClass* type, void* data)
    		//   {
    		//       if (type->valuetype)
    		//           memset(data, 0, type->instance_size - sizeof(Il2CppObject));
    		//       else
    		//           *static_cast<Il2CppObject**>(data) = NULL;
    		//   }
    		// 尽管我们知道这是一个struct,但还有一个“If”的开销
    		// 然后struct被再次清零
    		Initobj (MyVector3_t770449606_il2cpp_TypeInfo_var, (&V_0));
     
    		// 设置字段。 这些“set_*”函数是微不足道的传递。
    		(&V_0)->set_X_0((1.0f));
    		(&V_0)->set_Y_1((2.0f));
    		(&V_0)->set_Z_2((3.0f));
    		return;
    	}
    }
    

      为什么那个静态开销又回来了?我们能摆脱它吗?让我们尝试一个对象初始化器语法:

    static void TestStructInitializer()
    {
    	MyVector3 vec = new MyVector3 { X = 1, Y = 2, Z = 3 };
    }
    
    extern "C"  void TestScript_TestStructInitializer_m3484430381 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
    {
    	// 相同的静态变量开销
    	static bool s_Il2CppMethodInitialized;
    	if (!s_Il2CppMethodInitialized)
    	{
    		il2cpp_codegen_initialize_method (TestScript_TestStructInitializer_m3484430381_MetadataUsageId);
    		s_Il2CppMethodInitialized = true;
    	}
     
    	// 相同的静态变量开销
    	MyVector3_t770449606  V_0;
    	memset(&V_0, 0, sizeof(V_0));
     
    	// 声明另一个结构?为什么?
    	// 同时,将其清除为零。
    	MyVector3_t770449606  V_1;
    	memset(&V_1, 0, sizeof(V_1));
    	{
    		// _再次_将其中一个结构清零
    		Initobj (MyVector3_t770449606_il2cpp_TypeInfo_var, (&V_1));
     
    		// 设置所有字段
    		(&V_1)->set_X_0((1.0f));
    		(&V_1)->set_Y_1((2.0f));
    		(&V_1)->set_Z_2((3.0f));
     
    		// 将一个结构复制到另一个结构
    		MyVector3_t770449606  L_0 = V_1;
    		V_0 = L_0;
    		return;
    	}
    }
    

      字段初始化器增加了更多开销!现在在静态变量初始化代码之上有两个结构、一个副本和三个不必要的清零。

    好的,让我们尝试一个自定义构造函数:

    static void TestCustomStructConstructor()
    {
    	MyVector3 vec = new MyVector3(1, 2, 3);
    }
    
    extern "C"  void TestScript_TestCustomStructConstructor_m3485483736 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
    {
    	MyVector3_t770449606  V_0;
    	memset(&V_0, 0, sizeof(V_0));
    	{
    		MyVector3__ctor_m3460461338((&V_0), (1.0f), (2.0f), (3.0f), /*hidden argument*/NULL);
    		return;
    	}
    }
     
    extern "C"  void MyVector3__ctor_m3460461338 (MyVector3_t770449606 * __this, float ___x0, float ___y1, float ___z2, const MethodInfo* method)
    {
    	{
    		float L_0 = ___x0;
    		__this->set_X_0(L_0);
    		float L_1 = ___y1;
    		__this->set_Y_1(L_1);
    		float L_2 = ___z2;
    		__this->set_Z_2(L_2);
    		return;
    	}
    }
    

      自定义构造函数摆脱了所有静态变量开销和额外的结构。它全部替换为设置字段的函数(构造函数)。不幸的是,即使 C# 语言要求构造函数设置所有字段并且在设置它们之前不访问它们,IL2CPP 仍然在调用构造函数以将结构清零之前生成了 memset 调用。这是我们将获得的最低开销。

    建议:考虑使用自定义构造函数而不是默认构造函数和对象初始值设定项。

    类的开销

    最后,让我们制作上述结构的类版本:

    class MyVector3Class
    {
    	public float X;
    	public float Y;
    	public float Z;
     
    	public MyVector3Class(float x, float y, float z)
    	{
    		X = x;
    		Y = y;
    		Z = z;
    	}
    }
    

    以下是它在 C++ IL2CPP 生成中的样子:

    struct  MyVector3Class_t1350799278  : public Il2CppObject
    {
    public:
    	// System.Single MyVector3Class::X
    	float ___X_0;
    	// System.Single MyVector3Class::Y
    	float ___Y_1;
    	// System.Single MyVector3Class::Z
    	float ___Z_2;
     
    public:
    	inline static int32_t get_offset_of_X_0() { return static_cast<int32_t>(offsetof(MyVector3Class_t1350799278, ___X_0)); }
    	inline float get_X_0() const { return ___X_0; }
    	inline float* get_address_of_X_0() { return &___X_0; }
    	inline void set_X_0(float value)
    	{
    		___X_0 = value;
    	}
     
    	inline static int32_t get_offset_of_Y_1() { return static_cast<int32_t>(offsetof(MyVector3Class_t1350799278, ___Y_1)); }
    	inline float get_Y_1() const { return ___Y_1; }
    	inline float* get_address_of_Y_1() { return &___Y_1; }
    	inline void set_Y_1(float value)
    	{
    		___Y_1 = value;
    	}
     
    	inline static int32_t get_offset_of_Z_2() { return static_cast<int32_t>(offsetof(MyVector3Class_t1350799278, ___Z_2)); }
    	inline float get_Z_2() const { return ___Z_2; }
    	inline float* get_address_of_Z_2() { return &___Z_2; }
    	inline void set_Z_2(float value)
    	{
    		___Z_2 = value;
    	}
    };
    

      有很多样板的“get”和“set”函数,但是这个类大多是我们所期望的。它具有三个浮点字段,并且它派生自 System.Object(又名对象),这是您未显式声明基类时的默认设置。这就是众所周知的 Il2CppObject,所以让我们看一下:

    struct Il2CppObject
    {
        Il2CppClass *klass;
        MonitorData *monitor;
    };
    

      这意味着我们类实例的大小不仅仅是三个浮点变量,还有两个指针的大小。在 64 位平台上,需要额外的 16 字节存储空间。因此,我们的向量实例实际上需要 40 个字节,而不是需要 24 个字节,增加了 66%。这是一个固定的开销,因此对于较大的类来说并不重要,但对于您有很多的较小的类,绝对要注意一些事情。

    为了比较,让我们看一下 struct 版本的 C++:

    struct  MyVector3_t770449606 
    {
    public:
    	// System.Single MyVector3::X
    	float ___X_0;
    	// System.Single MyVector3::Y
    	float ___Y_1;
    	// System.Single MyVector3::Z
    	float ___Z_2;
     
    public:
    	inline static int32_t get_offset_of_X_0() { return static_cast<int32_t>(offsetof(MyVector3_t770449606, ___X_0)); }
    	inline float get_X_0() const { return ___X_0; }
    	inline float* get_address_of_X_0() { return &___X_0; }
    	inline void set_X_0(float value)
    	{
    		___X_0 = value;
    	}
     
    	inline static int32_t get_offset_of_Y_1() { return static_cast<int32_t>(offsetof(MyVector3_t770449606, ___Y_1)); }
    	inline float get_Y_1() const { return ___Y_1; }
    	inline float* get_address_of_Y_1() { return &___Y_1; }
    	inline void set_Y_1(float value)
    	{
    		___Y_1 = value;
    	}
     
    	inline static int32_t get_offset_of_Z_2() { return static_cast<int32_t>(offsetof(MyVector3_t770449606, ___Z_2)); }
    	inline float get_Z_2() const { return ___Z_2; }
    	inline float* get_address_of_Z_2() { return &___Z_2; }
    	inline void set_Z_2(float value)
    	{
    		___Z_2 = value;
    	}
    };
    

    这个版本几乎相同,只是它没有这两个指针的开销。

    建议:当您需要节省每个实例的内存时,考虑使用结构而不是类。

    总结

      IL2CPP 生成的 C++ 代码充满了惊喜。花一些时间检查一下游戏中的已知热点。您可以简单地搜索“MyClass::MyFunction”并轻松找到与您的 C# 代码等效的 C++。您可能会惊讶于您的发现!

    更多相关内容
  • frida-il2cpp 那些想玩Unity il2cpp游戏的人的帮助库。 在Windows上进行了测试,但也可以轻松在Android / iOS上使用。 我认为只有Process.findModuleByName("GameAssembly.dll")!; il2cpp.ts需要更改。 示例类 ...
  • 主要特点将IL2CPP类型定义,元数据和方法指针输出为创建包含在反编译诸如用于使用IL2CPP应用结构和元数据.NET组件垫片的DLL , ,统一资源装载与或管理代理生成与 在IL2CPP应用程序中为所有类型,方法,函数指针和...
  • IL2CPP反汇编程序。 开启Apk 选择apk,它会自动展开和反汇编。 像代码编辑器一样,您可以看到反汇编的结果。 比较Apks 选择两个apk,并显示它们之间的差异。 如果存在差异,则更改背景色。 入门 视窗 下载 打开...
  • Il2CppDumper中文说明请戳Unity il2cpp逆向工程师产品特点完整的DLL还原(代码除外),可用于提取MonoBehaviour和MonoScript 支持ELF,ELF64,Mach-O,PE,NSO和WASM格式支持Unity 5.3-2020 支持生成IDA和Ghidra脚本...
  • -支持IL2CPP- 支持程序集定义文件(Unity 2017.3+) -删除命名空间而无任何冲突 -识别不得更改的Unity相关代码 -重命名类† -重命名方法 -重命名参数 -重命名字段 -重命名属性 -重命名事件 -字符串文字混淆 -添加...
  • il2cpp源码

    2019-04-22 04:12:24
    从Unity3D目录中提取的il2cpp部分的源码,内含il2cpp部分的所有细节源码
  • Frida模块可以在运行时以高度抽象的方式转储,操纵和劫持任何IL2CPP应用程序。 frida-il2cpp-bridge Frida模块可以在运行时以高度抽象的方式转储,操纵和劫持任何IL2CPP应用程序。 导入“ frida-il2cpp-bridge”; ...
  • Game-Guardian-Il2cpp-Script- 这是一个游戏守护者(il2cpp脚本),供您使用,如果您想在不使用mod菜单的情况下使用它。 #要求•转储游戏•获取补偿该怎么办? •在十六进制编辑器或其他任何工具中拖动il2cpp.so并...
  • 宇宙 用于制作通用Unity mod的API,以IL2CPP和Mono为目标。
  • il2cpp 逆向 破解

    2018-08-20 11:59:11
    逆向il2cpp,恢复类名,方法名。分析源代码
  • 1.简介这是Unity Android APP il2cpp热更完美解决方案的Demo( )的说明。和现有的热更解决方案不同的是,他不会约会多余的语言(只是UnityyScript,c#...),对Unityy程序设计和编码没有任何限制。你可以在预先和...
  • il2cpp 「在享受C#开发效率的同时,获得C ++编译器往死里优化的执行速度」测试方法前提条件: Windows 7或更高版本的64位系统; Visual Studio 2017 ,C#和C ++桌面开发环境; 打开il2cpp.sln ; 将test设置为启动...
  • 一般信息: 普通控制台用于调试和显示插件和Mod的输出。 调试模式/控制台用于调试MelonLoader内部。 所有插件都放置在游戏的“安装”文件夹内已创建的“插件”文件夹中。 所有Mod都放置在游戏的Install文件夹内...
  • u3d http besthttp1.7.8.rar 修改IL2CPP版本! u3d http besthttp1.7.8.rar 修改IL2CPP版本!
  • U3D游戏《东方新世界》Il2Cpp破解详细教程U3D游戏《东方新世界》Il2Cpp破解详细教程
  • il2cpp 我有时间学习SCPSL游戏中的megapatch2更改。 从现在开始,旧仓库是只读的。预习 该项目是il2cpp游戏黑客的基础。 您可以进行一些更改以使其可以与任何其他游戏一起使用,而不仅限于SCPSL。更新v0.4:挂接游戏...
  •   Unity 4.6.2 和 5.0 中的新 IL2CPP 脚本后端应该比旧的 Mono 后端快得多。我运行了一些基准测试,但与 Mono 相比,大部分都发现了速度下降。今天的文章展示了我运行的测试,我得到的结果,并想知道为什么 IL2CPP ...

    英文原文:

    https://www.jacksondunstan.com/articles/3001

      Unity 4.6.2 和 5.0 中的新 IL2CPP 脚本后端应该比旧的 Mono 后端快得多。我运行了一些基准测试,但与 Mono 相比,大部分都发现了速度下降。今天的文章展示了我运行的测试,我得到的结果,并想知道为什么 IL2CPP 版本看起来这么慢。亲爱的读者,也许你们中的一个人知道原因。更新:部分原因已被发现。继续阅读以获取更新的结果。

      今天测试中的所有基准测试均取自 The Computer Language Benchmarks Game。您可以从他们的网站下载 C# 和许多其他语言的源代码。我检查了每个基准测试,并采用了最新的具有在 Unity 中运行的 C# 版本的每个基准测试。这是我最终完成的测试列表:

    • binarytrees
    • chameneosredux
    • fannkuchredux
    • fasta-2
    • fastaredux
    • knucleotide-3
    • mandlebrot-3
    • nbody-3
    • regexdna-6
    • revcomp-3
    • spectralnorm-2

      然后我尽可能地修改了每个版本。这包括为每个创建一个公共 Main 方法,以便可以从我的基准测试运行程序中调用它们,并禁用对控制台的任何写入以支持对 MemoryStream 的写入。随意下载生成的测试。

      然后,我创建了一个基准运行程序脚本,在后台线程中运行测试,以免阻塞主 UI 线程。主线程只显示测试结果报告。测试使用 benchmarksgame/nanobench/makefiles/u32.ini 配置文件中指定的程序参数反复运行。该脚本只是简单地附加到一个空的 Unity 场景中的主摄像机游戏对象。

    这是基准运行程序源代码:

    using System;
    using System.Diagnostics;
    using System.Text;
    using System.Threading;
     
    using UnityEngine;
     
    public class TestScript : MonoBehaviour
    {
    	private class Test
    	{
    		public string Name;
    		public Action<string[]> Main;
    		public string[] Args;
    		public long TotalTime;
     
    		public Test(string name, Action<string[]> main, string[] args)
    		{
    			Name = name;
    			Main = main;
    			Args = args;
    		}
    	}
     
    	private static readonly Test[] tests = new Test[]{
    		new Test("BinaryTrees", BinaryTrees.Main, new string[]{"12"}),
    		new Test("chameneosredux", chameneosredux.Main, new string[]{"60000"}),
    		new Test("FannkuchRedux", FannkuchRedux.Main, new string[]{"10"}),
    		new Test("Fasta", Fasta.Main, new string[]{"250000"}),
    		new Test("FastaRedux", FastaRedux.Main, new string[]{"250000"}),
    		new Test("knucleotide", knucleotide.Main, new string[]{"250000"}),
    		new Test("MandelBrot", MandelBrot.Main, new string[]{"1000"}),
    		new Test("NBody", NBody.Main, new string[]{"500000"}),
    		new Test("regexdna", regexdna.Main, new string[]{"50000"}),
    		new Test("revcomp", revcomp.Main, new string[]{"250000"}),
    		new Test("SpectralNorm", SpectralNorm.Main, new string[]{"500"})
    	};
     
    	private Rect reportDrawArea;
    	private string report;
    	private object reportMutex;
    	private Thread reportThread;
     
    	void Awake()
    	{
    		reportDrawArea = new Rect(0, 0, Screen.width, Screen.height);
    		reportMutex = new object();
     
    		reportThread = new Thread(TestThread);
    		reportThread.Start();
    	}
     
    	void TestThread()
    	{
    		var reportBuilder = new StringBuilder();
    		var numRuns = 0;
    		var stopwatch = new Stopwatch();
     
    		while (true)
    		{
    			numRuns++;
    			reportBuilder.Length = 0;
    			reportBuilder.Append("Num Runs,");
    			reportBuilder.Append(numRuns);
    			reportBuilder.Append('\n');
    			reportBuilder.Append("Test,Time\n");
    			foreach (var test in tests)
    			{
    				stopwatch.Reset();
    				stopwatch.Start();
    				test.Main(test.Args);
    				var elapsedMillis = stopwatch.ElapsedMilliseconds;
    				test.TotalTime += elapsedMillis;
     
    				var averageMillis = test.TotalTime / (float)numRuns;
    				reportBuilder.Append(test.Name);
    				reportBuilder.Append(',');
    				reportBuilder.Append(averageMillis);
    				reportBuilder.Append('\n');
    			}
     
    			lock (reportMutex)
    			{
    				report = reportBuilder.ToString();
    				UnityEngine.Debug.Log(report);
    			}
    		}
    	}
     
    	void Update()
    	{
    		if (Input.GetMouseButtonDown(0))
    		{
    			reportThread.Abort();
    		}
    	}
     
    	void OnApplicationQuit()
    	{
    		reportThread.Abort();
    	}
     
    	void OnGUI()
    	{
    		lock (reportMutex)
    		{
    			if (report != null)
    			{
    				GUI.TextArea(reportDrawArea, report);
    			}
    		}
    	}
    }
    

      然后,我使用针对 ARMv7 的 Mono 和 IL2CPP 构建了适用于 iOS 的应用程序,而不是使用“开发构建”。这是我在运行 iOS 8.2 的 iPad 3 上得到的结果。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
      IL2CPP 完成任何大于 100% 的结果比 Mono 需要更长的时间。任何低于 100% 的速度都比 Mono 快。

      如您所见,与 Mono 相比,IL2CPP 中除了两个测试之外的所有测试都较慢。这两个测试是 BinaryTrees 快 19%,而 revcomp 快得无法测量。

      其他测试都显示放缓。这些范围从非常轻微到极端。在非常小的方面,knucleotide 和正则表达式仅慢了大约 20%。 MandelBrot、NBody 和 SpectralNorm 次之,慢了大约 80%。然后事情变得非常糟糕。 Fasta 和 FannkuchRedux 慢 400-500%,FastaRedux 慢 628%。到目前为止,速度之冠以惊人的 10580% 的速度进入 chameneosredux。

      正如您可以在“基准游戏”网站上反复阅读的那样,这些内容应该持保留态度。它们显然不是在 Unity 中运行的真实应用程序或游戏,差异很容易蔓延。再说一次,这 11 项测试应该相当代表常见的编程任务。

      我希望做这个测试并写一篇文章来展示 Unity 4.6.2 和 5.0 中新的 IL2CPP 脚本后端的速度有多快。不幸的是,我看到的结果完全相反。我也不知道为什么会这样。如果你这样做,请在评论中告诉我。

    更新

      感谢评论中的 Ralph Hauwert,已经发现了放缓的部分原因。事实证明,即使在 Unity 构建设置中没有选中“开发构建”,您也需要显式设置 Xcode 以进行发布构建。为此,请单击项目名称,选择“Edit Scheme…”,然后将“Build Configuration”更改为“Release”。顺便说一句,我在 Mac OS X 10.10.2 上使用 Xcode 6.2 构建了测试。

    以下是更新的结果:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
      正如预期的那样,发布模式下的 IL2CPP 确实比调试模式下快得多。 chameneosredux、fasta 和 fastaredux 仍然非常慢,比 Mono 慢 3 到 64 倍,但其余的要快得多。 knucleotide 的速度几乎与 Mono 完全相同。 FannkuchRedux、MandleBrot 和 regexdna 只慢了大约 10-15%。

      但是发布模式也有一些亮点。使用 IL2CPP 的 BinaryTrees、NBody 和 SpectralNorm 的速度都大约是 Mono 的两倍。 revcomp 仍然非常快,而在 Mono 中,它偶尔会记录几毫秒的时间。

      鉴于切换到 IL2CPP 时性能可能会从非常慢到非常快不等,因此您最终看到的性能将在很大程度上取决于您的应用程序具有的代码类型。不要简单地假设您的应用程序会看到全面的加速。

      如果您在实际代码中看到显着的加速或减速,尤其是特定类型的代码,我真的很想在评论中听到它。

    展开全文
  • il2cpp_IL2CPP内部介绍

    千次阅读 2020-09-12 19:55:40
    il2cppAlmost a year ago now, we started to talk about the future of scripting in ... The new IL2CPP scripting backend promised to bring a highly-performant, highly-portable virtual machine to Unity...

    il2cpp

    Almost a year ago now, we started to talk about the future of scripting in Unity. The new IL2CPP scripting backend promised to bring a highly-performant, highly-portable virtual machine to Unity. In January, we shipped our first platform using IL2CPP, iOS 64-bit. The Unity 5 release brought another platform, WebGL. Thanks to the input from our tremendous community of users, we have shipped many patch release updates for IL2CPP, steadily improving its compiler and runtime.

    大约一年前的现在,我们开始谈论Unity中脚本的未来 。 新的IL2CPP脚本后端承诺将为Unity带来高性能,高度可移植的虚拟机。 1月,我们使用IL2CPP( iOS 64位)发布了第一个平台。 Unity 5版本带来了另一个平台WebGL 。 感谢广大用户的反馈,我们为IL2CPP发行了许多补丁程序更新 ,从而稳步改善了其编译器和运行时。

    We have no plans to stop improving IL2CPP, but we thought it might be a good idea to take a step back and tell you a little bit about how IL2CPP works from the inside out. Over the next few months, we’re planning to write about the following topics (and maybe others) in this IL2CPP Internals series of posts:

    我们没有停止改善IL2CPP的计划,但是我们认为最好退一步,向您介绍一下IL2CPP从内而外的工作原理。 在接下来的几个月中,我们计划在此IL2CPP Internals系列文章中撰写以下主题(可能还有其他主题):

    1. A tour of generated code

      生成代码之旅

    2. Debugging tips for generated code

      生成代码的调试技巧

    3. Method calls (normal methods, virtual methods, etc.)

      方法调用 (普通方法,虚拟方法等)

    4. Generic sharing implementation

      通用共享实施

    5. P/invoke wrappers for types and methods

      P /调用包装器的类型和方法

    6. Garbage collection integration

      垃圾收集整合

    7. Testing frameworks and usage

      测试框架和用法

    In order to make this series of posts possible, we’re going to discuss some details about the IL2CPP implementation that will surely change in the future. Hopefully we can still provide some useful and interesting information.

    为了使这一系列文章成为可能,我们将讨论有关IL2CPP实现的一些细节,这些细节将来肯定会改变。 希望我们仍然可以提供一些有用和有趣的信息。

    What is IL2CPP?

    什么是IL2CPP?

    The technology that we refer to as IL2CPP has two distinct parts.

    我们称为IL2CPP的技术有两个不同的部分。

      The AOT compiler translates Intermediate Language (IL), the low-level output from .NET compilers, to C++ source code. The runtime library provides services and abstractions like a garbage collector, platform-independent access to threads and files, and implementations of internal calls (native code which modifies managed data structures directly).

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

      The AOT compiler

      AOT编译器

      The IL2CPP AOT compiler is named il2cpp.exe. On Windows you can find it in the Editor\Data\il2cpp directory. On OSX it is in the Contents/Frameworks/il2cpp/build directory in the Unity installation. The il2cpp.exe utility is a managed executable, written entirely in C#. We compile it with both .NET and Mono compilers during our development of IL2CPP.

      IL2CPP AOT编译器名为il2cpp.exe。 在Windows上,您可以在Editor \ Data \ il2cpp目录中找到它。 在OSX上,它位于Unity安装的Contents / Frameworks / il2cpp / build目录中。 il2cpp.exe实用程序是一个托管可执行文件,完全用C#编写。 在我们开发IL2CPP时,我们使用.NET和Mono编译器对其进行编译。

      The il2cpp.exe utility accepts managed assemblies compiled with the Mono compiler that ships with Unity and generates C++ code which we pass on to a platform-specific C++ compiler.

      il2cpp.exe实用程序接受使用Unity附带的Mono编译器编译的托管程序集,并生成C ++代码,然后将其传递给特定于平台的C ++编译器。

      You can think about the IL2CPP toolchain like this:

      您可以这样考虑一下IL2CPP工具链:

      il2cpp toolchain smaller

      The runtime library

      运行时库

      The other part of the IL2CPP technology is a runtime library to support the virtual machine. We have implemented this library using almost entirely C++ code (it has a little bit of platform-specific assembly code, but let’s keep that between the two of us). We call the runtime library libil2cpp, and it is shipped as a static library linked into the player executable. One of the key benefits of the IL2CPP technology is this simple and portable runtime library.

      IL2CPP技术的另一部分是运行时库,用于支持虚拟机。 我们几乎完全使用C ++代码实现了该库(它有一些特定于平台的汇编代码,但让我们将其保留在我们两个人之间)。 我们将运行时库称为libil2cpp,它作为链接到播放器可执行文件的静态库提供。 IL2CPP技术的主要优势之一就是这个简单且可移植的运行时库。

      You can find some clues about how the libil2cpp code is organized by looking at the header files for libil2cpp we ship with Unity (you’ll find them in the  Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include directory on Windows, or the Contents/Frameworks/il2cpp/libil2cpp directory on OSX). For example, the interface between the C++ code generated by il2cpp.exe and the libil2cpp runtime is located in the codegen/il2cpp-codegen.h header file.

      通过查看我们随Unity一起提供的libil2cpp的头文件,您可以找到有关libil2cpp代码组织方式的一些线索(您可以在Windows的Editor \ Data \ PlaybackEngines \ webglsupport \ BuildTools \ Libraries \ libil2cpp \ include目录中找到它们) ,或OSX上的Contents / Frameworks / il2cpp / libil2cpp目录)。 例如,il2cpp.exe生成的C ++代码与libil2cpp运行时之间的接口位于codegen / il2cpp-codegen.h头文件中。

      One key part of the runtime is the garbage collector. We’re shipping Unity 5 with libgc, the Boehm-Demers-Weiser garbage collector. However, libil2cpp has been designed to allow us to use other garbage collectors. For example, we are researching an integration of the Microsoft GC which was open-sourced as part of the CoreCLR. We’ll have more to say about this in our post about garbage collector integration later in the series.

      运行时的关键部分是垃圾收集器。 我们将使用Boehm-Demers-Weiser垃圾收集器libgc交付 Unity 5。 但是,libil2cpp旨在允许我们使用其他垃圾收集器。 例如,我们正在研究Microsoft GC的集成,该集成是CoreCLR的一部分开源。 在本系列后面的有关垃圾收集器集成的文章中,我们将对此进行更多说明。

      How is il2cpp.exe executed?

      il2cpp.exe如何执行?

      Let’s take a look at an example. I’ll be using Unity 5.0.1 on Windows, and I’ll start with a new, empty project. So that we have at least one user script to convert, I’ll add this simple MonoBehaviour component to the Main Camera game object:

      让我们看一个例子。 我将在Windows上使用Unity 5.0.1,并从一个新的空项目开始。 为了至少有一个要转换的用户脚本,我将这个简单的MonoBehaviour组件添加到Main Camera游戏对象中:

      1

      2
      3
      4
      5
      6
      7
      using UnityEngine;
      public class HelloWorld : MonoBehaviour {
      void Start () {
      Debug.Log("Hello, IL2CPP!");
      }
      }

      1

      2
      3
      4
      5
      6
      7
      using UnityEngine ;
      public class HelloWorld : MonoBehaviour {
      void Start ( ) {
      Debug . Log ( "Hello, IL2CPP!" ) ;
      }
      }

      When I build for the WebGL platform, I can use Process Explorer to see the command line Unity used to run il2cpp.exe:

      当我为WebGL平台构建时,可以使用Process Explorer查看用于运行il2cpp.exe的命令行Unity:

      1

      "C:\Program Files\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe" "C:\Program Files\Unity\Editor\Data\il2cpp/il2cpp.exe" --copy-level=None --enable-generic-sharing --enable-unity-event-support --output-format=Compact --extra-types.file="C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\Assembly-CSharp.dll" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\UnityEngine.UI.dll" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"

      1

      "C:\Program Files\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe" "C:\Program Files\Unity\Editor\Data\il2cpp/il2cpp.exe" -- copy - level = None -- enable - generic - sharing -- enable - unity - event - support -- output - format = Compact -- extra - types . file = "C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\Assembly-CSharp.dll" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\UnityEngine.UI.dll" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"

      That command line is pretty long and horrible, so let’s unpack it. First, Unity is running this executable:

      该命令行很长而且很可怕,所以让我们打开它的包装。 首先,Unity运行此可执行文件:

      1

      "C:\Program Files\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe"

      1

      "C:\Program Files\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe"

      The next argument on the command line is the il2cpp.exe utility itself.

      命令行上的下一个参数是il2cpp.exe实用程序本身。

      1

      "C:\Program Files\Unity\Editor\Data\il2cpp/il2cpp.exe"

      1

      "C:\Program Files\Unity\Editor\Data\il2cpp/il2cpp.exe"

      The remaining command line arguments are passed to il2cpp.exe, not mono.exe. Let’s look at them. First, Unity passes five flags to il2cpp.exe:

      其余命令行参数将传递给il2cpp.exe,而不是mono.exe。 让我们看看它们。 首先,Unity将五个标志传递给il2cpp.exe:

      • –copy-level=None

        –copy-level =无

        • –enable-generic-sharing

          –启用通用共享

          • –enable-unity-event-support

            –enable-unity-event-support

            • –output-format=Compact

              –output-format =紧凑

              • –extra-types.file=”C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt”

                –extra-types.file =” C:\ Program Files \ Unity \ Editor \ Data \ il2cpp \ il2cpp_default_extra_types.txt”

                It is important to note that these command line arguments can and will be changed in later releases. We’re not at a point yet where we have a stable and supported set of command line arguments for il2cpp.exe.

                重要的是要注意,这些命令行参数可以并且将在以后的版本中更改。 我们还没有为il2cpp.exe提供一套稳定且受支持的命令行参数。

                Finally, we have a list of two files and one directory on the command line:

                最后,在命令行上有两个文件和一个目录的列表:

                  The il2cpp.exe utility accepts a list of all of the IL assemblies it should convert. In this case they are the assembly containing my simple MonoBehaviour, Assembly-CSharp.dll, and the GUI assembly, UnityEngine.UI.dll. Note that there are a few conspicuously missing assembles here. Clearly, my script references UnityEngine.dll, and that references at least mscorlib.dll, and maybe other assemblies. Where are they? Actually, il2cpp.exe resolves those assemblies internally. They can be mentioned on the command line, but they are not necessary. Unity only needs to mention the root assemblies (those which are not referenced by any other assembly) explicitly.

                  il2cpp.exe实用程序接受应转换的所有IL程序集的列表。 在这种情况下,它们是包含我简单的MonoBehaviour的程序集Assembly-CSharp.dll和GUI程序集UnityEngine.UI.dll。 请注意,这里有一些明显缺少的程序集。 显然,我的脚本引用了UnityEngine.dll,并且至少引用了mscorlib.dll,也许还引用了其他程序集。 他们在哪? 实际上,il2cpp.exe在内部解析这些程序集。 可以在命令行上提及它们,但不是必需的。 Unity只需要明确提及根程序集(其他程序集未引用的根程序集)。

                  The last argument on the il2cpp.exe command line is the directory where the output C++ files should be created. If you are curious, have a look at the generated files in that directory, they will be the subject of the next post in this series. Before you do though, you might want to choose the “Development Player” option in the WebGL build settings. That will remove the –output-format=Compact command line argument and give you better type and method names in the generated C++ code.

                  il2cpp.exe命令行上的最后一个参数是应在其中创建输出C ++文件的目录。 如果您感到好奇,请查看该目录中生成的文件,它们将是本系列下一篇文章的主题。 但是,在进行此操作之前,您可能需要在WebGL构建设置中选择“开发播放器”选项。 这将删除–output-format = Compact命令行参数,并在生成的C ++代码中为您提供更好的类型和方法名称。

                  Try changing various options in the WebGL or iOS Player Settings. You should be able to see different command line options passed to il2cpp.exe to enable different code generation steps. For example, changing the “Enable Exceptions” setting in the WebGL Player Settings to a value of “Full” adds the –emit-null-checks, –enable-stacktrace, and  –enable-array-bounds-check arguments to the il2cpp.exe command line.

                  尝试更改WebGL或iOS Player设置中的各种选项。 您应该能够看到传递给il2cpp.exe的不同命令行选项,以启用不同的代码生成步骤。 例如,将WebGL Player设置中的“ Enable Exceptions”设置更改为“ Full”值,将–emit-null-checks,–enable-stacktrace和–enable-array-bounds-check参数添加到il2cpp。 exe命令行。

                  What does IL2CPP not do?

                  IL2CPP不做什么?

                  I’d like to point out one of the challenges that we did not take on with IL2CPP, and we could not be happier that we ignored it. We did not attempt to re-write the C# standard library with IL2CPP. When you build a Unity project which uses the IL2CPP scripting backend, all of the C# standard library code in mscorlib.dll, System.dll, etc. is the exact same code used for the Mono scripting backend.

                  我想指出我们在IL2CPP上没有遇到的挑战之一,并且我们对忽略它感到高兴。 我们没有尝试用IL2CPP重写C#标准库。 当您构建使用IL2CPP脚本后端的Unity项目时,mscorlib.dll,System.dll等中的所有C#标准库代码与用于Mono脚本后端的代码完全相同。

                  We rely on C# standard library code that is already well-known by users and well-tested in Unity projects. So when we investigate a bug related to IL2CPP, we can be fairly confident that the bug is in either the AOT compiler or the runtime library, and nowhere else.

                  我们依赖于C#标准库代码,该代码已经为用户所熟知,并在Unity项目中经过了良好的测试。 因此,当我们调查与IL2CPP有关的错误时,我们可以完全确定该错误在AOT编译器或运行时库中,而没有其他地方。

                  How we develop, test, and ship IL2CPP

                  我们如何开发,测试和运送IL2CPP

                  Since the initial public release of IL2CPP at version 4.6.1p5 in January, we’ve shipped 6 full releases and 7 patch releases (across versions 4.6 and 5.0 of Unity). We have corrected more than 100 bugs mentioned in the release notes.

                  自从一月份的IL2CPP首次公开发行4.6.1p5版本以来,我们已经发布了6个完整版本和7个补丁版本(Unity 4.6和5.0版本)。 我们已经纠正了发行说明中提到的100多个错误。

                  In order to make this continuous improvement happen, we develop against only one version of the IL2CPP code internally, which sits on the bleeding edge of the trunk branch in Unity used to ship alpha and beta releases. Just before each release, we port the IL2CPP changes to the specific release branch, run our tests, and verify all of the bugs we fixed are corrected in that version. Our QA and Sustained Engineering teams have done incredible work to make delivery at this rate possible. This means that our users are never more than about one week away from the latest fixes for IL2CPP bugs.

                  为了使这种持续改进得以实现,我们仅在内部针对IL2CPP代码的一个版本进行开发,该版本位于Unity中用于发布alpha和beta版本的主干分支的前沿。 就在每个发行版之前,我们将IL2CPP更改移植到特定的发行分支,运行我们的测试,并验证在该版本中纠正的所有错误。 我们的质量保证和持续工程团队做了出色的工作,以这种速度交付产品。 这意味着我们的用户离IL2CPP错误的最新修补程序只有不到一周的时间。

                  Our user community has proven invaluable by submitting many high quality bug reports. We appreciate all the feedback from our users to help continually improve IL2CPP, and we look forward to more of it.

                  通过提交许多高质量的错误报告,我们的用户社区被证明是无价的。 我们非常感谢用户提供的所有反馈意见,以帮助他们不断改善IL2CPP,我们期待着更多的反馈。

                  The development team working on IL2CPP has a strong test-first mentality. We often employee Test Driven Design practices, and seldom merge a pull request without good tests. This strategy works well for a technology like IL2CPP, where we have clear inputs and outputs. It means that the vast majority of the bugs we see are not unexpected behavior, but rather unexpected cases (e.g. it is possible to use an 64-bit IntPtr as a 32-bit array index, causing clang to fail with a C++ compiler error, and real code actually does this!). That difference allows us to fix bugs quickly with a high degree of confidence.

                  IL2CPP的开发团队具有强烈的测试优先心态。 我们经常采用“测试驱动设计”实践,并且很少合并没有良好测试的拉取请求。 这种策略对于像IL2CPP这样的技术非常有效,在该技术中我们有明确的输入和输出。 这意味着我们看到的绝大多数错误不是意外行为,而是意外情况(例如,可以将64位IntPtr用作32位数组索引,从而导致Clang失败并出现C ++编译器错误,而实际的代码实际上就是这样做的!)。 这种差异使我们能够高度自信地快速修复错误。

                  With the help of our community, we’re working hard to make IL2CPP as stable and fast as possible. By the way, if any of this excites you, we’re hiring (just sayin’).

                  在我们社区的帮助下,我们正在努力使IL2CPP尽可能稳定和快速。 顺便说一句,如果其中任何一个使您兴奋, 我们正在招聘 (只是说)。

                  More to come

                  还有更多

                  I fear that I’ve spent too much time here teasing future blog posts. We have a lot to say, and it simply won’t all fit in one post. Next time, we’ll dig into the code generated by il2cpp.exe to see how your project actually looks to the C++ compiler.

                  我担心我在这里花了太多时间来逗弄未来的博客文章。 我们有很多话要说,而且根本不可能全部适合一篇文章。 下次,我们将深入研究il2cpp.exe生成的代码,以查看您的项目对C ++编译器的实际外观。

                  翻译自: https://blogs.unity3d.com/2015/05/06/an-introduction-to-ilcpp-internals/

                  il2cpp

                  展开全文
                • Il2CppAssemblyUnho​​llower 从的输出生成 Managed->IL2CPP 代理程序集的工具。 这允许从受管域使用 IL2CPP 域和其中的对象。 这包括泛型类型和方法、数组和新对象创建。 有些东西可能会被严重破坏。用法构建或...
                • il2cppThis is the fourth blog post in the IL2CPP Internals series. In this post, we will look at how il2cpp.exe generates C++ code for method calls in managed code. Specifically, we will investigate s...

                  il2cpp

                  This is the fourth blog post in the IL2CPP Internals series. In this post, we will look at how il2cpp.exe generates C++ code for method calls in managed code. Specifically, we will investigate six different types of method calls:

                  这是IL2CPP Internals系列中的第四篇博客文章。 在本文中,我们将研究il2cpp.exe如何为托管代码中的方法调用生成C ++代码。 具体来说,我们将研究六种不同类型的方法调用:

                    In each case, we will focus on what the generated C++ code is doing and, specifically, on how much those instructions will cost.

                    在每种情况下,我们都将专注于生成的C ++代码的功能,尤其是这些指令的成本。

                    As with all of the posts in this series, we will be exploring code that is subject to change and, in fact, is likely to change in a newer version of Unity. However, the concepts should remain the same. Please take everything discussed in this series as implementation details. We like to expose and discuss details like this when it is possible though!

                    与本系列的所有文章一样,我们将探索可能会更改的代码,实际上,在新版本的Unity中,代码可能会更改。 但是,概念应保持不变。 请把本系列中讨论的所有内容作为实现细节。 我们希望尽可能地公开和讨论这样的细节!

                    Setup

                    建立

                    I’ll be using Unity version 5.0.1p4. I’ll run the editor on Windows, and build for the WebGL platform. I’m building with the “Development Player” option enabled, and the “Enable Exceptions” option set to a value of “Full”.

                    我将使用Unity版本5.0.1p4。 我将在Windows上运行该编辑器,并为WebGL平台构建。 我正在启用“ Development Player”选项的情况下进行构建,并且“ Enable Exceptions”选项设置为“ Full”。

                    I’ll build with a single script file, modified from the last post so that we can see the different types of method calls. The script starts with an interface and class definition:

                    我将使用一个脚本文件进行构建,该脚本文件是在上一篇文章中进行了修改的,以便我们可以看到不同类型的方法调用。 该脚本以接口和类定义开头:

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    interface Interface {
                    int MethodOnInterface(string question);
                    }
                    class Important : Interface {
                    public int Method(string question) { return 42; }
                    public int MethodOnInterface(string question) { return 42; }
                    public static int StaticMethod(string question) { return 42; }
                    }

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    interface Interface {
                    int MethodOnInterface ( string question ) ;
                    }
                    class Important : Interface {
                    public int Method ( string question ) { return 42 ; }
                    public int MethodOnInterface ( string question ) { return 42 ; }
                    public static int StaticMethod ( string question ) { return 42 ; }
                    }

                    Then we have a constant field and a delegate type, both used later in the code:

                    然后,我们有一个常量字段和一个委托类型,它们都将在后面的代码中使用:

                    1

                    2
                    3
                    private const string question = "What is the answer to the ultimate question of life, the universe, and everything?";
                    private delegate int ImportantMethodDelegate(string question);

                    1

                    2
                    3
                    private const string question = "What is the answer to the ultimate question of life, the universe, and everything?" ;
                    private delegate int ImportantMethodDelegate ( string question ) ;

                    Finally, these are the methods we are interested in exploring (plus the obligatory Start method, which has no content here):

                    最后,以下是我们感兴趣的方法(以及强制性的Start方法,此处无内容):

                    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
                    private void CallDirectly() {
                    var important = ImportantFactory();
                    important.Method(question);
                    }
                    private void CallStaticMethodDirectly() {
                    Important.StaticMethod(question);
                    }
                    private void CallViaDelegate() {
                    var important = ImportantFactory();
                    ImportantMethodDelegate indirect = important.Method;
                    indirect(question);
                    }
                    private void CallViaRuntimeDelegate() {
                    var important = ImportantFactory();
                    var runtimeDelegate = Delegate.CreateDelegate(typeof (ImportantMethodDelegate), important, "Method");
                    runtimeDelegate.DynamicInvoke(question);
                    }
                    private void CallViaInterface() {
                    Interface importantViaInterface = new Important();
                    importantViaInterface.MethodOnInterface(question);
                    }
                    private void CallViaReflection() {
                    var important = ImportantFactory();
                    var methodInfo = typeof(Important).GetMethod("Method");
                    methodInfo.Invoke(important, new object[] {question});
                    }
                    private static Important ImportantFactory() {
                    var important = new Important();
                    return important;
                    }
                    void Start () {}

                    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
                    private void CallDirectly ( ) {
                    var important = ImportantFactory ( ) ;
                    important . Method ( question ) ;
                    }
                    private void CallStaticMethodDirectly ( ) {
                    Important . StaticMethod ( question ) ;
                    }
                    private void CallViaDelegate ( ) {
                    var important = ImportantFactory ( ) ;
                    ImportantMethodDelegate indirect = important . Method ;
                    indirect ( question ) ;
                    }
                    private void CallViaRuntimeDelegate ( ) {
                    var important = ImportantFactory ( ) ;
                    var runtimeDelegate = Delegate . CreateDelegate ( typeof ( ImportantMethodDelegate ) , important , "Method" ) ;
                    runtimeDelegate . DynamicInvoke ( question ) ;
                    }
                    private void CallViaInterface ( ) {
                    Interface importantViaInterface = new Important ( ) ;
                    importantViaInterface . MethodOnInterface ( question ) ;
                    }
                    private void CallViaReflection ( ) {
                    var important = ImportantFactory ( ) ;
                    var methodInfo = typeof ( Important ) . GetMethod ( "Method" ) ;
                    methodInfo . Invoke ( important , new object [ ] { question } ) ;
                    }
                    private static Important ImportantFactory ( ) {
                    var important = new Important ( ) ;
                    return important ;
                    }
                    void Start ( ) { }

                    With all that defined, let’s get started. Recall that the generated C++ code will be located in the Temp\StagingArea\Data\il2cppOutput directory in the project (as long as the editor remains open). And don’t forget to generate Ctags on the generated code, to help navigate it.

                    完成所有定义后,我们开始吧。 回想一下,生成的C ++代码将位于项目中的Temp \ StagingArea \ Data \ il2cppOutput目录中(只要编辑器保持打开状态)。 并且不要忘记在生成的代码上生成Ctags ,以帮助进行导航。

                    Calling a method directly

                    直接调用方法

                    The simplest (and fastest, as we will see) way to call a method, is to call it directly. Here is the generated code for the CallDirectly method:

                    调用方法最简单(也是最快,我们将看到)的方法是直接调用它。 这是CallDirectly方法的生成代码:

                    1

                    2
                    3
                    4
                    5
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
                    V_0 = L_0;
                    Important_t1 * L_1 = V_0;
                    NullCheck(L_1);
                    Important_Method_m1(L_1, (String_t*) &_stringLiteral1, /*hidden argument*/&Important_Method_m1_MethodInfo);

                    1

                    2
                    3
                    4
                    5
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
                    V_0 = L_0 ;
                    Important_t1 * L_1 = V_0 ;
                    NullCheck ( L_1 ) ;
                    Important_Method_m1 ( L_1 , ( String_t * ) &_stringLiteral1 , /*hidden argument*/ &Important_Method_m1_MethodInfo ) ;

                    The last line is the actual method call. Note that it does nothing special, just calls a free function defined in the C++ code. Recall from the earlier post about generated code, that il2cpp.exe generates all methods as C++ free functions. The IL2CPP scripting backend does not use C++ member functions or virtual functions for any generated code. It follows then that calling a static method directory should be similar. Here is the generated code from the  CallStaticMethodDirectly method:

                    最后一行是实际的方法调用。 请注意,它没有什么特别的,只是调用C ++代码中定义的自由函数。 回想一下先前关于生成代码的文章 ,il2cpp.exe将所有方法生成为C ++自由函数。 IL2CPP脚本编写后端不对任何生成的代码使用C ++成员函数或虚函数。 随之而来的是,调用静态方法目录应该相似。 这是从CallStaticMethodDirectly方法生成的代码:

                    1

                    Important_StaticMethod_m3(NULL /*static, unused*/, (String_t*) &_stringLiteral1, /*hidden argument*/&Important_StaticMethod_m3_MethodInfo);

                    1

                    Important_StaticMethod_m3 ( NULL /*static, unused*/ , ( String_t * ) &_stringLiteral1 , /*hidden argument*/ &Important_StaticMethod_m3_MethodInfo ) ;

                    We could say there is less overhead calling a static method, since we don’t need to create and initialize an object instance. However, the method call itself is exactly the same, a call to a C++ free function. The only difference here is that the first argument is always passed with a value of NULL.

                    我们可以说调用静态方法的开销较小,因为我们不需要创建和初始化对象实例。 但是,方法调用本身完全相同,即对C ++自由函数的调用。 唯一的区别是,第一个参数始终以NULL值传递。

                    Since the difference between calls to static and instance methods is so minimal, we’ll focus on instance methods only for the rest of this post, but the information applies to static methods as well.

                    由于对静态方法和实例方法的调用之间的差异非常小,因此在本文的其余部分中,我们将仅关注实例方法,但该信息也适用于静态方法。

                    Calling a method via a compile-time delegate

                    通过编译时委托调用方法

                    What happens with a slightly more exotic method call, like an indirect call via delegate? We’ll first look at a what I’ll call a compile-time delegate, meaning that we know at compile time which method will be called on which object instance. The code for this type of call is in the CallViaDelegate method. It looks like this in the generated code:

                    稍微有点奇怪的方法调用(例如通过委托进行的间接调用)会发生什么? 我们首先来看一个所谓的编译时委托,这意味着我们在编译时知道将在哪个对象实例上调用哪个方法。 此类调用的代码在CallViaDelegate方法中。 在生成的代码中看起来像这样:

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    // Get the object instance used to call the method.
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
                    V_0 = L_0;
                    Important_t1 * L_1 = V_0;
                    // Create the delegate.
                    IntPtr_t L_2 = { &Important_Method_m1_MethodInfo };
                    ImportantMethodDelegate_t4 * L_3 = (ImportantMethodDelegate_t4 *)il2cpp_codegen_object_new (InitializedTypeInfo(&ImportantMethodDelegate_t4_il2cpp_TypeInfo));
                    ImportantMethodDelegate__ctor_m4(L_3, L_1, L_2, /*hidden argument*/&ImportantMethodDelegate__ctor_m4_MethodInfo);
                    V_1 = L_3;
                    ImportantMethodDelegate_t4 * L_4 = V_1;
                    // Call the method
                    NullCheck(L_4);
                    VirtFuncInvoker1< int32_t, String_t* >::Invoke(&ImportantMethodDelegate_Invoke_m5_MethodInfo, L_4, (String_t*) &_stringLiteral1);

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    // Get the object instance used to call the method.
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
                    V_0 = L_0 ;
                    Important_t1 * L_1 = V_0 ;
                    // Create the delegate.
                    IntPtr_t L_2 = { &Important_Method_m1_MethodInfo } ;
                    ImportantMethodDelegate_t4 * L_3 = ( ImportantMethodDelegate_t4 * ) il2cpp_codegen_object_new ( InitializedTypeInfo ( &ImportantMethodDelegate_t4_il2cpp_TypeInfo ) ) ;
                    ImportantMethodDelegate__ctor_m4 ( L_3 , L_1 , L_2 , /*hidden argument*/ &ImportantMethodDelegate__ctor_m4_MethodInfo ) ;
                    V_1 = L_3 ;
                    ImportantMethodDelegate_t4 * L_4 = V_1 ;
                    // Call the method
                    NullCheck ( L_4 ) ;
                    VirtFuncInvoker1 < int32_t , String_t * > :: Invoke ( &ImportantMethodDelegate_Invoke_m5_MethodInfo , L_4 , ( String_t * ) &_stringLiteral1 ) ;

                    I’ve added a few comments to indicate the different parts of the generated code.

                    我添加了一些注释以指示所生成代码的不同部分。

                    Note that the actual method called here is not part of the generated code. The method VirtFuncInvoker1<int32_t, String_t*>::Invoke is located in the GeneratedVirtualInvokers.h file. This file is generated by il2cpp.exe, but it doesn’t come from any IL code. Instead, il2cpp.exe creates this file based on the usage of virtual functions that return a value (VirtFuncInvokerN) and those that don’t (VirtActionInvokerN), where N is the number of arguments to the method.

                    请注意,此处调用的实际方法不属于所生成代码的一部分。 方法VirtFuncInvoker1<int32_t, String_t*>::Invoke位于GeneratedVirtualInvokers.h文件中。 该文件由il2cpp.exe生成,但是它不是来自任何IL代码。 相反,il2cpp.exe会根据虚拟函数的使用来创建此文件,这些虚拟函数返回一个值( VirtFuncInvoker N )和那些不返回值的虚拟函数( VirtActionInvoker N ),其中N是该方法的参数数目。

                    The Invoke method here looks like this:

                    这里的Invoke方法如下所示:

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    template <typename R, typename T1>
                    struct VirtFuncInvoker1
                    {
                    typedef R (*Func)(void*, T1, MethodInfo*);
                    static inline R Invoke (MethodInfo* method, void* obj, T1 p1)
                    {
                    VirtualInvokeData data = il2cpp::vm::Runtime::GetVirtualInvokeData (method, obj);
                    return ((Func)data.methodInfo->method)(data.target, p1, data.methodInfo);
                    }
                    };

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    template < typename R , typename T1 >
                    struct VirtFuncInvoker1
                    {
                    typedef R ( * Func ) ( void * , T1 , MethodInfo * ) ;
                    static inline R Invoke ( MethodInfo * method , void * obj , T1 p1 )
                    {
                    VirtualInvokeData data = il2cpp :: vm :: Runtime :: GetVirtualInvokeData ( method , obj ) ;
                    return ( ( Func ) data . methodInfo -> method ) ( data . target , p1 , data . methodInfo ) ;
                    }
                    } ;

                    The call into libil2cpp GetVirtualInvokeData looks up a virtual method in the vtable struct generated based on the managed code, then it makes a call to that method.

                    调用libil2cpp GetVirtualInvokeData在基于托管代码生成的vtable结构中查找虚拟方法,然后调用该方法。

                    Why don’t we used C++11 variadic templates to implement these VirtFuncInvokerN methods? This looks like a situation begging for variadic templates, and indeed it is. However, the C++ code generated by il2cpp.exe has to work with some C++ compilers which don’t yet support all C++ 11 features, including variadic templates. In this case at least, we did not think that forking the generated code for C++11 compilers was worth the additional complexity.

                    我们为什么不使用C ++ 11 可变参数模板来实现这些VirtFuncInvokerN方法? 这看起来像是乞求可变参数模板的情况,确实如此。 但是,il2cpp.exe生成的C ++代码必须与某些尚不支持所有C ++ 11功能(包括可变参数模板)的C ++编译器一起使用。 至少在这种情况下,我们认为为C ++ 11编译器分叉生成的代码不值得增加额外的复杂性。

                    But why is this a virtual method call? Aren’t we calling an instance method in the C# code? Recall that we are calling the instance method via a C# delegate. Look again at the generated code above. The actual method we are going to call is passed in via the MethodInfo* (method metadata) argument: ImportantMethodDelegate_Invoke_m5_MethodInfo. If we search for the method named “ImportantMethodDelegate_Invoke_m5” in the generated code, we see that the call is actually to the managed Invoke method on the ImportantMethodDelegate type. This is a virtual method, so we need to make a virtual call. It is this ImportantMethodDelegate_Invoke_m5 function which will actually make the call to the method named Method in the C# code.

                    但是,为什么这是一个虚拟方法调用? 我们不是在C#代码中调用实例方法吗? 回想一下,我们正在通过C#委托调用实例方法。 再次查看上面生成的代码。 我们要调用的实际方法是通过MethodInfo* (方法元数据)参数传递的: ImportantMethodDelegate_Invoke_m5_MethodInfo 。 如果我们在生成的代码中搜索名为“ ImportantMethodDelegate_Invoke_m5”的方法,则会看到该调用实际上是对ImportantMethodDelegate类型的托管Invoke方法的。 这是一种虚拟方法,因此我们需要进行虚拟调用。 正是这个ImportantMethodDelegate_Invoke_m5函数实际上将调用C#代码中名为Method

                    Wow, that was certainly a mouth-full. By making what looks like a simple change to the C# code, we’ve now gone from a single call to a C++ free function to multiple function calls, plus a table lookup. Calling a method via a delegate is significantly more costly than calling the same method directly.

                    哇,那肯定是满嘴的。 通过对C#代码进行简单的更改,我们现在已经从单个调用变为C ++自由函数,再到多个函数调用以及一个表查找。 通过委托调用方法比直接调用相同方法的成本高得多。

                    Note that in the process of looking at a delegate method call, we’ve also seen how a call via a virtual method works.

                    请注意,在查看委托方法调用的过程中,我们还看到了通过虚拟方法进行的调用的工作方式。

                    Calling a method via an interface

                    通过接口调用方法

                    It’s also possible to call a method in C# via an interface. This call is implemented by il2cpp.exe similar to a virtual method call:

                    也可以通过接口在C#中调用方法。 该调用由il2cpp.exe实现,类似于虚拟方法调用:

                    1

                    2
                    3
                    4
                    5
                    6
                    Important_t1 * L_0 = (Important_t1 *)il2cpp_codegen_object_new (InitializedTypeInfo(&Important_t1_il2cpp_TypeInfo));
                    Important__ctor_m0(L_0, /*hidden argument*/&Important__ctor_m0_MethodInfo);
                    V_0 = L_0;
                    Object_t * L_1 = V_0;
                    NullCheck(L_1);
                    InterfaceFuncInvoker1< int32_t, String_t* >::Invoke(&Interface_MethodOnInterface_m22_MethodInfo, L_1, (String_t*) &_stringLiteral1);

                    1

                    2
                    3
                    4
                    5
                    6
                    Important_t1 * L_0 = ( Important_t1 * ) il2cpp_codegen_object_new ( InitializedTypeInfo ( &Important_t1_il2cpp_TypeInfo ) ) ;
                    Important__ctor_m0 ( L_0 , /*hidden argument*/ &Important__ctor_m0_MethodInfo ) ;
                    V_0 = L_0 ;
                    Object_t * L_1 = V_0 ;
                    NullCheck ( L_1 ) ;
                    InterfaceFuncInvoker1 < int32_t , String_t * > :: Invoke ( &Interface_MethodOnInterface_m22_MethodInfo , L_1 , ( String_t * ) &_stringLiteral1 ) ;

                    Note the actual method call here is done via the InterfaceFuncInvoker1::Invoke function, which is in the GeneratedInterfaceInvokers.h file. Like the VirtFuncInvoker1 class the InterfaceFuncInvoker1 class does a lookup in a vtable via the il2cpp::vm::Runtime::GetInterfaceInvokeData function in libil2cpp.

                    请注意,此处的实际方法调用是通过InterfaceFuncInvoker1::Invoke函数完成的,该函数位于GeneratedInterfaceInvokers.h文件中。 类似于Virt FuncInvoker1类, Interface FuncInvoker1类通过il2cpp::vm::Runtime::GetInterfaceInvokeData函数在vtable中进行查找。

                    Why does an interface method call need to use a different API in libil2cpp from a virtual method call? Note that the call to InterfaceFuncInvoker1::Invoke is passing not only the method to call and its arguments, but also the interface to call that method on (L_1 in this case). The vtable for each type is stored so that interface methods are written at a specific offset. Therefore, il2cpp.exe needs to provide the interface in order to determine which method to call.

                    为什么接口方法调用需要在libil2cpp中使用与虚拟方法调用不同的API? 请注意,对InterfaceFuncInvoker1::Invoke不仅传递了要调用的方法及其参数,而且还传递了要调用该方法的接口(在本例中为L_1 )。 存储每种类型的vtable,以便以特定的偏移量写入接口方法。 因此,il2cpp.exe需要提供接口以确定要调用的方法。

                    The bottom line here is that calling a virtual method and calling a method via an interface have effectively the same overhead in IL2CPP.

                    最重要的是,在IL2CPP中,调用虚拟方法和通过接口调用方法实际上具有相同的开销。

                    Calling a method via a run-time delegate

                    通过运行时委托调用方法

                    Another way to use a delegate is to create it at runtime via the Delegate.CreateDelegate method. This approach is similar to a compile-time delegate, except that it be modified at runtime in a few more ways. We pay for that flexibility with an additional function call. Here is the generated code:

                    使用委托的另一种方法是在运行时通过Delegate.CreateDelegate方法创建Delegate.CreateDelegate 。 该方法类似于编译时委托,只是在运行时以其他几种方式对其进行了修改。 我们通过额外的函数调用来支付这种灵活性。 这是生成的代码:

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    20
                    // Get the object instance used to call the method.
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
                    V_0 = L_0;
                    // Create the delegate.
                    IL2CPP_RUNTIME_CLASS_INIT(InitializedTypeInfo(&Type_t_il2cpp_TypeInfo));
                    Type_t * L_1 = Type_GetTypeFromHandle_m19(NULL /*static, unused*/, LoadTypeToken(&ImportantMethodDelegate_t4_0_0_0), /*hidden argument*/&Type_GetTypeFromHandle_m19_MethodInfo);
                    Important_t1 * L_2 = V_0;
                    Delegate_t12 * L_3 = Delegate_CreateDelegate_m20(NULL /*static, unused*/, L_1, L_2, (String_t*) &_stringLiteral2, /*hidden argument*/&Delegate_CreateDelegate_m20_MethodInfo);
                    V_1 = L_3;
                    Delegate_t12 * L_4 = V_1;
                    // Call the method
                    ObjectU5BU5D_t9* L_5 = ((ObjectU5BU5D_t9*)SZArrayNew(ObjectU5BU5D_t9_il2cpp_TypeInfo_var, 1));
                    NullCheck(L_5);
                    IL2CPP_ARRAY_BOUNDS_CHECK(L_5, 0);
                    ArrayElementTypeCheck (L_5, (String_t*) &_stringLiteral1);
                    *((Object_t **)(Object_t **)SZArrayLdElema(L_5, 0)) = (Object_t *)(String_t*) &_stringLiteral1;
                    NullCheck(L_4);
                    Delegate_DynamicInvoke_m21(L_4, L_5, /*hidden argument*/&Delegate_DynamicInvoke_m21_MethodInfo);

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    20
                    // Get the object instance used to call the method.
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
                    V_0 = L_0 ;
                    // Create the delegate.
                    IL2CPP_RUNTIME_CLASS_INIT ( InitializedTypeInfo ( &Type_t_il2cpp_TypeInfo ) ) ;
                    Type_t * L_1 = Type_GetTypeFromHandle_m19 ( NULL /*static, unused*/ , LoadTypeToken ( &ImportantMethodDelegate_t4_0_0_0 ) , /*hidden argument*/ &Type_GetTypeFromHandle_m19_MethodInfo ) ;
                    Important_t1 * L_2 = V_0 ;
                    Delegate_t12 * L_3 = Delegate_CreateDelegate_m20 ( NULL /*static, unused*/ , L_1 , L_2 , ( String_t * ) &_stringLiteral2 , /*hidden argument*/ &Delegate_CreateDelegate_m20_MethodInfo ) ;
                    V_1 = L_3 ;
                    Delegate_t12 * L_4 = V_1 ;
                    // Call the method
                    ObjectU5BU5D_t9 * L_5 = ( ( ObjectU5BU5D_t9 * ) SZArrayNew ( ObjectU5BU5D_t9_il2cpp_TypeInfo_var , 1 ) ) ;
                    NullCheck ( L_5 ) ;
                    IL2CPP_ARRAY_BOUNDS_CHECK ( L_5 , 0 ) ;
                    ArrayElementTypeCheck ( L_5 , ( String_t * ) &_stringLiteral1 ) ;
                    * ( ( Object_t * * ) ( Object_t * * ) SZArrayLdElema ( L_5 , 0 ) ) = ( Object_t * ) ( String_t * ) &_stringLiteral1 ;
                    NullCheck ( L_4 ) ;
                    Delegate_DynamicInvoke_m21 ( L_4 , L_5 , /*hidden argument*/ &Delegate_DynamicInvoke_m21_MethodInfo ) ;

                    This delegate requires a good bit of code for creation and initialization. But the method call itself has even more overhead, too. First we need to create an array to hold the method arguments, then call the DynamicInvoke method on the Delegate instance. If we follow that method in the generated code, we can see that it calls the VirtFuncInvoker1::Invoke function, just as the compile-time delegate does. So this delegate requires one more function call then the compile-time delegate does, plus two lookups in a vtable, instead of just one.

                    该委托需要大量的代码来创建和初始化。 但是方法调用本身也有更多的开销。 首先,我们需要创建一个数组来保存方法参数,然后在Delegate实例上调用DynamicInvoke方法。 如果在生成的代码中遵循该方法,则可以看到它调用了VirtFuncInvoker1::Invoke函数,就像编译时委托一样。 因此,此委托需要比编译时委托多的一个函数调用,以及在vtable中进行两次查找,而不只是一次。

                    Calling a method via reflection

                    通过反射调用方法

                    The most costly way to call a method is, not surprisingly, via reflection. Let’s look at the generated code for the CallViaReflection method:

                    毫不奇怪,调用方法的最昂贵方法是通过反射。 让我们看看为CallViaReflection方法生成的代码:

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    20
                    21
                    // Get the object instance used to call the method.
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
                    V_0 = L_0;
                    // Get the method metadata from the type via reflection.
                    IL2CPP_RUNTIME_CLASS_INIT(InitializedTypeInfo(&Type_t_il2cpp_TypeInfo));
                    Type_t * L_1 = Type_GetTypeFromHandle_m19(NULL /*static, unused*/, LoadTypeToken(&Important_t1_0_0_0), /*hidden argument*/&Type_GetTypeFromHandle_m19_MethodInfo);
                    NullCheck(L_1);
                    MethodInfo_t * L_2 = (MethodInfo_t *)VirtFuncInvoker1< MethodInfo_t *, String_t* >::Invoke(&Type_GetMethod_m23_MethodInfo, L_1, (String_t*) &_stringLiteral2);
                    V_1 = L_2;
                    MethodInfo_t * L_3 = V_1;
                    // Call the method.
                    Important_t1 * L_4 = V_0;
                    ObjectU5BU5D_t9* L_5 = ((ObjectU5BU5D_t9*)SZArrayNew(ObjectU5BU5D_t9_il2cpp_TypeInfo_var, 1));
                    NullCheck(L_5);
                    IL2CPP_ARRAY_BOUNDS_CHECK(L_5, 0);
                    ArrayElementTypeCheck (L_5, (String_t*) &_stringLiteral1);
                    *((Object_t **)(Object_t **)SZArrayLdElema(L_5, 0)) = (Object_t *)(String_t*) &_stringLiteral1;
                    NullCheck(L_3);
                    VirtFuncInvoker2< Object_t *, Object_t *, ObjectU5BU5D_t9* >::Invoke(&MethodBase_Invoke_m24_MethodInfo, L_3, L_4, L_5);

                    1

                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    20
                    21
                    // Get the object instance used to call the method.
                    Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
                    V_0 = L_0 ;
                    // Get the method metadata from the type via reflection.
                    IL2CPP_RUNTIME_CLASS_INIT ( InitializedTypeInfo ( &Type_t_il2cpp_TypeInfo ) ) ;
                    Type_t * L_1 = Type_GetTypeFromHandle_m19 ( NULL /*static, unused*/ , LoadTypeToken ( &Important_t1_0_0_0 ) , /*hidden argument*/ &Type_GetTypeFromHandle_m19_MethodInfo ) ;
                    NullCheck ( L_1 ) ;
                    MethodInfo_t * L_2 = ( MethodInfo_t * ) VirtFuncInvoker1 < MethodInfo_t * , String_t * > :: Invoke ( &Type_GetMethod_m23_MethodInfo , L_1 , ( String_t * ) &_stringLiteral2 ) ;
                    V_1 = L_2 ;
                    MethodInfo_t * L_3 = V_1 ;
                    // Call the method.
                    Important_t1 * L_4 = V_0 ;
                    ObjectU5BU5D_t9 * L_5 = ( ( ObjectU5BU5D_t9 * ) SZArrayNew ( ObjectU5BU5D_t9_il2cpp_TypeInfo_var , 1 ) ) ;
                    NullCheck ( L_5 ) ;
                    IL2CPP_ARRAY_BOUNDS_CHECK ( L_5 , 0 ) ;
                    ArrayElementTypeCheck ( L_5 , ( String_t * ) &_stringLiteral1 ) ;
                    * ( ( Object_t * * ) ( Object_t * * ) SZArrayLdElema ( L_5 , 0 ) ) = ( Object_t * ) ( String_t * ) &_stringLiteral1 ;
                    NullCheck ( L_3 ) ;
                    VirtFuncInvoker2 < Object_t * , Object_t * , ObjectU5BU5D_t9 * > :: Invoke ( &MethodBase_Invoke_m24_MethodInfo , L_3 , L_4 , L_5 ) ;

                    As in the case of the runtime delegate, we need to spend some time creating an array for the arguments to the method. Then we make a virtual method call to MethodBase::Invoke (the MethodBase_Invoke_m24 function). This function in turn invokes another virtual function, before we finally get to the actual method call!

                    与运行时委托的情况一样,我们需要花一些时间为该方法的参数创建一个数组。 然后,我们对MethodBase::Invoke ( MethodBase_Invoke_m24函数)进行虚拟方法调用。 在我们最终进入实际的方法调用之前,该函数依次调用了另一个虚拟函数!

                    Conclusion

                    结论

                    While this is no substitute for actual profiling and measurement, we can get some insight about the overhead of any given method invocation by looking at how the generated C++ code is used for different types of method calls. Specifically, it is clear that we want to avoid calls via run-time delegates and reflection, if at all possible. As always, the best advice about making performance improvements is to measure early and often with profiling tools.

                    尽管这不能替代实际的性能分析和测量,但通过查看生成的C ++代码如何用于不同类型的方法调用,我们可以对任何给定方法调用的开销有一些了解。 具体来说,很明显,我们希望尽可能避免通过运行时委托和反射进行调用。 与往常一样,有关提高性能的最佳建议是尽早进行测量,并且通常使用性能分析工具进行测量。

                    We’re always looking for ways to optimize the code generated by il2cpp.exe, so it is likely that these method calls will look different in a later version of Unity.

                    我们一直在寻找优化il2cpp.exe生成的代码的方法,因此这些方法调用在Unity的更高版本中看起来可能会有所不同。

                    Next time we’ll delve deeper in to method implementations and see how we share the implementation of generic methods to minimize generated code and executable size.

                    下次,我们将深入研究方法的实现,并了解如何共享通用方法的实现,以最大程度地减少生成的代码和可执行文件的大小。

                    翻译自: https://blogs.unity3d.com/2015/06/03/il2cpp-internals-method-calls/

                    il2cpp

                    展开全文
                  • unity il2cpp 热更实现

                    千次阅读 2021-02-05 14:58:53
                    unity libil2cpp.so 代码动态库热更实现 代码热更分别要实现动态库热更和global-metadata.dat的热更。...1.声明出il2cpp所有的导出函数 // // Created by Administrator on 2019/5/14. // #ifndef TEST_UPDATE_
                  • D:\����\RW-MonumentValley\RW-MonumentValley\MonumentValleyStart\Library\il2cpp_cache il2cpp.exe didn't catch exception: Unity.IL2CPP.Building.BuilderFailedException: pch-c.c D:\unity\Builder\2020...
                  • il2cpp 这是IL2CPP Internals系列的第八篇也是最后一篇文章。 在本文中,我将与以前的内容有所不同,而不是讨论IL2CPP在编译时或运行时如何工作的某些方面。 相反,我们将简要概述如何开发和测试IL2CPP。 (This is ...
                  • il2cpp 在我们的IL2CPP微型优化迷你系列的最后一集中,我们将探讨被称为“装箱”的高成本,并且我们将了解IL2CPP如何避免不必要的操作。 (In this final episode of our IL2CPP micro-optimization miniseries, we’...
                  • il2cpp动态调试This is the third blog post in the IL2CPP Internals series. In this post, we will explore some tips which make debugging C++ code generated by IL2CPP a little bit easier. We will see how...
                  • A tool translate a apk file to common android project and support so hook include il2cpp c++ scaffolding FakerAndroid A tool translate apk file to common android project and support so hook and ...
                  • il2cpp 这是IL2CPP Internals系列中的第六篇 。 在本文中,我们将探讨il2cpp.exe如何生成用于在托管代码和本机代码之间进行互操作的包装方法和类型。 具体来说,我们将研究可blittable类型和不可bbltable类型之间的...
                  • Mono和IL2CPP

                    千次阅读 2021-07-25 14:54:16
                    IL2CPP工具链: 运行时库 IL2CPP技术的另一部分是运行时库(libil2cpp),用于支持IL2CPP虚拟机的运行。 这个简单且可移植的运行时库是IL2CPP技术的主要优势之一! 通过查看我们随Unity一起提供的libil2cpp的头文件...
                  • il2cpp 这是IL2CPP Internals系列中的第七篇 。 在本文中,我们将探讨IL2CPP运行时如何与垃圾收集器集成。 具体来说,我们将看到托管代码中的GC根如何与本地垃圾收集器通信。 (This is the seventh post in the IL2...

                  空空如也

                  空空如也

                  1 2 3 4 5 ... 20
                  收藏数 6,682
                  精华内容 2,672
                  关键字:

                  il2cpp

                  友情链接: JsqFhAlG.rar