重要提醒:
耐心了解基础理论知识,是准确、快速接入的前提条件!
磨刀不误砍柴工。
Unity 中的 .NET、Mono 和 IL2CPP_NRatel的博客-CSDN博客
关于HybridCLR | Focus Creative Games
一、示例项目 hybridclr_trial(旧)
1、下载并按照指引操作
指引:HybridCLRData/README.md
(目的是让Unity 使用 hybridclr 自己实现的 libil2cpp)
- 酌情修改 init_local_il2cpp_data.bat(或.sh)文件中代码
- `set IL2CPP_BRANCH=2020.3.33` 改成你的版本(目前只有2020.3.33或2021.3.1)
- `set IL2CPP_PATH=<你的Unity editor的il2cpp目录的路径>` 改成你的Unity安装目录
- 运行 init_local_il2cpp_data.bat 或.sh 文件 创建本地il2cpp目录,即 LocalIl2CppData 目录。
看到如下表示成功:
Cloning into 'hybridclr_repo'...
Cloning into 'il2cpp_plus_repo'...
...
succ
若出现:'git' 不是内部或外部命令,也不是可运行的程序或批处理文件。
下载安装git:Git for Windows
2、运行
⑴、LoaddDll.cs 中:
DownLoadDlls ==》LoadGameDll ==》RunMain;
(下载热更Dll ==》加载热更Dll ==》运行)
⑵、App.cs 的 Main 方法中:
LoadMetadataForAOTAssembly ==》TestAOTGeneric;
(为AOT程序集加载元数据 ==》 测试AOT泛型正常运行)
3、代码文件简析
⑴、RuntimeApi.cs
主要引入 C++ 程序集的方法 LoadMetadataForAOTAssembly(为AOT程序集加载元数据)
⑵、RefTypes.cs
避免 反射用到的代码 和 值类型泛型 被裁剪/剥离。
具体方式为:
①、使用 Preserve 特性
②、在方法中使用,但方法不被实际调用(仅为了告诉编译器有用,不要裁剪)
③、还可使用 link.xml 文件。
-----------------------------------
(https://focus-creative-games.github.io/hybridclr/code_striping/)
(https://focus-creative-games.github.io/hybridclr/aot_generic/)
⑶、BuildConfig.cs
配置 MonoHotUpdateDllNames、AllHotUpdateDllNames、AOTMetaDlls、AssetBundleFiles、各阶段输入输出路径等。
⑷、AssetBundleBuildHelper.cs
提供各平台(Win64、Win32、Android、IOS)构建 AssetBundle 的接口方法。
①、编译HotFixDlls
②、将HotFixDlls、AOTMetaDlls(需要提前BuildPlayer生成)、testPrefab(测试AOT补充元数据的预设)编入 notSceneAb 中。
(notSceneAb 是名为 common 的 AssetBundleBuild)
(ab 内的 assetNames 取各资源的相对路径)
③、调用 BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, ...) 构建 abs。(注意,并非全量接口)
④、将 abs 拷入 Application.streamingAssetsPath。
⑸、BuildPlayerHelper.cs
提供各平台(Win64、Win32、Android、IOS)构建 App 的接口方法。
最终调用 BuildPipeline.BuildPlayer。
注意,移动平台需要调用两次,第1次 Build App 是为了生成补充AOT元数据dll。
⑹、CompileDllHelper.cs
提供各平台(Win64、Win32、Android、IOS)编译 HotFixDlls 的方法。
最终调用 PlayerBuildInterface.CompilePlayerScripts。
⑺、MethodBridgeHelper.cs
提供各平台(Win64、Win32、Android、IOS)生成桥接函数的接口方法。
(桥接函数:HybridCLR 的 interpreter 与 AOT 之间需要双向函数调用)
(文档:https://focus-creative-games.github.io/hybridclr/method_bridge/)
⑻、BuildProcessor_2019.cs
在 OnFilterAssemblies 中,将热更dll从打包列表中移除。
在 OnPostprocessBuild 中(非安卓时),加回在OnFilterAssemblies 中移除的条目。
在 OnPostGenerateGradleAndroidProject(安卓时),加回在OnFilterAssemblies 中移除的条目。
⑼、BuildProcessor_2020_1_OR_NEWER.cs
在 OnFilterAssemblies 中,将热更dll从打包列表中移除。
在 OnPostprocessBuild 中(非安卓时),加回在OnFilterAssemblies 中移除的条目。
在 OnPostGenerateGradleAndroidProject(安卓时),加回在OnFilterAssemblies 中移除的条目。
在 OnBeforeConvertRun(仅2020中),拷贝 StripDlls。
(例安卓,从 Application.dataPath/../Library/Bee/artifacts/Android/ManagedStripped 拷贝到 Application.dataPath/../HybridCLRDataDir/AssembliesPostIl2CppStrip)
⑽、Generators 目录:生成桥接函数的库方法。
⑾、UnityBinFileReader 目录:Unity二进制文件的读写工具。
二、示例项目(新)(2022/10/19)
整体更方便易用了。
1、HybridCLR 整体改为通过 unitypackage 放入Unity。
编译 Dll、生成桥接函数已内置到其中。
2、安装入口改为了 Untiy菜单栏 HybridCLR/Installer... 。
3、配置方式改为了 Untiy菜单栏 HybridCLR/Settings。
4、使用上还是 下载 Dlls 和 预设ab、加载Dlls、实例化预设还原成功。但 Dlls 不再打入ab中。(Dll 本来就是序列化文件,可以不打AB)
5、BuildAssetsCommand.cs:对外提供方法 BuildAndCopyABAOTHotUpdateDlls
①、为 activeBuildTarget 打资源 ab。
②、拷贝 资源ab 到 Application.streamingAssets。
③、为 activeBuildTarget 编译 Dll。
④、拷贝 AOTAssemblies 到 Application.streamingAssets。
④、拷贝 HotUpdateAssemblies 到 Application.streamingAssets。
6、BuildPlayerCommand.cs:对外提供方法 Build_Win64
①、执行 PrebuildCommand.GenerateAll; 包括 link.xml、MethodBridge、AOTGenericReferences、ReversePInvokeWrapper。
②、执行 BuildPipeline.BuildPlayer
③、执行 BuildAssetsCommand.BuildAndCopyABAOTHotUpdateDlls(就是上面那5步)
④、将 Application.streamingAssets 整体复制到 Application.dataPath/../Release-Win64/HybridCLRTrial_Data/StreamingAssets。
暂未提供 移动平台的构建方法。
7、AOTGenericReferences.cs
根据当前热更新 dll 扫描出所有产生的AOT泛型类型及函数的实例化,并生成一个启发的泛型实例化文件。
自动生成的是注释的,可以不要,但影响性能。
生成后应该 打开注释并挪走,不然会被覆盖。
这个文件,本来不手加会在真机运行时报错(IL2CPP本身的机制)。
但 HybridCLR 利用 “基于补充元数据的泛型函数实例化技术” 在解释器里去运行,避免报错。
但 由于是解释执行,所以效率不高。
对于一些性能敏感的代码,提前泛型实例化可以明显提升性能。
三、实际接入要做什么?
1、理解 代码裁剪、AOT 泛型、桥接函数 这些重要问题的概念和处理流程(理论基础)。
AOT泛型问题 | Focus Creative Games
2、理解 AOT dll 和 热更 dll 的关系(理论基础)。
AOT dll 内容主要包含:程序主入口、支持资源和脚本热更的脚本 及 理论上不会变化的工具、插件等。热更 dll 需要 借助 AOT dll 才能完成热更。
理论上,AOT dll 不能热更,若有变化,需要重新打包。
但据Q群内消息(2022/11/1):HybridCLR开创性地实现了 `differential hybrid execution` 技术。即可以对AOT dll任意增删改,会智能地让变化或者新增的类和函数以interpreter模式运行,但未改动的类和函数以AOT方式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平。正在申请专利。
这意味着,可以不用刻意划分 AOT dll 和 热更 dll(清晰起见,还是建议划分)
以一个点为界,在 “某个方法” 调用之后,HybridCLR 会自动区分是否变化,以 AOT+interpreter 方式运行后续代码。(需要注意,“这个方法” 调用前的代码还是完全以 AOT 方式运行的。)
这可以解决一个纠结点:“业务框架” 要放AOT dll里还是热更Dll里?想在AOT里也用业务框架的一部分,又怕业务框架在上线后有改动(大概率会有改动)。
通常大家都会比较贪心!既希望一些底层代码能够被畅用(希望尽量放到AOT),又希望让更多的代码能够被热更(希望放到热更程序集)。很明显这是需要权衡的。
3、理解 MonoBehaviour 的挂载/还原限制(理论基础)。
热更新MonoBehaviour | Focus Creative Games
AOT 资源挂热更程序集中的脚本,不能还原。
因为它是逆向的、底层依赖高层的。
( 如下图:同级引用(水平方向)和 高引用低(斜向下)都是没问题的)
4、实际接入。
(按照文档:为现有项目添加HybridCLR支持 | Focus Creative Games)。
⑴、安装HybridCLR | Focus Creative Games
问题:【程序集划分】是否将 Assembly-CSharp 作为热更新程序集?
需权衡! 它似乎会默认直接依赖所有导入Untiy的运行时程序集。
这样的话,如果后期有人 直接 using 一个新插件的程序集,将不能在编辑器下发现,而是会在热更后出错。但不得不说,它是最简单方便的方式。
⑶、打包app(紫色为自己需要做的)
HybridCLR打包工作流 | Focus Creative Games
iOS平台打包 | Focus Creative Games
①、设置 UNITY_IL2CPP_PATH 环境变量。
(若开启HybridCLR,打包时自动)
②、排除热更新 assembly。
(根据设置,打包时自动)
③、将热更新 dll 名添加到 assembly 列表。
(根据设置,打包时自动)
④、将打包过程中生成的裁剪后的 aot dll 拷贝出来,供补充元数据使用。
(打包时会被自动复制到 HybridCLRData/AssembliesPostIl2CppStrip/{platform}
目录)
(应在热更打包资源时,将这些 dll 拷走)
⑤、编译热更新dll。
(应在热更打包资源时执行编译,并将这些 dll 拷走)
(使用 Unity 的 PlayerBuildInterface.CompilePlayerScripts
Api 进行编译)
⑥、生成一些打包需要的文件和代码。
(应在打包时调用 PrebuildCommand.GenerateAll() 生成)
(或 提前手动点击 HybridCLR/Generate/All 生成)
其中包括:扫描生成 link.xml、生成桥接函数、生成 AOT 泛型实例化代码、生成ReversePInvokeCallback 相关 wrapper文件。
⑦、iOS平台的特殊处理。
(打ios包前,需要自行手动替换xcode工程中的libil2cpp.a为扩充了HybridCLR代码libil2cpp.a)
⑷、将 热更新 dll(必选) 和 补充元数据AOT dll(可选) 纳入项目的热更新资源管理系统。
热更时,将 热更Dll 和 AOT Dll 拷到自己的热更目录。
使其可以上传到资源服务器,再更新到玩家设备。
⑸、加载补充元数据dll(可选)。
主要方法:RuntimeApi.LoadMetadataForAOTAssembly(dllBytes);
⑹、加载热更新dll。
主要方法:Assembly gameAss = System.Reflection.Assembly.Load(dllBytes.bytes);
注意,应按照程序集依赖顺序进行加载(被依赖的先加载)。
⑺、开始执行热更新代码逻辑。
加载包含热更新脚本的热更新场景或者实例化热更新prefab。
Unity自动会创建相应的热更新脚本并执行脚本函数。
这种办法使用简单,对项目改动最小,而且不会产生反射开销。(强烈推荐)
四、C# 编译效率问题
1、运行时触发编译卡死:
修改 Preferences/General/ScriptChangesWhilePlaying
2、脚本每次修改都触发资源刷新:
修改 Preferences/AssetPipeline/AutoRefresh 为 false,
然后使用 Ctrl + R 手动触发(Windows 下)
3、尽量减少 dll 的编译时间:
⑴、将 第三方库 或 不常改动的代码放入 Standard Assets 或 Plugins.
⑵、尽可能多的使用自定义程序集。