我的unity ios metal 选项5里,怎么没有IL2CPP选项

8521人阅读
Unity IL2CPP原理(6)
作者:小玉
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Unity3D想必大家都不陌生,独立游戏制作者们很多人都在用它,甚至一些大公司也用在很商业的游戏制作上。Unity3D最大的一个特点是一次制作,多平台部署,而这一核心功能是靠Mono实现的。可以说Mono是Unity3D核心的核心,是Unity3D跨平台的根本。但是在2014年年中的时候,Unity3D官方博客上却发了一篇“”的文章,引出了IL2CPP的概念,感觉有取代Mono之势。那什么是IL2CPP,它能为Unity3D和作为使用Unity3D的我们带来哪些好处和改变?这就是本文尝试说明的。
C#,.Net Framework
我们先说说IL2CPP试图取代的Mono。在说Mono之前,不得不提C#语言和背后的.Net Framework。C#是微软推出的一种基于.NET框架的、面向对象的高级编程语言。C#的发音为“see sharp”,模仿音乐上的音名“C?”(C调升),是C语言的升级的意思。其正确写法应和音名一样为“C?”。C#由C语言和C++派生而来,继承了其强大的性能,同时又以.NET框架类库作为基础,拥有类似Visual Basic的快速开发能力。C#由安德斯·海尔斯伯格主持开发,微软在2000年发布了这种语言。说到安德斯·海尔斯伯格这里要多说一句,当年和VC(注意,是VC,那个时候还没有Visual
Studio)齐名还有另外一家公司的IDE也非常流行,那就是Borland公司的Delphi,也是由安德斯·海尔斯伯格主导开发的。
他后来被微软挖走,创建了J++,一门类似Java的语言(好吧,以我肤浅的知识认为,那基本就是照着Java做的)。后来由于和Sun公司授权的原因,微软在2001年停止了J++的开发而推出了C# 1.0。说来要感谢和Sun的这场官司,否则微软也不会有C#,J++也可能一直会跟随Java的脚步。相反C#经过不断的进化,从1.0开始到4.0和最新的5.0,C#已经远远甩开Java几条街了(还是以我个人的使用Java和C#感觉而言,关于两门语言的比较,无论是效率上,夸平台上,还是语言易用性上,社区活跃度上,网上的争论随处可见,每个人都有自己的看法,这也不是本文的重点)。
Mono,Mono VM
C#虽好,但是只能在Windows上运行,微软那时候也没有将其开源,所以总是会有人说不能跨平台,光就这点,C#和Java就不能比呀。
微软公司已经向ECMA申请将C#作为一种标准。在2001年12月,ECMA发布了ECMA-334 C#语言规范。C#在2003年成为一个ISO标准(ISO/IEC 23270)。这意味着只要你遵守CLI(Common Language Infrastructure),第三方可以将任何一种语言实现到.Net平台之上。Mono就是在这种环境下诞生的。
Mono是一个由Xamarin公司(先前是Novell,最早为Ximian)所主持的自由开放源代码项目。该项目的目标是创建一系列符合ECMA标准(Ecma-334和Ecma-335)的.NET工具,包括C#编译器和通用语言架构。与微软的.NET Framework(共通语言运行平台)不同,Mono项目不仅可以运行于Windows系统上,还可以运行于Linux,FreeBSD,Unix,OS X和Solaris,甚至一些游戏平台,例如:Playstation 3,Wii或XBox 360之上。Mono使得C#这门语言有了很好的跨平台能力。相对于微软的.Net
Framework运行时库Mono使用自己的Mono VM作为运行时库。 加上C#本身快速友好的开发能力,最终使得Unity团队在创建之初就决定将Mono,C#作为其核心。(嗯,这是我猜的)
有人也许会说,Unity还支持JavaScript和Boo呢,不光光只有C#一门语言。首先我要纠正的是,在Unity中的JavaScript严格意义上说并不是W3C规范中的JavaScript,它正确的名字叫做Unity Script,其实是从Boo演变过来的(这样大家就能理解为啥在3门语言中,Boo用的人最少,但是却还一直存在的原因了吧)。我认为是Unity开始为了让更多的人能够快速的上手,特别是考虑到很多脚本程序员对JavaScript已经很熟悉了,为了照顾这部分人,发明了Unity Script,它的语法和W3C的JavaScript几乎一致,使得大家可以直接用其进行开发,降低门槛。但是Unity
Script在运行上却和JavaScript有着本质的不同。这个我会在下一节IL中进行详细的描述。从三门语言在Unity中的使用情况而言:Boo几乎就没人用了,Unity Script和C#两者中无论是演示代码还是Unity Asset Store中的第三方代码,C#已经有85%-90%的比例(个人粗略估计,没有做详细统计)。可见C#在Unity中深受我等游戏码农的爱戴。
啰嗦完了C#,.Net Framework和Mono,引出了我们很重要的一个概念”IL“。IL的全称是 Intermediate Language,很多时候还会看到CIL(Common Intermediate Language,特指在.Net平台下的IL标准)。在Unity博客和本文中,IL和CIL表示的是同一个东西:翻译过来就是中间语言。它是一种属于通用语言架构和.NET框架的低阶(lowest-level)的人类可读的编程语言。目标为.NET框架的语言被编译成CIL,然后汇编成字节码。CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的,它运行在虚拟机上(.Net
Framework, Mono VM)的语言。
具体过程是:C#或者VB这样遵循CLI规范的高级语言,被先被各自的编译器编译成中间语言:IL(CIL),等到需要真正执行的时候,这些IL会被加载到运行时库,也就是VM中,由VM动态的编译成汇编代码(JIT)然后在执行。
正是由于引入了VM,才使得很多动态代码特性得以实现。通过VM我们甚至可以由代码在运行时生成新代码并执行。这个是静态编译语言所无法做到的。回到上一节我说的Boo和Unity Script,有了IL和VM的概念我们就不难发现,这两者并没有对应的VM虚拟机,Unity中VM只有一个:Mono VM,也就是说Boo和Unity Script是被各自的编译器编译成遵循CLI规范的IL,然后再由Mono VM解释执行的。这也是Unity Script和JavaScript的根本区别。JavaScript是最终在浏览器的JS解析器中运行的(例如大名鼎鼎的Google
Chrome V8引擎),而Unity Script是在Mono VM中运行的。本质上说,到了IL这一层级,它是由哪门高级语言创建的也不是那么重要了,你可以用C#,VB,Boo,Unity Script甚至C++,只要有相应的编译器能够将其编译成IL都行!
IL2CPP, IL2CPP VM
本文的主角终于出来了:IL2CPP。有了上面的知识,大家很容易就理解其意义了:把IL中间语言转换成CPP文件。大家如果看明白了上面动态语言的CLI, IL以及VM,再看到IL2CPP一定心中充满了疑惑。现在的大趋势都是把语言加上动态特性,哪怕是c++这样的静态语言,也出现了适合IL的c++编译器,为啥Unity要反其道而行之,把IL再弄回静态的CPP呢?这不是吃饱了撑着嘛。根据本文最前面给出的Unity官方博客所解释的,原因有以下几个:
1.Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成
Mono的跨平台是通过Mono VM实现的,有几个平台,就要实现几个VM,像Unity这样支持多平台的引擎,Mono官方的VM肯定是不能满足需求的。所以针对不同的新平台,Unity的项目组就要把VM给移植一遍,同时解决VM里面发现的bug。这非常耗时耗力。这些能移植的平台还好说,还有比如WebGL这样基于浏览器的平台。要让WebGL支持Mono的VM几乎是不可能的。
2.Mono版本授权受限
大家有没有意识到Mono的版本已经更新到3.X了,但是在Unity中,C#的运行时版本一直停留在2.8,这也是Unity社区开发者抱怨的最多一条:很多C#的新特性无法使用。这是因为Mono 授权受限,导致Unity无法升级Mono。如果换做是IL2CPP,IL2CPP VM这套完全自己开发的组件,就解决了这个问题。
3.提高运行效率
根据官方的实验数据,换成IL2CPP以后,程序的运行效率有了1.5-2.0倍的提升。
使用Mono的时候,脚本的编译运行如下图所示:
简单的来说,3大脚本被编译成IL,在游戏运行的时候,IL和项目里其他第三方兼容的DLL一起,放入Mono VM虚拟机,由虚拟机解析成机器码,并且执行
IL2CPP做的改变由下图红色部分标明:
在得到中间语言IL后,使用IL2CPP将他们重新变回C++代码,然后再由各个平台的C++编译器直接编译成能执行的原生汇编代码。
几点注意:
1.将IL变回CPP的目的除了CPP的执行效率快以外,另一个很重要的原因是可以利用现成的在各个平台的C++编译器对代码执行编译期优化,这样可以进一步减小最终游戏的尺寸并提高游戏运行速度。
2.由于动态语言的特性,他们多半无需程序员太多关心内存管理,所有的内存分配和回收都由一个叫做GC(Garbage Collector)的组件完成。虽然通过IL2CPP以后代码变成了静态的C++,但是内存管理这块还是遵循C#的方式,这也是为什么最后还要有一个IL2CPP VM的原因:它负责提供诸如GC管理,线程创建这类的服务性工作。但是由于去除了IL加载和动态解析的工作,使得IL2CPP VM可以做的很小,并且使得游戏载入时间缩短。
3.由于C++是一门静态语言,这就意味着我们不能使用动态语言的那些酷炫特性。运行时生成代码并执行肯定是不可能了。这就是Unity里面提到的所谓AOT(Ahead Of Time)编译而非JIT(Just In Time)编译。其实很多平台出于安全的考虑是不允许JIT的,大家最熟悉的有iOS平台,在Console游戏机上,不管是微软的Xbox360, XboxOne,还是Sony的PS3,PS4,PSV,没有一个是允许JIT的。使用了IL2CPP,就完全是AOT方式了,如果原来使用了动态特性的代码肯定会编译失败。这些代码在编译iOS平台的时候天生也会失败,所以如果你是为iOS开发的游戏代码,就不用担心了。因此就这点而言,我们开发上几乎不会感到什么问题。
最后给出Unite 2014上官方给出的性能测试截图(数字越小表示运行得越快):
有了IL2CPP,程序尺寸可以相对缩小,运行速度可以提高!看了兴奋吗?其实现有的Unity版本中已经引入了IL2CPP技术。本文下篇就通过一个实际的例子,看看IL2CPP都为我们做了哪些,以及我们需要注意些什么。
参考链接:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:25431次
排名:千里之外
转载:15篇
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'今天看啥 热点:
(八)Unity5.0新特性------IL2CPP Internals: 生成的代码之旅,unity5.0
孙广东& 转载请注明出处吧这是 IL2CPP Internals系列中的第二个博客文章。在这篇文章,我们将探讨由 il2cpp.exe 生成的 c + + 代码。一路走来,我们将看到托管的类型怎么样表示在本机代码中,看看运行时检查用来支持.NET 虚拟机,请参阅如何循环生成的更多 !&&&& 我们会遇到一些非常特定于版本的代码,更高版本的Unity一定会改变。尽管如此,但概念将保持不变。示例项目:我会为此示例使用Unity5.0.1p1 最新版本。在本系列的第一篇,我会从空项目开始,并添加一个脚本文件。这一次,它具有下列内容:using UnityEpublic class HelloWorld : MonoBehaviour {& private class Important {&&& public static int ClassIdentifier = 42;&&& public int InstanceI& }& 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,建立这一项目。我选择Development Player选项在Build Settings中,这样我们可以得到相对不错的名字生成的 c + + 代码中。我也已经设置Enable Exceptions选项在 WebGL Player Settings中值为Full。生成的代码的概述:&&&& WebGL build完成后,生成的 c + + 代码是在我的项目目录中的 Temp\StagingArea\Data\il2cppOutput 目录中。一旦关闭了编辑器,将删除此目录。只要编辑器是打开的此目录将保持不变,所以我们可以检查它。Il2cpp.exe 实用程序生成的文件的数目,甚至小项目。4625个 头文件和 89 c + + 源代码文件, 若要获得此代码的所有句柄,我喜欢使用一个文本编辑器 Exuberant CTags。CTags 通常会迅速产生为这段代码的标签文件,这使得它易于导航。最初,你可以看到很多生成的 c + + 文件,而不是从简单的脚本代码,但相反的转换版本代码在标准库,如 mscorlib.dll 中的代码。在本系列中的第一篇文章中提到,IL2CPP 脚本后端使用相同的标准库代码作为Mono的脚本后端。注意: 我们转换 mscorlib.dll 和其他标准库程序集,每个时间 il2cpp.exe 运行中的代码。这可能似乎不必要的因为该代码不会改变。然而,IL2CPP 脚本后端始终使用字节代码剥离减小该可执行文件的大小。因此,即使在脚本代码中微小的变化可以引发许多不同的部分要使用标准库代码,根据具体情况。因此,我们需要每次转换将 mscorlib.dll 程序集。我们正在研究如何更好地做增量生成,但我们还没有任何好的解决方案。如何将托管的代码映射到生成的 c + + 代码:对于在托管代码中的每个类型,il2cpp.exe 将生成一个 c + +的头文件 中定义类型 和类型的方法声明在另一个头文件。例如,让我们看看转换后的 UnityEngine.Vector3 类型的内容。头文件的类型是命名为 UnityEngine_UnityEngine_Vector3.h。创建了一种基于程序集名称,UnityEngine.dll 紧跟的命名空间和类型的名称的名称。代码如下所示:// UnityEngine.Vector3struct 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 转换的方法是有趣的事情:&&& · 在c++中这些不是成员函数。所有方法都是free functions,其中的第一个参数是&this&指针。关于托管代码中的静态函数,IL2CPP 总是让这第一个参数this设为 NULL 值。通过始终声明具有&this&指针作为第一个参数的方法,我们简化了il2cpp.exe生成代码方法和我们使其他方法 (如委托) 的调用方法生成的代码更简单。&· 每个方法有一个额外的参数的类型是MethodInfo *,其中包括用于类的虚拟方法调用的有关方法的元数据。Mono的脚本后端使用特定于平台的trampolines传递此元数据。关于IL2CPP,我们已经决定避免使用trampolines,有助于可移植性。&· 所有方法都声明 extern &C&,以便 il2cpp.exe 有时可以对 c++ 编译器撒谎和对待所有方法,因为如果他们有相同的类型。&· 以&_t&后缀命名的类型。以&_m&后缀命名的方法。命名冲突解决的每个名称后附加一个唯一的数字。如果任何用户脚本代码中发生更改这些数字将会改变,所以你不能在build时指望他们。&前两个点暗示每个方法有至少两个参数,是&this&指针和 MethodInfo 指针。这些额外的参数会导致不必要的开销吗?尽管他们会增加开销,到目前为止我们从来没有见过那些额外的参数会导致性能问题。尽管它看起来他们可能会导致,分析表明性能的差异是不可以衡量太小了。&我们可以使用 Ctags 工具跳转到这个 ToString 方法的定义。它是在 Bulk_UnityEngine_0.cpp 文件中。代码中该方法的定义看起来不太像 C# 代码中的 Vector3::ToString() 方法。然而,如果你使用像 ILSpy 这样的工具来反射 Vector3::ToString() 方法的代码,您将看到生成的 c + + 代码看起来非常类似于 IL 代码。&为什么 il2cpp.exe 不会为每个类型的一样的方法声明分别产生一个单独的 c + + 文件,这个 Bulk_UnityEngine_0.cpp 文件是相当大,其实20,481 行 !我们发现我们正在使用的 c + + 编译器有大量的源代码文件的麻烦。编译四千个.cpp 文件时间远远多于 80 个.cpp相同的源代码 文件编译。所以 il2cpp.exe 类型分组的批方法定义,并每个组生成一个 c + + 文件,。现在跳回方法声明的头文件,并注意到该文件的顶部附近的这行:#include &codegen/il2cpp-codegen.h&il2cpp-codegen.h文件包含生成的代码用来访问 libil2cpp 运行时服务的接口。我们将讨论一些运行时使用的方法生成的代码。Method prologues&让我们看看 Vector3::ToString() 方法的定义。具体说来,它具有共同的prologue部分,由 il2cpp.exe emitted的所有方法。StackTraceSentry _stackTraceSentry(&Vector3_ToString_m2315_MethodInfo);static bool Vector3_ToString_m2315_if (!Vector3_ToString_m2315_init){& ObjectU5BU5D_t4_il2cpp_TypeInfo_var = il2cpp_codegen_class_from_type(&ObjectU5BU5D_t4_0_0_0);& Vector3_ToString_m2315_init =}&&这样的prologue的第一行创建一个局部变量的类型 StackTraceSentry。此变量用于跟踪托管的调用堆栈,因此,IL2CPP 可以报告它在调用像 Environment.StackTrace。此条目的代码生成是实际上是可选的并在这种情况下启用的--启用栈跟踪选项传递给 il2cpp.exe (因为我在WebGL Player Settings中设置Enable Exceptions选项为Full)。对于小函数,我们发现此变量的开销对性能有负面的影响。所以对于 iOS 和其他平台,在那里我们可以使用特定于平台的堆栈跟踪信息,我们永远不会发出这条线到生成的代码。WebGL,我们没有特定于平台的堆栈跟踪支持,因此有必要允许托管的代码异常才能正常工作。&prologue的第二部分没有延迟初始化的数组或在方法体中使用的泛型类型的类型元数据。所以名称 ObjectU5BU5D_t4 是类型名为 System.Object [] 。prologue的这一部分只执行一次,并经常做什么如果类型已初始化在其他地方,所以我们还没有看到任何负面性能影响从生成的代码。&可是此代码线程安全吗?如果两个线程同时调用 Vector3::ToString()?其实,此代码并不成问题,因为所有的 libil2cpp 运行时用于类型中的代码初始化是安全的从多个线程中调用。它是可能 (甚至可能) 会不止一次,调用 il2cpp_codegen_class_from_type 函数,但它的实际工作才会有一次,发生在一个线程上。方法执行不会继续,直到初始化已完成。所以这方法开场白是线程安全的。Runtime checks运行时检查&该方法的下一部分创建一个对象数组、 Vector3的 x 字段的值存储在本地,然后盒当地和将其添加到索引从零开始的数组。下面是生成的 c + + 代码 (用一些注释功能):&&// Create a new single-dimension, zero-based object array&ObjectU5BU5D_t4* L_0 = ((ObjectU5BU5D_t4*)SZArrayNew(ObjectU5BU5D_t4_il2cpp_TypeInfo_var, 3));&// Store the Vector3::x field in a local&float L_1 = (__this-&___x_1);&float L_2 = L_1;&// Box the float instance, since it is a value type.&Object_t * L_3 = Box(InitializedTypeInfo(&Single_t264_il2cpp_TypeInfo), &L_2);&// Here are three important runtime checks&NullCheck(L_0);&IL2CPP_ARRAY_BOUNDS_CHECK(L_0, 0);&ArrayElementTypeCheck (L_0, L_3);&// Store the boxed value in the array at index 0&*((Object_t **)(Object_t **)SZArrayLdElema(L_0, 0)) = (Object_t *)L_3;& & 三个运行时检查不存在 IL 代码中,但反而被由 il2cpp.exe注入。o 该 NullCheck 代码将引发NullReferenceException,如果数组的值为 null。o 该 IL2CPP_ARRAY_BOUNDS_CHECK 代码将引发 IndexOutOfRangeException,如果数组索引不正确。o 该 ArrayElementTypeCheck 代码会引发的 ArrayTypeMismatchException,如果被添加到该数组中元素的类型不正确。&这些三个运行时检查是由.NET 虚拟机提供的所有保证。而不是注入代码,Mono脚本后端使用平台特定的信号转导机制来处理这些相同的运行时检查。对于 IL2CPP,我们想要更多的平台得到不可知论的和支持的平台,像 WebGL,那里有没有特定于平台的信号转导机制,所以 il2cpp.exe 注入这些检查。&&做这些运行时检查会导致性能问题吗?在大多数情况下,在性能上,我们没看到任何不利的影响,他们提供的好和.NET 虚拟机所需的安全。不过,在几个特定的情况下我们看到这些检查,导致性能下降,尤其是在紧凑循环中。我们正在做现在允许托管的代码进行注释以移除这些运行时检查,当 il2cpp.exe 生成 c + + 代码。敬请关注这一方面。&Static Fields静态字段&现在,我们已经看到如何实例字段(Vector3 类型),让我们看到了静态字段转换和访问。找到的 HelloWorld_Start_m3 方法定义,是在我生成的 Bulk_Assembly CSharp_0.cpp 文件中定义。从那里,跳转到 Important_t1 类型 (在 theAssemblyU2DCSharp_HelloWorld_Important.h 文件中):struct Important_t1& : public Object_t{& // System.Int32 HelloWorld/Important::InstanceIdentifier& int32_t ___InstanceIdentifier_1;};struct Important_t1_StaticFields{& // System.Int32 HelloWorld/Important::ClassIdentifier& int32_t ___ClassIdentifier_0;};&Notice that il2Notice that il2cpp.exe has generated a separate C++ struct to hold the static field for this type, since the static field is shared between all instances of this type. So at runtime, there will be one instance of the Important_t1_StaticFields type created, and all of the instances of the Important_t1 type will share that instance of the static fields type. In generated code, the static field is accessed like this:请注意,il2cpp.exe 已对此类型的静态字段生成一个单独的 c + + 结构体,因为该静态字段这种类型的所有实例之间要共享。所以在运行时,会有创建,Important_t1_StaticFields 类型的一个实例,所有 Important_t1 类型的实例将共享该实例的静态字段的类型。生成的代码中访问静态字段时像这样:int32_t L_1 = (((Important_t1_StaticFields*)InitializedTypeInfo(&Important_t1_il2cpp_TypeInfo)-&static_fields)-&___ClassIdentifier_0);Important_t1 的类型元数据握着一个指针,指向的 Important_t1_StaticFields 类型的一个实例,该实例用于获取静态字段的值。Exceptions例外情况托管异常被il2cpp.exe转换为c + +异常。我们选择了这条道路,以再次避免特定于平台的解决办法。当 il2cpp.exe 需要emit代码引发的托管的异常时,它将调用 il2cpp_codegen_raise_exception 函数。&在我们的 HelloWorld_Start_m3 方法来引发和捕捉托管的异常的代码如下所示:try{ // begin try (depth: 1)& InvalidOperationException_t7 * L_17 = (InvalidOperationException_t7 *)il2cpp_codegen_object_new (InitializedTypeInfo(&InvalidOperationException_t7_il2cpp_TypeInfo));& InvalidOperationException__ctor_m8(L_17, (String_t*) &_stringLiteral5, /*hidden argument*/&InvalidOperationException__ctor_m8_MethodInfo);& il2cpp_codegen_raise_exception(L_17);& // IL_0092: leave IL_00a8& goto IL_00a8;} // end try (depth: 1)catch(Il2CppExceptionWrapper& e){& __exception_local = (Exception_t8 *)e.& if(il2cpp_codegen_class_is_assignable_from (&InvalidOperationException_t7_il2cpp_TypeInfo, e.ex-&object.klass))& goto IL_0097;&}IL_0097:{ // begin catch(System.InvalidOperationException)& V_1 = ((InvalidOperationException_t7 *)__exception_local);& NullCheck(V_1);& String_t* L_18 = (String_t*)VirtFuncInvoker0& String_t* &::Invoke(&Exception_get_Message_m9_MethodInfo, V_1);& Debug_Log_m6(NULL /*static, unused*/, L_18, /*hidden argument*/&Debug_Log_m6_MethodInfo);// IL_00a3: leave IL_00a8& goto IL_00a8;} // end catch (depth: 1)&所有托管的异常将包装在 c + + 的Il2CppExceptionWrapper 类型内。当生成的代码捕获该类型的异常时,它解包 (其类型 Exception_t8) 的托管异常的 c + + 表示。我们期待在这种情况下,只为能反转,所以如果我们找不到该类型的异常的 c + + 异常的副本又扔了回来。如果我们找到正确的类型,该代码跳转到的 catch 处理程序,执行并写出的异常消息。Goto!?!&这段代码提出了一个有趣的点。这些标签和 goto 语句在那里做什么?这些构造是不必要的结构化编程 !然而,IL 没有结构化编程概念,如循环及 if/then 语句。因为它是较低级别,il2cpp.exe 遵循低级别概念生成的代码中。例如,让我们看看 for 循环在 HelloWorld_Start_m3 中的方法:&IL_00a8:{& V_2 = 0;& goto IL_00}IL_00af:{& ObjectU5BU5D_t4* L_19 = ((ObjectU5BU5D_t4*)SZArrayNew(ObjectU5BU5D_t4_il2cpp_TypeInfo_var, 1));& int32_t L_20 = V_2;& Object_t * L_21 =Box(InitializedTypeInfo(&Int32_t5_il2cpp_TypeInfo), &L_20);& NullCheck(L_19);& IL2CPP_ARRAY_BOUNDS_CHECK(L_19, 0);& ArrayElementTypeCheck (L_19, L_21);*((Object_t **)(Object_t **)SZArrayLdElema(L_19, 0)) = (Object_t *)L_21;& Debug_LogFormat_m7(NULL /*static, unused*/, (String_t*) &_stringLiteral6, L_19, /*hidden argument*/&Debug_LogFormat_m7_MethodInfo);& V_2 = ((int32_t)(V_2+1));}IL_00cc:{& if ((((int32_t)V_2) & ((int32_t)3)))& {&&& goto IL_00& }}&&这里的 V_2 变量是循环索引。是开始的一个值为 0,然后递增下面这一行中的循环:&V_2 = ((int32_t)(V_2+1));然后在这里检查循环的结束条件:&if ((((int32_t)V_2) & ((int32_t)3)))只要 V_2 是小于 3,goto 语句跳转到 IL_00af 标签,这是循环体的顶部。你可能能够猜出那 il2cpp.exe 器当前正在生成 c + + 代码直接从 IL,而无需使用中间的抽象语法树表示形式。如果您猜到这,你是正确的。你可能已经还注意到在运行时检查上述一些生成的代码看起来像这样:&float L_1 = (__this-&___x_1);&float L_2 = L_1;&显然,采用 L_2 变量在这里不是必要的。大多数 c + + 编译器可以优化掉这额外的任务,但是我们想要避免emitting它在所有。我们目前正在研究使用 AST 来更好地理解 IL 代码和生成更好的 c + + 代码涉及本地变量的情况下,for 循环,其中的可能性。Conclusion结论&我们只是抓到一个非常简单的项目的 IL2CPP 脚本后端所生成的 c + + 代码的表面。如果你没这么做过,我鼓励你来到您的项目中生成的代码。当你在探索,请牢记我们正在不断努力提高构建和运行时性能的 IL2CPP 脚本后端所生成的 c + + 代码将看上去不同的,未来版本中。&通过将 IL 代码转换为 c + + 中,我们已经能够获得很好的平衡,便携式和高性能代码之间。我们可以有很多不错的开发人员友好功能的托管代码中,同时仍获得 c + + 编译器提供各种平台的质量机器代码的好处。&在将来职位,我们会探索更多生成的代码,包括方法调用、 分享的方法实现和调用到本机库的包装。但下一次我们将调试一些为使用 Xcode iOS 64 位内部版本生成的代码。文章的源地址://il2cpp-internals-a-tour-of-generated-code/
相关搜索:
相关阅读:
相关频道:
Android教程最近更新}

我要回帖

更多关于 unity我的世界 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信