Unity热更新项目自动化生成link.xml:告别手动配置,精准对抗IL2CPP代码裁剪
1. 项目概述告别手动配置的繁琐时代如果你正在使用Unity的IL2CPP后端并且项目里集成了像HybridCLR这样的热更新方案那你一定对link.xml这个文件又爱又恨。爱它是因为它能保住你热更新代码里用到的那些AOT预先编译类型防止它们在IL2CPP的代码裁剪阶段被无情地“优化”掉从而避免游戏在热更新后出现各种诡异的“找不到类型”或“方法丢失”的运行时错误。恨它是因为维护这份link.xml文件简直就是一场与代码库同步的持久战。每次热更新DLL有变动比如新增了一个继承自MonoBehaviour的脚本或者调用了一个新的系统API你都得手动去分析这个DLL引用了哪些AOT程序集里的类型然后小心翼翼地把这些类型名、命名空间、甚至整个程序集以assembly和type节点的形式添加到link.xml里。这个过程不仅枯燥、容易出错而且随着项目迭代link.xml文件会变得越来越臃肿难以管理。更头疼的是你很难保证手动添加的列表是完整且精确的漏掉一个类型线上可能就是一次崩溃事故。所以当我看到“Unity热更新项目别再手动配link.xml了一个编辑器工具帮你搞定il2cpp代码裁剪”这个标题时简直感同身受。这说的不就是我们这些常年奋战在热更新一线的开发者最迫切需要的自动化解决方案吗这个工具的核心价值就是通过静态分析热更新DLL自动生成一份精确的link.xml文件把开发者从繁琐、易错的手动劳动中彻底解放出来让热更新工作流变得更加可靠和高效。无论你是刚接触HybridCLR的新手还是被link.xml折磨已久的老兵这个工具都能显著提升你的开发体验和项目稳定性。2. 核心原理IL2CPP代码裁剪与Link.xml的救赎要理解这个工具为什么如此重要我们得先搞清楚IL2CPP的代码裁剪机制以及link.xml在其中扮演的关键角色。2.1 IL2CPP的“瘦身”逻辑与副作用IL2CPP是Unity将C#/.NET代码转换为C代码再编译为原生机器码的脚本后端。它的一个核心优化手段叫做“代码裁剪”Code Stripping。这个功能的本意是好的在最终打包时分析整个项目的代码依赖树将那些从未被任何地方引用到的类、方法、属性等“死代码”统统剔除掉。这样一来最终的应用包体APK/IPA等体积会显著减小对于移动端平台来说这是至关重要的优化。然而这个“瘦身”过程对于动态性极强的热更新来说就成了一个巨大的挑战。热更新的核心思想是主包AOT部分发布后后续的代码逻辑可以通过下载新的DLL托管程序集来动态加载和执行。IL2CPP在打包主包时它只能看到并分析当时项目里的静态代码。对于那些未来才会通过热更新DLL引入的代码以及这些热更新代码所依赖的AOT类型IL2CPP是完全“看不见”的。举个例子你的主包里有一个AOT类GameManager。在热更新DLL里你写了一个新的NewFeature脚本它继承自MonoBehaviour并且在Start方法里创建了一个GameManager的实例。在打包主包时IL2CPP发现没有任何静态代码引用GameManager因为NewFeature还没被编译进项目它就会认为GameManager是“死代码”并将其从最终的二进制文件中裁剪掉。当玩家更新了热更包运行到NewFeature.Start()时游戏就会因为找不到GameManager类的元数据而崩溃。2.2 Link.xml给IL2CPP的“保留名单”link.xml文件就是Unity提供给开发者用来对抗这种过度裁剪的机制。你可以把它理解为一份给IL2CPP的“保留名单”。它的语法很简单主要就是告诉IL2CPP“嘿不管你有没有看到引用下面这些类型或者整个程序集你必须给我保留下来不许裁剪”一个典型的手动配置的link.xml可能长这样linker assembly fullnameUnityEngine.CoreModule preserveall/ assembly fullnamemscorlib type fullnameSystem.Collections.Generic.List1 preserveall/ /assembly type fullnameMyGame.AOT.GameManager preserveall/ /linkerassembly preserveall保留整个指定程序集的所有内容。type preserveall保留指定的特定类型。手动配置的痛点不全开发者需要自己遍历所有热更新脚本找出所有引用的AOT类型。这个过程极易遗漏尤其是那些通过反射、泛型、接口等间接方式使用的类型。不准为了“保险”开发者往往会选择保留整个程序集如UnityEngine.CoreModule。这确实能解决问题但代价是包体急剧膨胀因为很多根本用不到的代码也被保留了违背了代码裁剪的初衷。不智能每当热更新代码发生变更都需要重新审查和更新link.xml这是一个持续的、高维护成本的工作。2.3 自动化工具的解决思路而标题中提到的这个编辑器工具其核心思路就是“以子之矛攻子之盾”。既然IL2CPP是通过静态分析来决定裁剪什么那么我们同样可以在编辑器环境下对热更新DLL进行一轮静态分析。工具的工作流程可以概括为扫描在打包前工具读取所有指定的热更新DLL文件。分析利用.NET的反射机制或更底层的Cecil等库解析这些DLL的元数据遍历其中所有的类型、方法、字段分析它们的基类、接口、参数类型、返回值类型、属性类型等。追溯对于分析中找到的每一个类型引用工具会判断它是否来自于一个AOT程序集即那些会被IL2CPP编译进主包的程序集如UnityEngine.*,mscorlib,System,netstandard等。生成将所有识别出的、来自AOT程序集的类型引用按照link.xml的格式要求自动生成对应的assembly或type节点。输出将生成的XML内容写入到项目指定的link.xml文件路径中。这样生成的link.xml是精确且完整的。它只保留热更新代码真正用到的AOT类型既避免了运行时崩溃又最大程度地控制了包体大小。更重要的是这个过程是全自动的与你的构建流水线CI/CD集成后每次打包都能生成一份最新的、与当前热更新代码完全匹配的保留列表彻底解放了开发者的双手和脑力。3. 工具实战以HybridCLR的Generate/LinkXml为例理论很美好我们来看看具体怎么实现。实际上在热更新领域广受欢迎的HybridCLR框架其Package中已经内置了一个非常成熟的自动化link.xml生成工具这正是我们标题所指的“编辑器工具”的典范。我们就以它为例拆解整个配置和使用流程。3.1 环境准备与HybridCLR安装首先确保你的Unity项目已经正确安装并配置了HybridCLR。这里假设你已经有一个基本的HybridCLR热更新环境。安装HybridCLR Package通过Unity的Package Manager从Git URL添加HybridCLR的Package。地址通常是https://gitee.com/focus-creative-games/hybridclr_unity.git或对应的GitHub地址。安装后菜单栏会出现HybridCLR选项。运行Installer点击HybridCLR/Installer...工具会自动检测并安装对应你Unity编辑器版本的、修改过的il2cpp源码。这是HybridCLR运行的基础务必确保安装成功。配置热更新程序集这是关键一步。你需要告诉HybridCLR哪些是你打算进行热更新的代码程序集。打开HybridCLR/Settings。在Hot Update Assembly Definitions列表中拖入你的热更新模块的.asmdef文件。这是最推荐的方式因为它模块清晰不易出错。或者在Hot Update Assemblies列表中直接填写程序集名称不含.dll后缀例如你直接使用Assembly-CSharp作为热更新DLL。注意Hot Update Assembly Definitions和Hot Update Assemblies是等效的但不要将同一个程序集同时在两个列表中配置否则会报错。对于有.asmdef的模块优先使用拖拽方式。3.2 一键生成Link.xml配置好热更新程序集后生成link.xml就变得异常简单。编译热更新DLL首先你需要确保你的热更新代码已经编译成了DLL。HybridCLR提供了HybridCLR/Compile Dll/ActiveBuildTarget菜单可以一键编译当前激活平台的热更新DLL。编译后的DLL会输出到{Project}/HybridCLRData/HotUpdateDlls/{Platform}目录下。执行生成命令点击菜单栏的HybridCLR/Generate/LinkXml。查看输出命令执行成功后工具会自动在项目中生成link.xml文件。默认的生成路径是在Assets/LinkGenerator/link.xml这个路径可以在HybridCLR/Settings的Output Link File字段中自定义。生成的link.xml文件内容解析 打开生成的link.xml你会发现它不再是简单粗暴的preserveall。HybridCLR的工具非常智能它会进行更细粒度的分析。例如它可能不会保留整个UnityEngine.CoreModule而是只保留热更新DLL里实际用到的具体类型比如UnityEngine.GameObject、UnityEngine.Transform等。对于系统程序集如mscorlib它也会精确到泛型类型例如System.Collections.Generic.List1[[System.String, mscorlib]]。这种精度是手动配置几乎无法达到的。3.3 集成到打包流程手动点击菜单固然可以但为了自动化我们需要将其集成到项目的构建脚本中。HybridCLR的生成命令都提供了对应的API。你可以创建一个编辑器脚本例如BuildScript.cs来组织整个打包流程using UnityEditor; using HybridCLR.Editor; public static class BuildScript { [MenuItem(MyTools/Build Game)] public static void BuildGame() { // 1. 生成必要的定义版本宏等 HybridCLR.Editor.Commands.GenerateIl2CppDefCommand.GenerateIl2CppDef(); // 2. 编译热更新DLL针对目标平台例如Android HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(BuildTarget.Android); // 3. 自动生成 link.xml HybridCLR.Editor.Commands.GenerateLinkXmlCommand.GenerateLinkXml(); // 4. 可选生成裁剪后的AOT DLL和桥接函数为补充元数据做准备 // HybridCLR.Editor.Commands.GenerateAOTDllsCommand.GenerateAOTDlls(); // HybridCLR.Editor.Commands.GenerateMethodBridgeCommand.GenerateMethodBridge(); // 5. 执行Unity常规的构建流程 BuildPlayerOptions options new BuildPlayerOptions(); options.scenes new[] { Assets/Scenes/Main.unity }; options.locationPathName Build/MyGame.apk; options.target BuildTarget.Android; options.options BuildOptions.None; BuildPipeline.BuildPlayer(options); } }将这个脚本放到Assets/Editor目录下你就可以通过MyTools/Build Game菜单一键完成从生成link.xml到打出APK/IPA的完整流程。在CI/CD服务器上你也可以通过命令行调用相应的API。3.4 高级配置与自定义HybridCLR的Generate/LinkXml命令已经足够智能但有些特殊场景你可能需要干预。自定义输出路径在HybridCLR/Settings中修改Output Link File字段。强烈建议不要指向Assets/link.xml因为Unity项目根目录的link.xml通常用于手动保留一些特殊类型如通过反射调用的、工具分析不到的类型。让自动生成的文件和手动维护的文件分开可以避免覆盖。处理外部DLL如果你的热更新DLL是在Unity工程外部编译的例如一个独立的Visual Studio项目你可以在HybridCLR/Settings的External Hot Update Assembly Dirs中配置这些DLL所在的目录。工具在生成link.xml时会自动扫描这些目录。与“补充元数据”工作流结合Generate/LinkXml通常是HybridCLR完整工作流的一环。在它之后你通常还需要运行Generate/AotDlls来获取裁剪后的AOT DLL用于Consistent模式补充元数据以及Generate/MethodBridge来生成桥接函数。Generate/All命令可以一键执行所有这些步骤。4. 避坑指南与实战经验即使有了自动化工具在实际项目集成过程中依然会遇到一些坑。下面是我在多个项目中总结出来的常见问题和解决方案。4.1 类型裁剪了但工具没扫到这是最让人头疼的问题。明明热更新代码里用了某个AOT类型但生成的link.xml里却没有导致游戏崩溃。可能的原因和排查思路如下间接引用与动态调用场景热更新代码通过Type.GetType(“MyType”)、Assembly.Load、Activator.CreateInstance或者依赖注入框架动态创建类型。分析静态分析工具只能分析代码中的显式类型引用如变量声明、继承、方法参数等。对于这种运行时才决定的字符串形式的类型名工具是无法追溯的。解决对于这类已知的、会被动态调用的AOT类型必须在手动维护的link.xml例如项目根目录的Assets/link.xml中进行保留。这是自动化工具的盲区需要开发者手动补全。泛型序列化/反序列化场景使用JsonUtility.FromJsonListMyData(json)或MessagePack等序列化库反序列化一个泛型类型。分析工具在分析FromJsonT这个方法调用时T是ListMyData但MyData可能是一个AOT类型。有些分析工具能处理这种简单的泛型参数但如果嵌套过深或逻辑复杂可能会遗漏。解决检查生成的link.xml如果发现MyData类型没有被保留手动将其添加到保留列表中。更稳妥的方式是将所有用于序列化的AOT数据类都加入手动保留名单。通过接口或基类引用场景热更新代码中有一个变量声明为IAOTInterface接口或AOTBaseClass基类但实际运行时赋值的是热更新子类的实例。然而这个接口或基类本身是AOT类型。分析好的静态分析工具应该能识别出对接口和基类的依赖并将其保留。HybridCLR的工具在这方面做得不错。验证检查生成的link.xml确认相关的接口或基类是否已被包含。如果没有可能需要手动添加。排查流程建议 当出现“TypeLoadException”或“MissingMethodException”时首先查看错误日志确定是哪个类型或方法找不到。然后去热更新DLL中搜索这个类型或方法是在哪里被使用的。对比自动生成的link.xml看该类型或其所在的程序集是否被保留。如果没保留分析使用场景是否属于上述“静态分析盲区”。如果是将其添加到手动link.xml中。4.2 生成的Link.xml过于庞大有时候工具可能会保留比你预期更多的类型甚至接近保留整个程序集。这通常发生在公共基础库或框架的广泛引用如果你的热更新代码大量使用了某个AOT框架比如一个完整的UI框架或网络框架那么引用它的所有基础类型都会被保留导致link.xml很大。工具分析策略有些工具为了“绝对安全”可能会采取更保守的策略例如当分析到一个类型来自某个AOT程序集时直接保留该程序集下所有相关的命名空间。应对策略接受并优化首先包体大小的增加是否在可接受范围内如果影响不大这其实是最省心的方式确保了绝对的安全。精细化手动调整如果包体压力很大你可以以自动生成的link.xml为基础进行手动精简。但这需要你对代码依赖有非常深的理解风险较高。通常的做法是将自动生成的文件作为“基线”然后与一个长期维护的、更精简的手动文件进行合并或对比。审视热更新设计是否有些过于庞大、通用的AOT框架被放到了热更新依赖中考虑能否将这些框架的核心部分也做成可热更的或者对框架进行模块化拆分减少热更新代码对它的直接依赖。4.3 多平台构建的注意事项不同平台的IL2CPP裁剪行为可能略有差异虽然大部分情况下link.xml是通用的但仍有需要注意的地方平台特定的程序集例如UnityEngine.AndroidJNI只在Android平台有用UnityEngine.iOS只在iOS平台有用。如果你的热更新代码通过条件编译#if UNITY_ANDROID引用了它们那么在为不同平台生成link.xml时需要确保编译了对应平台的热更新DLL。HybridCLR的Compile Dll命令可以指定目标平台BuildTarget务必为每个要发布的平台编译对应的DLL并生成link.xml。统一的Link.xml管理一种推荐的做法是为每个主要平台Android, iOS, Standalone生成独立的link.xml文件例如link_android.xml,link_ios.xml。然后在打包脚本中根据当前构建平台将对应的文件复制或重命名为项目所需的link.xml。这样可以确保每个平台都得到最精确的保留列表。4.4 与Addressable等资源管理系统的协同如果你的项目使用了Addressables进行资源热更可能会遇到资源上挂载的热更新脚本丢失的问题。这是因为Addressables打包时也需要知道哪些脚本包括热更新脚本是有效的。关键点确保热更新程序集的名称被添加到了Unity的“脚本汇编列表”中。HybridCLR的打包处理器PatchScriptingAssemblyList已经自动帮你做了这件事。它会在打包时将你在HybridCLR Settings中配置的所有热更新程序集名称写入到ScriptingAssemblies.json等配置文件中。这样Unity资源数据库和Addressables系统就能识别这些热更新脚本从而正确地在资源中引用和实例化它们。你通常不需要为此额外配置link.xml但需要确保HybridCLR的配置是正确的。5. 超越Link.xmlHybridCLR完整工作流中的其他自动化这个自动生成link.xml的工具其实是HybridCLR庞大而精密的自动化工作流中的一环。理解它与其它环节的关系能让你更好地驾驭整个热更新体系。5.1 生成裁剪后的AOT Dlls (Generate/AotDlls)link.xml解决了“保留什么”的问题而Generate/AotDlls则解决了“用什么来补充”的问题。在HybridCLR的Consistent一致模式下你补充给运行时的元数据DLL必须与主包中裁剪后的AOT DLL在结构上完全一致。这个命令的作用就是模拟IL2CPP的打包过程在一个临时工程中生成一份裁剪后的AOT DLL。这些DLL就是后续调用RuntimeApi.LoadMetadataForAOTAssembly时需要加载的字节码来源。它的执行依赖于link.xml因为模拟裁剪过程需要依据link.xml来决定保留哪些内容。5.2 生成桥接函数 (Generate/MethodBridge)这是为了解决函数指针回调问题。当你的热更新C#函数需要被原生代码比如C插件或者Lua这样的脚本引擎回调时你需要为这个C#函数生成一个对应的、固定的C函数指针。IL2CPP在AOT编译时会为所有标记了[MonoPInvokeCallback]特性的函数生成桥接。但是热更新代码是动态加载的IL2CPP在打包主包时并不知道它们的存在。Generate/MethodBridge命令会在编辑器阶段扫描所有热更新DLL中标记了[MonoPInvokeCallback]的函数并提前为它们生成好桥接函数代码编译进主包。这样当热更新函数被加载后就能获得正确的函数指针。5.3 生成AOT泛型引用 (Generate/AOTGenericReference)泛型是IL2CPP代码裁剪和热更新的另一个复杂点。对于Listint和ListstringIL2CPP会生成两份不同的代码。如果热更新中使用了ListMyAOTType而MyAOTType是一个AOT类型那么ListMyAOTType这个泛型实例必须在AOT代码中存在。Generate/AOTGenericReference命令会扫描热更新DLL找出所有引用的AOT泛型实例如Dictionarystring, GameObject然后生成一个包含所有可能需要的泛型实例化引用的C#文件AOTGenericReferences.cs。这个文件里的代码是注释状态它像一个“购物清单”告诉你需要在AOT代码中提前实例化哪些泛型。你可以选择性地将其中性能关键的部分取消注释确保它们在主包中被编译从而避免热更新时的泛型共享泛型函数调用带来的性能开销。5.4 完整的工作流串联一个健壮的HybridCLR热更新项目打包流程应该是这样的// 伪代码展示逻辑顺序 1. HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(target); // 编译热更DLL 2. HybridCLR.Editor.Commands.GenerateIl2CppDefCommand.GenerateIl2CppDef(); // 生成版本定义 3. HybridCLR.Editor.Commands.GenerateLinkXmlCommand.GenerateLinkXml(); // **生成link.xml** 4. HybridCLR.Editor.Commands.GenerateAOTDllsCommand.GenerateAOTDlls(); // 生成裁剪后AOT DLL需步骤3的link.xml 5. HybridCLR.Editor.Commands.GenerateMethodBridgeCommand.GenerateMethodBridge(); // 生成桥接函数需步骤1的热更DLL和步骤4的AOT DLL 6. // 可选HybridCLR.Editor.Commands.GenerateAOTGenericReferenceCommand.GenerateAOTGenericReference(); 7. BuildPipeline.BuildPlayer(...); // 执行Unity构建可以看到Generate/LinkXml是承上启下的关键一步。它确保了后续生成裁剪DLL和桥接函数的依据是准确的。HybridCLR提供的Generate/All命令就是按这个顺序一键执行所有步骤。6. 自定义工具开发如果现有工具不满足需求虽然HybridCLR的工具已经非常强大但有些团队可能有更特殊的需求比如需要与公司内部的自研热更框架集成。需要对分析过程有更精细的控制例如排除某些特定模式的分析。想将这个过程深度集成到自研的构建平台中。这时你可能需要自己动手丰衣足食。开发一个自定义的link.xml生成工具核心就是程序集元数据扫描。技术选型Mono.Cecil这是最强大、最流行的.NET程序集分析库。它可以无损地读取、修改和写入.NET程序集提供了极其丰富的API来遍历类型、方法、字段、属性、引用等。它是实现静态分析的首选。System.Reflection.NET自带的反射API。它也能加载程序集并分析类型但功能上不如Cecil强大和灵活且对于某些只有路径没有加载到当前AppDomain的程序集处理起来不如Cecil方便。一个简易的自定义工具实现思路using Mono.Cecil; using Mono.Cecil.Cil; using System.Collections.Generic; using System.IO; using System.Xml.Linq; public class LinkXmlGenerator { public void Generate(string hotUpdateDllPath, string outputPath) { // 1. 加载热更新DLL var resolver new DefaultAssemblyResolver(); // 添加Unity引擎和.NET框架程序集的搜索路径确保能解析到AOT引用 resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(object).Assembly.Location)); resolver.AddSearchDirectory(C:\Program Files\Unity\Hub\Editor\2022.3\Editor\Data\Managed\UnityEngine); var readerParams new ReaderParameters { AssemblyResolver resolver }; var hotUpdateAssembly AssemblyDefinition.ReadAssembly(hotUpdateDllPath, readerParams); HashSetstring aotAssembliesToPreserve new HashSetstring(); HashSetstring aotTypesToPreserve new HashSetstring(); // 2. 遍历所有模块、类型、方法 foreach (var module in hotUpdateAssembly.Modules) { foreach (var type in module.Types) { ProcessType(type, aotAssembliesToPreserve, aotTypesToPreserve); } } // 3. 生成link.xml XElement linker new XElement(linker); foreach (var asmName in aotAssembliesToPreserve) { // 这里可以根据策略决定是保留整个程序集还是具体类型 // 简单策略对于UnityEngine和System核心集保留整个程序集更安全 if (asmName.StartsWith(UnityEngine.) || asmName mscorlib || asmName System) { linker.Add(new XElement(assembly, new XAttribute(fullname, asmName), new XAttribute(preserve, all))); } else { // 对于其他程序集可以尝试只保留用到的类型需要更复杂的类型-程序集映射 } } foreach (var typeName in aotTypesToPreserve) { // 注意typeName需要是完整的类型名包括命名空间和程序集限定名可能更准确 // 例如: MyGame.AOT.GameManager, MyGame.AOT linker.Add(new XElement(type, new XAttribute(fullname, typeName), new XAttribute(preserve, all))); } XDocument doc new XDocument(new XDeclaration(1.0, utf-8, null), linker); doc.Save(outputPath); } private void ProcessType(TypeDefinition type, HashSetstring assemblies, HashSetstring types) { // 处理基类 if (type.BaseType ! null IsAOTAssembly(type.BaseType.Scope.Name)) { assemblies.Add(type.BaseType.Scope.Name); types.Add(GetFullTypeName(type.BaseType)); } // 处理实现的接口 foreach (var iface in type.Interfaces) { if (IsAOTAssembly(iface.InterfaceType.Scope.Name)) { assemblies.Add(iface.InterfaceType.Scope.Name); types.Add(GetFullTypeName(iface.InterfaceType)); } } // 处理方法... foreach (var method in type.Methods) { ProcessMethod(method, assemblies, types); } // 处理字段、属性... } private void ProcessMethod(MethodDefinition method, HashSetstring assemblies, HashSetstring types) { // 处理返回值类型 if (IsAOTAssembly(method.ReturnType.Scope.Name)) { assemblies.Add(method.ReturnType.Scope.Name); types.Add(GetFullTypeName(method.ReturnType)); } // 处理参数类型 foreach (var param in method.Parameters) { if (IsAOTAssembly(param.ParameterType.Scope.Name)) { assemblies.Add(param.ParameterType.Scope.Name); types.Add(GetFullTypeName(param.ParameterType)); } } // 处理方法体中的指令更深入的分析如局部变量、调用等 if (method.HasBody) { foreach (var instruction in method.Body.Instructions) { if (instruction.Operand is MemberReference memberRef) { if (IsAOTAssembly(memberRef.DeclaringType.Scope.Name)) { assemblies.Add(memberRef.DeclaringType.Scope.Name); types.Add(GetFullTypeName(memberRef.DeclaringType)); } } } } } private bool IsAOTAssembly(string assemblyName) { // 判断一个程序集是否属于AOT程序集 // 通常包括: mscorlib, System.*, netstandard, UnityEngine.*, Unity.*, 以及你自己项目的主模块非热更新模块 return assemblyName.StartsWith(UnityEngine.) || assemblyName.StartsWith(System.) || assemblyName mscorlib || assemblyName netstandard || assemblyName MyGame.Main; // 你的主工程程序集 } private string GetFullTypeName(TypeReference typeRef) { // 构造一个link.xml认可的完整类型名 // 注意处理泛型如 System.Collections.Generic.List1[[System.String, mscorlib]] return typeRef.FullName; } }这个示例只是一个起点真正的生产级工具需要考虑泛型、嵌套类型、数组类型、通过接口和基类的间接引用、以及如何智能地决定是保留整个程序集还是单个类型等复杂情况。但它的核心流程加载DLL - 遍历分析 - 判断AOT引用 - 输出XML是通用的。开发自定义工具的最大价值在于你可以完全控制分析的粒度和策略使其完美契合你项目的特定架构和需求。