System.Reflection.Emit.AssemblyBuilder-Klasse

Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.

Eine dynamische Assembly ist eine Assembly, die mithilfe der Reflection Emit-APIs erstellt wird. Eine dynamische Assembly kann auf Typen verweisen, die in einer anderen dynamischen oder statischen Assembly definiert sind. Sie können AssemblyBuilder verwenden, um dynamische Assemblys im Arbeitsspeicher zu generieren und deren Code während der gleichen Anwendungsausführung auszuführen. In .NET 9 haben wir eine neue PersistedAssemblyBuilder mit vollständig verwalteter Implementierung von Reflection Emit hinzugefügt, mit der Sie die Assembly in einer Datei speichern können. In .NET Framework können Sie beides ausführen– führen Sie die dynamische Assembly aus, und speichern Sie sie in einer Datei. Die für das Speichern erstellte dynamische Assembly wird als permanente Assembly bezeichnet, während die normale Nur-Speicherassembly als vorübergehend oder runnierbar bezeichnet wird. In .NET Framework kann eine dynamische Assembly aus einem oder mehreren dynamischen Modulen bestehen. In .NET Core und .NET 5+ kann eine dynamische Assembly nur aus einem dynamischen Modul bestehen.

Die Art und Weise, wie Sie eine AssemblyBuilder Instanz erstellen, unterscheidet sich für jede Implementierung, aber weitere Schritte zum Definieren eines Moduls, typs, einer Methode oder einer Enumeration sowie zum Schreiben von IL sind ziemlich ähnlich.

Ausführen von dynamischen Assemblys in .NET

Verwenden Sie die AssemblyBuilder.DefineDynamicAssembly Methode, um ein runnable-Objekt AssemblyBuilder abzurufen. Dynamische Assemblys können mit einem der folgenden Zugriffsmodi erstellt werden:

Der Zugriffsmodus muss angegeben werden, indem der entsprechende AssemblyBuilderAccess Wert im Aufruf der AssemblyBuilder.DefineDynamicAssembly Methode angegeben wird, wenn die dynamische Assembly definiert ist und später nicht geändert werden kann. Die Laufzeit verwendet den Zugriffsmodus einer dynamischen Assembly, um die interne Darstellung der Assembly zu optimieren.

Im folgenden Beispiel wird das Erstellen und Ausführen einer Assembly veranschaulicht:

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 }));
}

Beibehaltene dynamische Assemblys in .NET

Die AssemblyBuilder.Save API wurde ursprünglich nicht zu .NET (Core) portiert, da die Implementierung stark von windowsspezifischem systemeigenem Code abhängt, der ebenfalls nicht portiert wurde. In .NET 9 haben wir eine vollständig verwaltete Reflection.Emit Implementierung hinzugefügt, die das Speichern unterstützt. Diese Implementierung hat keine Abhängigkeit von der bereits vorhandenen, laufzeitspezifischen Reflection.Emit Implementierung. Das heißt, es gibt nun zwei verschiedene Implementierungen in .NET, die ausgeführt und beibehalten werden können. Wenn Sie die permanente Assembly ausführen möchten, speichern Sie sie zuerst in einem Speicherdatenstrom oder einer Datei, und laden Sie sie dann wieder. Da PersistedAssemblyBuilderSie zuvor nur eine generierte Assembly ausführen und sie nicht speichern konnten, war es schwierig, diese In-Memory-Assemblys zu debuggen. Vorteile des Speicherns einer dynamischen Assembly in einer Datei sind:

  • Sie können die generierte Assembly mit Tools wie ILVerify überprüfen oder dekompilieren und manuell mit Tools wie ILSpy untersuchen.
  • Die gespeicherte Assembly kann direkt geladen werden, sie muss nicht erneut kompiliert werden, wodurch die Startzeit der Anwendung verringert werden kann.

Verwenden Sie den public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null) Konstruktor, um eine PersistedAssemblyBuilder Instanz zu erstellen. Der coreAssembly Parameter wird verwendet, um Basislaufzeittypen aufzulösen und kann zum Auflösen der Referenzassemblyversionsverwaltung verwendet werden:

  • Wenn Reflection.Emit zum Generieren einer Assembly verwendet wird, die auf eine bestimmte TFM ausgerichtet ist, öffnen Sie die Referenzassemblys für das angegebene TFM, MetadataLoadContext und verwenden Sie den Wert der MetadataLoadContext.CoreAssembly -Eigenschaft für coreAssembly. Mit diesem Wert kann der Generator auf einer .NET-Laufzeitversion ausgeführt und auf eine andere .NET-Laufzeitversion ausgerichtet werden.

  • Wenn Reflection.Emit zum Generieren einer Assembly verwendet wird, die nur für dieselbe Laufzeitversion wie die Laufzeitversion ausgeführt wird, auf der der Compiler ausgeführt wird (in der Regel in proc), kann die Kernassembly sein typeof(object).Assembly. Die Referenzassemblys sind in diesem Fall nicht erforderlich.

Im folgenden Beispiel wird veranschaulicht, wie Sie eine Assembly in einem Datenstrom erstellen und speichern und ausführen:

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 }));
}

Um einen Einstiegspunkt für eine ausführbare Datei festzulegen und/oder andere Optionen für die Assemblydatei festzulegen, können Sie die public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) Methode aufrufen und die aufgefüllten Metadaten zum Generieren der Assembly mit den gewünschten Optionen verwenden, z. B.:

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); 

Hinweis

Die Metadatentoken für alle Member werden für den Save Vorgang aufgefüllt. Verwenden Sie die Token eines generierten Typs und seiner Member vor dem Speichern nicht, da sie Standardwerte aufweisen oder Ausnahmen auslösen. Es ist sicher, Token für Typen zu verwenden, auf die verwiesen wird, nicht generiert.

Einige APIs, die für das Emittieren einer Assembly nicht wichtig sind, werden nicht implementiert. Beispiel: GetCustomAttributes() ist nicht implementiert. Mit der Laufzeitimplementierung konnten Sie diese APIs nach dem Erstellen des Typs verwenden. Für die beibehaltenen AssemblyBuilder, sie werfen NotSupportedException oder NotImplementedException. Wenn Sie über ein Szenario verfügen, das diese APIs erfordert, geben Sie ein Problem im Dotnet/Runtime-Repository an.

Eine alternative Möglichkeit zum Generieren von Assemblydateien finden Sie unter MetadataBuilder.

Beibehaltene dynamische Assemblys in .NET Framework

In .NET Framework können dynamische Assemblys und Module in Dateien gespeichert werden. Zur Unterstützung dieses Features deklariert die AssemblyBuilderAccess Enumeration zwei zusätzliche Felder: Save und RunAndSave.

Die dynamischen Module in der dauerhaften dynamischen Assembly werden gespeichert, wenn die dynamische Assembly mithilfe der Save Methode gespeichert wird. Zum Generieren einer ausführbaren Datei muss die SetEntryPoint Methode aufgerufen werden, um die Methode zu identifizieren, die der Einstiegspunkt für die Assembly ist. Assemblys werden standardmäßig als DLLs gespeichert, es sei denn, die SetEntryPoint Methode fordert die Generierung einer Konsolenanwendung oder einer Windows-basierten Anwendung an.

Im folgenden Beispiel wird das Erstellen, Speichern und Ausführen einer Assembly mithilfe von .NET Framework veranschaulicht.

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");
}

Einige Methoden für die Basisklasse Assembly , z GetModules . B. und GetLoadedModules, funktionieren nicht ordnungsgemäß, wenn sie von AssemblyBuilder Objekten aufgerufen werden. Sie können die definierte dynamische Assembly laden und die Methoden für die geladene Assembly aufrufen. Um beispielsweise sicherzustellen, dass Ressourcenmodule in der zurückgegebenen Modulliste enthalten sind, rufen Sie das geladene Assembly Objekt aufGetModules. Wenn eine dynamische Assembly mehrere dynamische Module enthält, sollte der Manifestdateiname der Assembly mit dem Namen des Moduls übereinstimmen, der als erstes Argument der DefineDynamicModule Methode angegeben ist.

Die Signierung einer dynamischen Assembly mithilfe KeyPair einer dynamischen Assembly ist erst wirksam, wenn die Assembly auf dem Datenträger gespeichert wird. Daher funktionieren starke Namen nicht mit vorübergehenden dynamischen Assemblys.

Dynamische Assemblys können auf typen verweisen, die in einer anderen Assembly definiert sind. Eine vorübergehende dynamische Assembly kann sicher auf Typen verweisen, die in einer anderen vorübergehenden dynamischen Assembly, einer persistierbaren dynamischen Assembly oder einer statischen Assembly definiert sind. Die Common Language Runtime lässt jedoch nicht zu, dass ein persistentes dynamisches Modul auf einen Typ verweist, der in einem vorübergehenden dynamischen Modul definiert ist. Dies liegt daran, dass beim Laden des beibehaltenen dynamischen Moduls nach dem Speichern auf dem Datenträger die Verweise auf Typen, die im vorübergehenden dynamischen Modul definiert sind, nicht aufgelöst werden können.

Einschränkungen beim Ausstellen von Remoteanwendungen Standard

In einigen Szenarien muss eine dynamische Assembly erstellt und in einer Remoteanwendung ausgeführt werden Standard. Spiegelungsausstoß lässt nicht zu, dass eine dynamische Assembly direkt an eine Remoteanwendung ausgegeben wird Standard. Die Lösung besteht darin, die dynamische Assembly in der aktuellen Anwendung zu senden Standard, die ausgegebene dynamische Assembly auf dem Datenträger zu speichern und dann die dynamische Assembly in die Remoteanwendung zu laden Standard. Das Remoting und die Anwendung werden nur in .NET Framework unterstützt Standard.