System.Reflection.Emit.AssemblyBuilder 类

本文提供了此 API 参考文档的补充说明。

动态程序集是使用反应发出 API 创建的程序集。 动态程序集可以引用在另一个动态或静态程序集中定义的类型。 可用于 AssemblyBuilder 在内存中生成动态程序集,并在相同的应用程序运行期间执行其代码。 在 .NET 9 中,我们添加了一个新的 PersistedAssemblyBuilder 反射发出完全托管的实现,允许将程序集保存到文件中。 在 .NET Framework 中,可以同时运行动态程序集并将其保存到文件中。 为保存而创建的动态程序集称为 持久 程序集,而常规的仅内存程序集称为 暂时 性程序集或 运行程序集。 在 .NET Framework 中,动态程序集可以包含一个或多个动态模块。 在 .NET Core 和 .NET 5+ 中,动态程序集只能包含一个动态模块。

为每个实现创建 AssemblyBuilder 实例的方式有所不同,但定义模块、类型、方法或枚举以及编写 IL 的进一步步骤非常相似。

.NET 中的可运行动态程序集

若要获取可 AssemblyBuilder 运行的对象,请使用 AssemblyBuilder.DefineDynamicAssembly 该方法。 可以使用以下访问模式之一创建动态程序集:

必须通过在定义动态程序集时在调用AssemblyBuilder.DefineDynamicAssembly方法中提供适当的AssemblyBuilderAccess值来指定访问模式,并且以后不能更改。 运行时使用动态程序集的访问模式来优化程序集的内部表示形式。

以下示例演示如何创建和运行程序集:

public void CreateAndRunAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
    ModuleBuilder mob = ab.DefineDynamicModule("MyModule");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder mb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
}

.NET 中的持久动态程序集

AssemblyBuilder.Save API 最初未移植到 .NET (Core),因为实现在很大程度上依赖于 Windows 特定的本机代码,而该本机代码也未移植。 在 .NET 9 中,我们添加了一个完全托管 Reflection.Emit 的实现,支持保存。 此实现不依赖于预先存在的特定于运行时的 Reflection.Emit 实现。 也就是说,现在 .NET 中有两个不同的实现,可运行且持久化。 若要运行持久化程序集,请先将其保存到内存流或文件中,然后将其加载回。 以前 PersistedAssemblyBuilder,由于只能运行生成的程序集而无法保存它,因此很难调试这些内存中程序集。 将动态程序集保存到文件的优点包括:

  • 可以使用 ILVerify 等工具验证生成的程序集,或者反编译,并使用 ILSpy 等工具手动检查它。
  • 可以直接加载保存的程序集,无需再次编译,这可以减少应用程序启动时间。

若要创建 PersistedAssemblyBuilder 实例,请使用 public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null) 构造函数。 该 coreAssembly 参数用于解析基本运行时类型,可用于解析引用程序集版本控制:

  • 如果Reflection.Emit用于生成面向特定 TFM 的程序集,请使用给定 TFM MetadataLoadContext 的引用程序集并使用 MetadataLoadContext.CoreAssembly 属性的值coreAssembly 此值允许生成器在一个 .NET 运行时版本上运行,并面向不同的 .NET 运行时版本。

  • 如果 Reflection.Emit 用于生成仅在运行时版本上执行的程序集,该程序集与编译器运行的运行时版本相同(通常是在过程内),则核心程序集可以是 typeof(object).Assembly。 在这种情况下,不需要引用程序集。

以下示例演示如何创建程序集并将其保存到流并运行它:

public void CreateSaveAndRunAssembly(string assemblyPath)
{
    PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
    ModuleBuilder mob = ab.DefineDynamicModule("MyModule");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder meb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = meb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    tb.CreateType();

    using var stream = new MemoryStream();
    ab.Save(stream);  // or pass filename to save into a file
    stream.Seek(0, SeekOrigin.Begin);
    Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(stream);
    MethodInfo method = assembly.GetType("MyType").GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
}

若要为可执行文件设置入口点和/或设置程序集文件的其他选项,可以调用 public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) 该方法并使用填充的元数据生成具有所需选项的程序集,例如:

PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
// ...
MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();
// ...
il2.Emit(OpCodes.Ret);
tb.CreateType();

MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage);

ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                header: peHeaderBuilder,
                metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
                ilStream: ilStream,
                mappedFieldData: fieldData,
                entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));

BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);

// in case saving to a file:
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream); 

注意

所有成员的元数据令牌都在操作上 Save 填充。 保存前不要使用生成的类型的标记及其成员,因为它们将具有默认值或引发异常。 对于引用的类型(未生成)使用令牌是安全的。

未实现某些不重要的发出程序集的 API;例如, GetCustomAttributes() 未实现。 使用运行时实现,可以在创建类型后使用这些 API。 对于持久化 AssemblyBuilder,他们抛出 NotSupportedExceptionNotImplementedException。 如果有需要这些 API 的方案,请将问题提交到 dotnet/runtime 存储库中。

有关生成程序集文件的替代方法,请参阅 MetadataBuilder

.NET Framework 中的持久动态程序集

在 .NET Framework 中,动态程序集和模块可以保存到文件中。 为了支持此功能, AssemblyBuilderAccess 枚举声明两个附加字段: SaveRunAndSave

使用 Save 该方法保存动态程序集时,将保存持久动态程序集中的动态模块。 若要生成可执行文件, SetEntryPoint 必须调用该方法来标识作为程序集入口点的方法。 默认情况下,程序集将保存为 DLL,除非 SetEntryPoint 该方法请求生成控制台应用程序或基于 Windows 的应用程序。

以下示例演示如何使用 .NET Framework 创建、保存和运行程序集。

public void CreateRunAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder mob = ab.DefineDynamicModule("MyAssembly.dll");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder meb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = meb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
    ab.Save("MyAssembly.dll");
}

Assembly 类上的某些方法(例如 GetModules ,从 GetLoadedModules对象调用 AssemblyBuilder 时)无法正常工作。 可以加载定义的动态程序集,并在加载的程序集上调用方法。 例如,为了确保资源模块包含在返回的模块列表中,请调用 GetModules 加载 Assembly 的对象。 如果动态程序集包含多个动态模块,则程序集的清单文件名应与指定为该方法的第一个参数 DefineDynamicModule 的模块名称匹配。

在将程序集保存到磁盘之前,动态程序集 KeyPair 的签名才有效。 因此,强名称不适用于暂时性动态程序集。

动态程序集可以引用在另一个程序集中定义的类型。 暂时性动态程序集可以安全地引用在另一个暂时性动态程序集、持久动态程序集或静态程序集中定义的类型。 但是,公共语言运行时不允许持久动态模块引用在暂时性动态模块中定义的类型。 这是因为在保存到磁盘后加载持久动态模块时,运行时无法解析对在暂时性动态模块中定义的类型的引用。

对向远程应用程序域发出限制

某些方案要求在远程应用程序域中创建和执行动态程序集。 反应ion 发出不允许将动态程序集直接发送到远程应用程序域。 解决方案是在当前应用程序域中发出动态程序集,将发出的动态程序集保存到磁盘,然后将动态程序集加载到远程应用程序域中。 远程处理和应用程序域仅在 .NET Framework 中受支持。