.NET NativeAOT 指南中,如何实现高效跨平台应用编译?

摘要:.NET NativeAOT 指南 随着 .NET 8 的发布,一种新的“时尚”应用模型 NativeAOT 开始在各种真实世界的应用中广泛使用。 除了对 NativeAOT 工具链的基本使用外,“NativeAOT”一词还带有原生世界的所
.NET NativeAOT 指南 随着 .NET 8 的发布,一种新的“时尚”应用模型 NativeAOT 开始在各种真实世界的应用中广泛使用。 除了对 NativeAOT 工具链的基本使用外,“NativeAOT”一词还带有原生世界的所有限制,因此您必须知道如何处理这些问题才能正确使用它。 在这篇博客中,我将讨论它们。 基本用法 使用 NativeAOT 非常简单,只需要在发布应用时使用 MSBuild 传递一个属性 PublishAot=true 即可。 通常,它可以是: dotnet publish -c Release -r win-x64 /p:PublishAot=true 其中 win-x64 是运行时标识符,可以替换为 linux-x64,osx-arm64 或其他平台。您必须指定它,因为 NativeAOT 需要为您指定的运行时标识符生成原生代码。 然后发布的应用可以在 bin/Release/<target framework>/<runtime identifier>/publish 中找到 关于编译 在讨论使用 NativeAOT 时可能遇到的各种问题的解决方案之前,我们需要稍微深入一点,看看 NativeAOT 是如何编译代码的。 我们经常听说 NativeAOT 会剪裁掉没有被使用的代码。而实际上,它并不像 IL 剪裁那样从程序集中剪裁掉不必要的代码,而是只编译代码中引用的东西。 NativeAOT 编译包括两个阶段: 扫描 IL 代码,构建整个程序视图(一个依赖图),其中包含所有需要编译的必要依赖节点。 对依赖图中的每个方法进行实际的编译,生成代码。 请注意,在编译过程中可能会出现一些“延迟”的依赖,因此上述两个阶段可能会交错出现。 这意味着,在分析过程中没有被计算为依赖的任何东西最终都不会被编译。 反射 依赖图是在编译期间静态构建的,这也意味着任何无法静态分析的东西都不会被编译。不幸的是,反射,即在不事先告诉编译器的情况下在运行时获取东西,正是编译器无法弄清楚的一件事。 NativeAOT 编译器有一些能力可以根据编译时的字面量来推断出反射调用需要什么东西。 例如: var type = Type.GetType("Foo"); Activator.CreateInstance(type); class Foo { public Foo() => Console.WriteLine("Foo instantiated"); } 上面的反射目标(即 Foo)可以被编译器弄清楚,因为编译器可以看到你试图获取类型 Foo,所以类型 Foo 会被标记为一个依赖,这导致 Foo 被编译到最终的产物中。 如果你运行这个程序,它会如预期地打印 Foo instantiated。 但是如果我们将代码改为如下: var type = Type.GetType(Console.ReadLine()); Activator.CreateInstance(type); class Foo { public Foo() => Console.WriteLine("Foo instantiated"); } 现在让我们用 NativeAOT 构建并运行这个程序,然后输入 Foo 来创建一个 Foo 的实例。你会立刻得到一个异常: Unhandled Exception: System.ArgumentNullException: Value cannot be null. (Parameter 'type') at System.ArgumentNullException.Throw(String) + 0x2b at System.ActivatorImplementation.CreateInstance(Type, Boolean) + 0xe7 ... 这是因为编译器无法看到你在哪里使用了 Foo,所以它根本不会为 Foo 生成任何代码,导致这里的 type 为 null。 此外,依赖分析是精确到单个方法的,这意味着即使一个类型被认为是一个依赖,如果该类型中的某个方法没有被使用,该方法也不会被包含在代码生成中。 虽然这可以通过将所有类型和方法添加到依赖图中来解决,这样编译器就会为它们生成代码。这就是 TrimmerRootAssembly 的作用:通过提供 TrimmerRootAssembly,NativeAOT 编译器会将你指定的程序集中的所有东西都作为根。 但是涉及泛型的情况就不是这样了。
阅读全文