HotFix原理学习 IL2CPP 学习

原文链接:Unity 游戏用XLua的HotFix实现热更原理揭秘-CSDN博客

本文通过对XLua的HoxFix使用原理的研究揭示出来这样的一套方法。这个方法的

  • 第一步:通过对C#的类与函数设置Hotfix标签。来标识需要支持热更的类和函数。
  • 第二步:生成函数连接器来连接LUA脚本与C#函数。
  • 第三步:在C#脚本编译结束后,使用Mono提供的一套C#的API函数,对已经编译过的.Net体系生成的DLL文件进行修改。
  • 第四步,通过LUA脚本修改C#带有标签的类中静态变量,把代码的执行路径修改到LUA脚本中。通过这套方案可以实现对已经标识的C#代码进行动态更新。

基础准备
知识准备

CIL: 通用中间语言(Common Intermediate Language,简称CIL), 是一种属于通用语言架构和 .NET 框架的低阶(lowest-level)的人类可读的编程语言。目标为 .NET 框架的语言被编译成CIL(基于.NET框架下的伪汇编语言,原:MSIL),这是一组可以有效地转换为本机代码且独立于 CPU 的指令。CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的。它运行在CLR上(类似于JVM),其主要支持的语言有C#、VisualBasic .NET、C++/CLI以及 J#(集成这些语言向CIL的编译功能)。

在编译.NET编程语言时,源代码被翻译成CIL码,而不是基于特定平台或处理器的目标代码。CIL是一种独立于具体CPU和平台的指令集,它可以在任何支持.NET framework的环境下运行。CIL码在运行时被检查并提供比二进制代码更好的安全性和可靠性。在Unity3D中,是用过Mono虚拟机来实现运行这些中间语言指令的。

之前写一篇介绍过一篇使用微软的API函数,利用中间语言生成或注入.NET支持下的DLL。这里就不在赘述,需要了解的请参考《使用MSIL采用Emit方式实现C#的代码生成与注入》。

IL2CPP: 直接理解把IL中间语言转换成CPP文件。根据官方的实验数据,换成IL2CPP以后,程序的运行效率有了1.5-2.0倍的提升。引用地址:Unity将来时:IL2CPP是什么?有了Mono为什么还需要IL2CPP?_unity windows il2cpp什么时候用-CSDN博客

使用Mono的时候,脚本的编译运行如下图所示:

简单的来说,3大脚本被编译成IL,在游戏运行的时候,IL和项目里其他第三方兼容的DLL一起,放入Mono VM虚拟机,由虚拟机解析成机器码,并且执行

IL2CPP做的改变由下图红色部分标明:

在得到中间语言IL后,使用IL2CPP将他们重新变回C++代码,然后再由各个平台的C++编译器直接编译成能执行的原生汇编代码。

一,Lua基础之热更新

首先,什么是热更新

  • 字面意思就是对lua的一些代码进行更新,在介绍热更新之前,我想要和大家分享一下lua的require的机制
  • 我们知道lua加载一个文件的方式可以有:dofile,loadfile以及 require。其中loadfile是只编译不执行,dofile和require是同时编译和执行。而dofile和require的区别是dofile同一个文件每次都要加载,也就是说,dofile两次返回来的是两个不同的地址。而require同一个文件,不管多少次都是都返回同一个地址,其原因是lua的地址缓存在了package.load()中。所以效率比dofile要高许多,因而现在一般都是用require加载文件。
  • 那么问题来了,如果我在lua文件中改变了一些数值(产生了新的地址),结果你却用之前的地址,那岂不是白给了吗?

于是热更新机制应运而生。其实现方式有两种:

(1)简单版但是有缺陷

package.load(“modelname”) = nil-- 修改modelname.lua的数据require(“modelname”)
  • 既然你有缓存,我直接置为空不就好了吗?然后重新require一次把修改好的加进来。这样子做的话第二次require的数据可能是正确的,但是之前require过一次的数值却仍然错误,所以说程序除非在之前没有加载过这个文件,否则得到的结果不完善。

(2)复杂版但是很有用

function reload_module(module_name)local old_module = package.loaded[module_name] or {}package.loaded[module_name] = nilrequire (module_name)local new_module = package.loaded[module_name]for k, v in pairs(new_module) doold_module[k] = vendpackage.loaded[module_name] = old_modulereturn old_module
end
  • 简单来说就是使用一个全局表存储了新修改后的所有数值,然后循环赋值给旧的值,这样就可以确保同一个旧地址也可以得到正确的数据。

要点分析

Lua 语言

  • 再热更新功能开发过程中,我们需要用到一款新的语言:Lua语言。

  • Lua和C#对比:C#是编译型语言,Lua是解析型语言

  • Lua语言不可以单独完成一个项目的开发,Lua语言出现的目的是“嵌入式”,为其他语言开发出来的项目进行功能的扩展和补丁的更新。

2.Lua语言与C#语言交互

  • Unity项目是使用C#开发的,后续热更新的功能需要使用Lua语言实现。而我们在最开始使用C#开发项目的时候,需要预留和Lua代码的“交互接口”,这就涉及到两门语言的代码相互调用访问。

3.AssetBundle

  • AssetBundle是Unity内资源的一种打包格式,和电脑上的rar、zip压缩包比较类似,客户端热更新过程中,从服务器上下载下来的资源,都是AssetBundle打包过的资源。

4.ULua和XLua热更新框架

  • ULua和XLua是两个热更新框架,专门用于Unity客户端项目热更新开发。其实就是两个“资源包”,导入到我们的项目中,在框架的基础之上,完成我们项目需要的热更新逻辑。

3.Lua热更新的实现

  • 1.将模块中旧的函数替换成新的函数,这个新的函数可以放到一个lua文件中,或者以字符串的形式给出。
  • 2.将模块中旧的函数,当前用到的所有上值,(什么是上值,后面有讲到)保存到起来,用于新函数引用,保证新函数作为模块中的一部分能够正确运行。

下面以一个demo为例,这也是抽取 snax 模块中热更新部分:

./main.lua                调用 test.lua,做为运行文件,显示最终运行效果
./test.lua                一个简单模块文件,用于提供热更新的来源
./test_hot.lua            用于更新替换 test 模块中的某些函数,更新文件
./hotfix.lua              实现热更新机制

通过这幅关系图,可以了解到,test 模块和 test_hot 之间的关系,test_hot 负责更新 test 模块中的某些函数,但更新后的这些函数依然属于 test 模块中的一部分,并没有脱离 test 模块的掌控,而独立出来。

  • 现在我们看看 test.lua 包含了哪些内容,分别有 一个局部变量 index,两个函数 print_index,show ,函数体分别是圆圈1和2,两个函数都引用到了这个局部变量 index。
  • 假设当前,我们想更新替换掉 print_index 函数,让其 index 加1 操作,并打印 index 值,那么我们可以在 test_hot.lua 文件中这么写,见下图黄色框部分:
     

  • 我们希望在 print_index 更新后, index 加 1 后,show 函数获取到的 index 值是 1,即把更新函数也看作是 test.lua 模块中的一部分。而不应该是 index 加 1 后,show 函数获取到的还是原值 0。
  • 假设我们希望更新 print_index 后,再一次更新,把 index 值直接设置为 100,那么它又应该是这样子的,见下图最左侧黄色部分:

4._ENV 环境变量
  • 在 lua 程序设计一书中有过这样的解释,lua 语言并没有全局变量,所谓的全局变量都是通过某种手段模拟出来的。
Lua 语言是在一个名为 _ENV 的预定义上值(一个外部的局部变量,upvalue)存在的情况下编译所有的代码段的。因此,所有的变量要么绑定到一个名称的局部变量,要么是 _ENV 中的一个字段,而 _ENV 本身是一个局部变量。
例如:
local z = 10
x = 0
y = 1
x = y + z
等价于
local z = 10
_ENV.x = 0
_ENV.y = 1
_ENV.x = _ENV.y + z
  • x,y 都是不用 local 声明,z 是 local 声明。
  • 所以,我们用到的全局变量其实是保存到 _ENV 变量中。lua 语言在内部维护了一个表来作用全局环境(_G),通常,我们在 load 一个代码段,一个模块时,lua 会用这个表(_G)来初始化 _ENV。如果上面的几行代码是写在一个文件中,那么当 load 调用它时,又会等价于:
-- xxx.lua 文件
local _ENV = the global environment (全局环境)
return function(...)
local z = 10
_ENV.x = 0
_ENV.y = 1
_ENV.x = _ENV.y +z
end
5.上值 upvalue

从这里开始不是很懂,之后再复盘吧 ------------------------------------------------------------------------------

当一个局部变量被内层的函数中使用的时候, 它被内层函数称作上值,或是外部局部变量。引用 Lua 5.3 参考手册
例如:
local x = 10
function hello(a, b)
local c = a + b + x
print(c)
end
那么在这段代码中,hello 函数的上值有 变量 x,_ENV,而我们刚刚讲到,print 没有经过声明,就可以直接使用,那么它肯定是保存于 _ENV 表中,print(c) 等价于 _ENV.print(c),而变量 a、b、c 都是做为 hello 函数的局部变量。
6.热更新函数Lua的require函数
  • Lua的require(modelname)把一个lua文件加载存放到package.loaded[modelname]中,重复require同一个模块实际还是沿用第一次加载的chunk。因此,很容易想到,第一个版本的热更新模块可以写成这样:
--强制重新载入module  
function require_ex( _mname )  log( string.format("require_ex = %s", _mname) )  if package.loaded[_mname] then  log( string.format("require_ex module[%s] reload", _mname))  end  package.loaded[_mname] = nil  require( _mname )  
end  
  • 可以看到,强制地require新的模块来更新新的代码,非常简单暴力。但是,显然问题很多,旧的引用住的模块无法得到更新,全局变量需要用"a = a or 0"这种约定来保留等等。这种程度的热更新显然不能满足现在的游戏开发需求。
7.热更新函数Lua的setenv函数

setenv是Lua 5.1中可以改变作用域的函数,或者可以给函数的执行设置一个环境表,如果不调用setenv的话,一段lua chunk的环境表就是_G,即Lua State的全局表,print,pair,require这些函数实际上都存储在全局表里面。那么这个setenv有什么用呢?我们知道loadstring一段lua代码以后,会经过语法解析返回一个Proto,Lua加载任何代码chunk或function都会返回一个Proto,执行这个Proto就可以初始化我们的lua chunk。为了让更新的时候不污染_G的数据,我们可以给这个Proto设置一个空的环境表。同时,我们可以保留旧的环境表来保证之前的引用有效。

local Old = package.loaded[PathFile]  
local func, err = loadfile(PathFile)  
--先缓存原来的旧内容  
local OldCache = {}  
for k,v in pairs(Old) do  OldCache[k] = v  Old[k] = nil  
end  
--使用原来的module作为fenv,可以保证之前的引用可以更新到  
setfenv(func, Old)()  
8.热更新函数Lua的debug库函数

Lua的函数是带有词法定界的first-class value,即Lua的函数与其他值(数值、字符串)一样,可以作为变量、存放在表中、作为传参或返回。通过这样实现闭包的功能,内嵌的函数可以访问外部的局部变量。这一特性给Lua带来强大的编程能力同时,其函数也不再是单一无状态的函数,而是连同外部局部变量形成包含各种状态的闭包。如果热更新缺少了对这种闭包的更新,那么可用性就大打折扣。
下面讲一下热更新如何处理旧的数据,还有闭包的upvalue的有效性问题怎么解决。这时候强大的Lua debug api上场了,调用debug库的getlocal函数可以访问任何活动状态的局部变量,getupvalue函数可以访问Lua函数的upvalues,还有相对应的修改函数。
例如,这是查询和修改函数局部变量写的debug函数:

-- 查找函数的local变量  
function get_local( func, name )  local i=1  local v_name, value  while true do  v_name, value = debug.getlocal(func,i)  if not v_name or v_name == name then  break  end  i = i+1  end  if v_name and v_name == name then  return value  end  return nil  
end  
-- 修改函数的local变量  
function set_local( func, name, value )  local i=1  local v_name  while true do  v_name, _ = debug.getlocal(func,i)  if not v_name or v_name == name then  break  end  i = i+1  end  if not v_name then  return false  end  debug.setlocal(func,i,value)  return true  
end  

一个函数的局部变量的位置实际上在语法解析阶段就已经能确定下来了,这时候生成的opcode就是通过寄存器的索引来找到局部变量的,了解这一点应该很容易理解上面的代码。

9.深度递归替换所有的upvalue
  • 接下来要做的事情很清晰了,递归所有的upvalue,根据一定的替换规则替换就可以,注意新的upvalue需要设置回原来的环境表。
function UpdateUpvalue(OldFunction, NewFunction, Name, Deepth)  local OldUpvalueMap = {}  local OldExistName = {}  -- 记录旧的upvalue表  for i = 1, math.huge do  local name, value = debug.getupvalue(OldFunction, i)  if not name then break end  OldUpvalueMap[name] = value  OldExistName[name] = true  end  -- 新的upvalue表进行替换  for i = 1, math.huge do  local name, value = debug.getupvalue(NewFunction, i)  if not name then break end  if OldExistName[name] then  local OldValue = OldUpvalueMap[name]  if type(OldValue) ~= type(value) then          -- 新的upvalue类型不一致时,用旧的upvalue  debug.setupvalue(NewFunction, i, OldValue)  elseif type(OldValue) == "function" then     -- 替换单个函数  UpdateOneFunction(OldValue, value, name, nil, Deepth.."    ")  elseif type(OldValue) == "table" then          -- 对table里面的函数继续递归替换  UpdateAllFunction(OldValue, value, name, Deepth.."    ")  debug.setupvalue(NewFunction, i, OldValue)  else  debug.setupvalue(NewFunction, i, OldValue)     -- 其他类型数据有改变,也要用旧的  end  else  ResetENV(value, name, "UpdateUpvalue", Deepth.."    ")     -- 对新添加的upvalue设置正确的环境表  end  end  
end  
10.实例分析
  • 下面就来看下具体 demo 的实现。
-- main.lua
local hotfix = require "hotfix"
local test =  require "test"
local test_hot = require "test_hot"print("before hotfix")
for i = 1, 5 do test.print_index() -- 热更前,调用 print_index,打印 index 的值
end hotfix.update(test.print_index, test_hot) -- 收集旧函数的上值,用于新函数的引用,这个对应之前说的归纳第2小点
test.print_index = test_hot -- 新函数替换旧的函数,对应之前说的归纳第1小点print("after hotfix")
for i = 1, 5 do test.print_index() -- 打印更新后的 index 值
end test.show() -- show 函数没有被热更,但它获取到的 index 值应该是 最新的,即 index = 5。
  • 接下来看看 test.lua 模块内容:
-- test.lua
local test = {}
local index = 0 function test.print_index()print(index)
end function test.show( )print("show:", index)
endreturn test
  • 再看看 热更文件 test_hot.lua 内容:
-- test_hot.lua
local index -- 这个 index 必须声明,不用赋值,才能够引用到 test 模块中的局部变量 indexreturn function ()  -- 返回一个闭包函数,这个就是要更新替换后的原型index = index + 1print(index)
end
  • 最后,再看看 hotfix.lua:
-- hotfix.lua
local hotfix = {}local function collect_uv(f, uv)local i = 1while true dolocal name, value = debug.getupvalue(f, i)if name == nil then -- 当所有上值收集完时,跳出循环breakendif not uv[name] thenuv[name] = { func = f, index = i } -- 这里就会收集到旧函数 print_index 所有的上值,包括变量 indexif type(value) == "function" thencollect_uv(value, uv)endendi = i + 1end
endlocal function update_func(f, uv) local i = 1while true dolocal name, value = debug.getupvalue(f, i)if name == nil then -- 当所有上值收集完时,跳出循环breakend-- value 值为空,并且这个 name 在 旧的函数中存在if not value and uv[name] then local desc = uv[name]-- 将新函数 f 的第 i 个上值引用旧模块 func 的第 index 个上值debug.upvaluejoin(f, i, desc.func, desc.index)end-- 只对 function 类型进行递归更新,对基本数据类型(number、boolean、string) 不管if type(value) == "function" thenupdate_func(value, uv)endi = i + 1end
endfunction hotfix.update(old, new)local uv = {}collect_uv(old, uv)update_func(new, uv)
endreturn hotfix
  • 这个用到了 lua 的两个 api 函数,在 Lua 5.3 参考手册 中有介绍。
debug.getupvalue (f, up)
此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。
debug.upvaluejoin (f1, n1, f2, n2)
让 Lua 闭包 f1 的第 n1 个上值 引用 Lua 闭包 f2 的第 n2 个上值。
  • 我们可以看到, hotfix.lua 做的事也是比较简单的,主要是收集 旧函数的所有上值,更新到新函数中。最后一步替换旧函数是在 main.lua 中完成。
  • 最后看看运行结果:
[root@instance test]# lua main.lua
before hotfix
0
0
0
0
0
after hotfix
1
2
3
4
5
-------------
show:   5

四、Lua脚本热更新方案

  • 热更新,通俗点说就是补丁,玩家那边知道重启客户端就可以更新到了的,不用卸载重新安装app,相对于单机游戏,这也是网络游戏用得比较多的一个东西吧。

  • 首先,大概流程如下:

  • luaFileList.json文件内容一般是lua文件的键值对,key为lua文件路径+文件名,value为MD5值:

五、lua热更新

1.什么是热更新
  • 热更新也叫不停机更新,是在游戏服务器运行期间对游戏进行更新。实现不停机修正bug、修改游戏数据等操作
2.热更新原理第一种:
  • lua中的require会阻止多次加载相同的模块。所以当需要更新系统的时候,要卸载掉响应的模块。(把package.loaded里对应模块名下设置为nil,以保证下次require重新加载)并把全局表中的对应的模块表置 nil 。同时把数据记录在专用的全局表下,并用 local 去引用它。初始化这些数据的时候,首先应该检查他们是否被初始化过了。这样来保证数据不被更新过程重置。

原文链接:Unity将来时:IL2CPP是什么? - 知乎 (zhihu.com)

IL


啰 嗦完了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

  • 1.Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成
  • 2.Mono版本授权受限
  • 3.提高运行效率

几点注意:
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开发的游戏代码,就不用担心了。因此就这点而言,我们开发上几乎 不会感到什么问题。

原文链接:【Unity游戏开发】Mono和IL2CPP的区别 - 知乎 (zhihu.com)

二、Mono介绍

Mono是一个由 Xamarin公司所主持的自由开放源码项目。
Mono的目标是在尽可能多的平台上使.net标准的东西能正常运行的一套工具,核心在于“跨平台的让.net代码能运行起来“。
Mono组成组件:C# 编译器,CLI虚拟机,以及核心类别程序库。
Mono的编译器 负责生成符合公共语言规范的映射代码,即公共中间语言(Common Intermediate Language, CIL),我的理解就是工厂方法实现不同解析。
IL科普
IL的全称是 Intermediate Language,很多时候还会看到 CIL(特指在.Net平台下的IL标准)。翻译过来就是中间语言。
它是一种属于通用语言架构和.NET框架的低阶的人类可读的编程语言。
CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的,它运行在虚拟机上(.Net Framework, Mono VM)的语言。

2.1 工作流程

  1. 通过C#编译器mcs,将C#编译为IL(中间语言,byte code)
  2. 通过Mono运行时中的编译器将IL编译成对应平台的原生码

2.2 知识点

2.2.1. 编译器

C#编译器mcs:将C#编译为 IL
Mono Runtime编译器:将IL转移为 原生码

2.2.2. 三种转译方式

即时编译(Just in time,JIT):程序运行过程中,将CIL的byte code转译为目标平台的原生码。
提前编译(Ahead of time,AOT):程序运行之前,将.exe或.dll文件中的CIL的byte code部分转译为目标平台的原生码并且存储,程序运行中仍有部分CIL的byte code需要JIT编译。
完全静态编译(Full ahead of time,Full-AOT):程序运行前,将所有源码编译成目标平台的原生码。

2.2.3 Unity跨平台的原理

Mono运行时编译器支持将IL代码转为对应平台原生码
IL可以在任何支持CLI,通用语言环境结构)中运行,IL的运行是依托于Mono运行时。

2.2.4 IOS不支持jit编译原因

机器码被禁止映射到内存,即封存了内存的可执行权限,变相的封锁了jit编译方式. 详情见

2.2.5 JIT编译

将IL代码转为对应平台原生码并且将原生码映射到虚拟内存中执行。JIT编译的时候IL是在依托Mono运行时,转为对应的原生码后在依托本地运行。

2.3 优点

  1. 构建应用非常快
  2. 由于Mono的JIT(Just In Time compilation ) 机制, 所以支持更多托管类库
  3. 支持运行时代码执行

三、IL2CPP【AOT编译】

IL2CPP分为两个独立的部分:
1. AOT(静态编译)编译器:把IL中间语言转换成CPP文件
2. 运行时库:例如 垃圾回收、线程/文件获取(独立于平台,与平台无关)、内部调用直接修改托管数据结构的原生代码的服务与抽象

3.1 AOT编译器

IL2CPP AOT编译器名为il2cpp.exe。
在Windows上,您可以在 Editor \ Data \ il2cpp目录中找到它。
在OSX上,它位于Unity安装的 Contents / Frameworks / il2cpp / build目录中
il2cpp.exe 是由C#编写的受托管的可执行程序,它接受我们在Unity中通过Mono编译器生成的托管程序集,并生成指定平台下的C++代码。

IL2CPP工具链:

3.2 运行时库

IL2CPP技术的另一部分是运行时库(libil2cpp),用于支持IL2CPP虚拟机的运行。
这个简单且可移植的运行时库是IL2CPP技术的主要优势之一!
通过查看我们随Unity一起提供的libil2cpp的头文件,您可以找到有关libil2cpp代码组织方式的一些线索
您可以在Windows的 Editor \ Data \ PlaybackEngines \ webglsupport \ BuildTools \ Libraries \ libil2cpp \ include目录中找到它们
或OSX上的 Contents / Frameworks / il2cpp / libil2cpp目录。

3.3 为啥要转成CPP呢?

  1. 运行效率快
根据官方的实验数据,换成IL2CPP以后,程序的运行效率有了1.5-2.0倍的提升。

2. Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成

Mono的跨平台是通过Mono VM实现的,有几个平台,就要实现几个VM,像Unity这样支持多平台的引擎,Mono官方的VM肯定是不能满足需求的。所以针对不同的新平台,Unity的项目组就要把VM给移植一遍,同时解决VM里面发现的bug。这非常耗时耗力。这些能移植的平台还好说,还有比如WebGL这样基于浏览器的平台。要让WebGL支持Mono的VM几乎是不可能的。

3. 可以利用现成的在各个平台的C++编译器对代码执行编译期优化,这样可以进一步减小最终游戏的尺寸并提高游戏运行速度

4. 由于动态语言的特性,他们多半无需程序员太多关心内存管理,所有的内存分配和回收都由一个叫做GC(Garbage Collector)的组件完成。

虽然通过IL2CPP以后代码变成了静态的C++,但是内存管理这块还是遵循C#的方式,这也是为什么最后还要有一个 IL2CPP VM的原因:它负责提供诸如GC管理,线程创建这类的服务性工作。

但是由于去除了IL加载和动态解析的工作,使得IL2CPP VM可以做的很小并且使得游戏载入时间缩短

3.5 优点

  1. 相比Mono, 代码生成有很大的提高
  2. 可以调试生成的C++代码
  3. 可以启用引擎代码剥离(Engine code stripping)来减少代码的大小
  4. 程序的运行效率比Mono高,运行速度快
  5. 多平台移植非常方便
  6. 相比Mono构建应用慢
  7. 只支持AOT(Ahead of Time)编译

四、Mono与IL2CPP的区别

IL2CPP比较适合开发和发布项目 ,但是为了提高版本迭代速度,可以在开发期间切换到Mono模式(构建应用快)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/713524.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

好视通视频会议系统存在任意文件读取漏洞复现 [附POC]

漏洞简介 好视通视频会议是由深圳市华视瑞通信息技术有限公司开发,其在国内率先推出了3G互联网视频会议,并成功应用于SAAS领域。 资产 FOFA:app"好视通-视频会议" POC GET /register/toDownload.do?fileName../../../../../../../../../.…

代码随想录-回溯算法

组合 //未剪枝 class Solution {List<List<Integer>> ans new ArrayList<>();Deque<Integer> path new LinkedList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n, k, 1);return ans;}public void back…

MySql安全加固:可信IP地址访问控制 设置密码复杂度

MySql安全加固&#xff1a;可信IP地址访问控制 & 设置密码复杂度 1.1 可信IP地址访问控制1.2 设置密码复杂度 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1.1 可信IP地址访问控制 当您在创建用户时使用’%作为主机部分&#xff0c;…

java数据结构与算法刷题-----LeetCode437. 路径总和 III(前缀和必须掌握)

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 深度优先2. 前缀和 1. 深度优先 解题思路&#xff1a;时间复…

kibana7.17.7 将数据导出csv文件

配置kibana文件 首先先配置kibana.yaml内容如下&#xff0c;这里假设我的服务器ip地址为192.168.130.128&#xff0c;elasticsearch的ip地址为&#xff1a;192.168.130.129:9200&#xff0c;192.168.130.130:9200&#xff1a; server.host: "192.168.130.128" serv…

Mac 以SH脚本安装Arthas

SH脚本安装Aethas curl -L https://alibaba.github.io/arthas/install.sh | sh安装脚本说明 示例源文件&#xff1a; #! /bin/bash# temp file of as.sh TEMP_ARTHAS_FILE"./as.sh.$$"# target file of as.sh TARGET_ARTHAS_FILE"./as.sh"# update timeo…

Android挖取原图手指触点区域RectF(并框线标记)放大到ImageView宽高与矩阵mapRadius,Kotlin

Android挖取原图手指触点区域RectF(并框线标记)放大到ImageView宽高与矩阵mapRadius&#xff0c;Kotlin 这里 Android挖取原图中心区域RectF(并框线标记)放大到ImageView宽高&#xff0c;Kotlin-CSDN博客 实现的是把原图中心区域的一片小图挖取出来放大放到下面的ImageView里面…

if语句用法

if语句是单条件分支语句 定义&#xff1a;根据一个条件来控制程序执行流程(如图3.2)。 语法格式&#xff1a; if&#xff08;表达式&#xff09;{ 若干语句 } ★注意★&#xff1a; ① 表达式的值必须是boolean 型&#xff1b; ② 不能用0代表false&#xff1b;用1代表 true&am…

德人合科技 | —数据泄露可能会对公司造成哪些影响?

数据泄露可能会对公司造成多方面的影响&#xff0c;以下是一些可能的影响&#xff1a; 财务损失&#xff1a;数据泄露可能导致公司遭受财务损失。攻击者可能会盗取公司的敏感信息&#xff0c;如客户信息、银行账户信息、商业机密等&#xff0c;并利用这些信息进行欺诈、盗窃等非…

本地maven库缓存导入私库

为了加速编译代码&#xff0c;想将本地maven缓存导入内网私库使用。 脚本网上搜的 #!/bin/bash # copy and run this script to the root of the repository directory containing files # this script attempts to exclude uploading itself explicitly so the script name …

高效备考2024年AMC10:吃透2000-2023年1250道AMC10真题

距离2024年AMC10的比赛只有8个月多一点的时间了&#xff0c;如何备考AMC10美国数学竞赛最有效&#xff1f;参加AMC10竞赛是否一定要参加机构的培训班&#xff1f;吃透历年真题是有效的自学、了解AMC10和备考策略之一。事实上&#xff0c;网络上有很多关于AMC10的学习资源&#…

Github 2024-03-02 开源项目日报Top9

根据Github Trendings的统计&#xff0c;今日(2024-03-02统计)共有9个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量非开发语言项目2Rust项目1JavaScript项目1Shell项目1C项目1TypeScript项目1C#项目1Python项目1 任天堂Switch模…

决定西弗吉尼亚州地区版图的关键历史事件

决定西弗吉尼亚州地区版图的关键历史事件&#xff1a; 1. 内部分裂与美国内战&#xff1a; - 在1861年美国内战爆发时&#xff0c;弗吉尼亚州作为南方邦联的一员宣布退出美利坚合众国。然而&#xff0c;弗吉尼亚州西部的一些县由于经济结构&#xff08;主要是农业非依赖奴隶制…

Redis 存储原理和数据模型

redis 是不是单线程 redis 单线程指的是命令处理在一个单线程中。主线程 redis-server&#xff1a;命令处理、网络事件的监听。 辅助线程 bio_close_file&#xff1a;异步关闭大文件。bio_aof_fsync&#xff1a;异步 aof 刷盘。bio_lazy_free&#xff1a;异步清理大块内存。io_…

一种基于三角剖分划分白区范围的白平衡算法

常规的白平衡算法中,一般会通过标准色温的R/G-B/G建议色温坐标系,然后在该坐标系中设定白区范围,对落入到白区范围的R/G/B进行加权统计处理,输出给到软件进行白平衡的增益计算。 所介绍的这篇专利利用三角剖分的算法,在划定的白区范围内,利用各个标准色温光源下所标定的白…

STM32------分析GPIO寄存器

一、初始LED原理图 共阴极led LED发光二极管&#xff0c;需要有电流通过才能点亮&#xff0c;当有电压差就会产生电流 二极管两端的电压差超过2.7v就会有电流通过 电阻的作用 由于公式IV/R 不加电阻容易造成瞬间电流无穷大 发光二极管工作电流为10-20MA 3.3v / 1kΩ 3.…

C#中什么是非托管代码?托管代码和非托管代码有什么区别

在C#中&#xff0c;托管代码和非托管代码是两种不同类型的代码&#xff0c;它们在内存管理和执行环境上有所不同。 托管代码&#xff08;Managed Code&#xff09;&#xff1a; 托管代码是由.NET运行时&#xff08;CLR&#xff0c;Common Language Runtime&#xff09;管理和执…

新能源汽车产业架构设计与实现:引领未来出行新风向

随着环保意识的增强和能源结构的转型&#xff0c;新能源汽车产业正迅速崛起成为汽车行业的新宠。构建一个完善的新能源汽车产业架构对于推动产业发展、提升竞争力至关重要。本文将从设计原则、关键技术、产业生态等方面&#xff0c;探讨如何设计与实现新能源汽车产业架构。 ##…

那些壁纸,不只是背景

1、方小童在线工具集 网址&#xff1a; 方小童 该网站是一款在线工具集合的网站&#xff0c;目前包含PDF文件在线转换、随机生成美女图片、精美壁纸、电子书搜索等功能&#xff0c;喜欢的可以赶紧去试试&#xff01;

【快速选择】解决TopK问题

目录 一、什么是TopK问题 二、优先级队列 优先级队列介绍 代码实现 三、使用优先级队列解决TopK问题 四、快速选择算法解决TopK问题 快速选择 图解快速选择 代码解决前k小个元素 五、优先级队列与快速选则算法比较 优先级队列 快速选择 一、什么是TopK问题 TopK问题…