Beibehaltene dynamische Assemblys in .NET
Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.
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. Neu in .NET 9 fügt die PersistedAssemblyBuilder Klasse eine vollständig verwaltete Reflection.Emit
Implementierung hinzu, 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.
Vorher PersistedAssemblyBuilder
konnten Sie nur eine generierte Assembly ausführen und sie nicht speichern. Da die Assembly nur im Arbeitsspeicher war, war es schwierig 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 PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) 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 nur auf derselben Laufzeitversion wie die Laufzeitversion ausgeführt wird, auf der der Compiler ausgeführt wird (in der Regel in proc), kann die Kernassembly einfachtypeof(object).Assembly
sein. Im folgenden Beispiel wird veranschaulicht, wie Sie eine Assembly in einem Stream erstellen und speichern und mit der aktuellen Laufzeitassembly ausführen:public static void CreateSaveAndRunAssembly() { 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 })); }
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ürcoreAssembly
. Mit diesem Wert kann der Generator auf einer .NET-Laufzeitversion ausgeführt und auf eine andere .NET-Laufzeitversion ausgerichtet werden. Sie sollten Typen verwenden, die von derMetadataLoadContext
Instanz beim Verweisen auf Kerntypen zurückgegeben werden. Suchen Sie z. B. anstelle vontypeof(int)
" denSystem.Int32
TypMetadataLoadContext.CoreAssembly
nach Dem Namen:"public static void CreatePersistedAssemblyBuilderCoreAssemblyWithMetadataLoadContext(string refAssembliesPath) { PathAssemblyResolver resolver = new PathAssemblyResolver(Directory.GetFiles(refAssembliesPath, "*.dll")); using MetadataLoadContext context = new MetadataLoadContext(resolver); Assembly coreAssembly = context.CoreAssembly; PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyDynamicAssembly"), coreAssembly); TypeBuilder typeBuilder = ab.DefineDynamicModule("MyModule").DefineType("Test", TypeAttributes.Public); MethodBuilder methodBuilder = typeBuilder.DefineMethod("Method", MethodAttributes.Public, coreAssembly.GetType(typeof(int).FullName), Type.EmptyTypes); // .. add members and save the assembly }
Festlegen des Einstiegspunkts für eine ausführbare Datei
Um den Einstiegspunkt für eine ausführbare Datei festzulegen 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 verwenden, um die Assembly mit den gewünschten Optionen zu generieren, z. B.:
public static void SetEntryPoint()
{
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);
}
Ausgeben von Symbolen und Generieren von PDB
Die Symbolmetadaten werden in den pdbBuilder
Ausgabeparameter aufgefüllt, wenn Sie die GenerateMetadata(BlobBuilder, BlobBuilder) Methode für eine PersistedAssemblyBuilder
Instanz aufrufen. So erstellen Sie eine Assembly mit einem tragbaren PDB:
- Erstellen Sie ISymbolDocumentWriter Instanzen mit der ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) Methode. Geben Sie beim Ausgeben der Il-Methode auch die entsprechenden Symbolinformationen aus.
- Erstellen Sie eine PortablePdbBuilder Instanz mithilfe der instanz, die
pdbBuilder
von der GenerateMetadata(BlobBuilder, BlobBuilder) Methode erstellt wird. - Serialisieren Sie die
PortablePdbBuilder
Datei in ein Blob, und schreiben Sie denBlob
In einen PDB-Dateidatenstrom (nur, wenn Sie einen eigenständigen PDB generieren). - Erstellen Sie eine DebugDirectoryBuilder Instanz, und fügen Sie eine DebugDirectoryBuilder.AddCodeViewEntry (eigenständige PDB) oder DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
- Legen Sie das optionale
debugDirectoryBuilder
Argument beim Erstellen der PEBuilder Instanz fest.
Das folgende Beispiel zeigt, wie Symbolinformationen ausgegeben und eine PDB-Datei generiert werden.
static void GenerateAssemblyWithPdb()
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
ModuleBuilder mb = ab.DefineDynamicModule("MyModule");
TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder mb1 = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]);
ISymbolDocumentWriter srcDoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp);
ILGenerator il = mb1.GetILGenerator();
LocalBuilder local = il.DeclareLocal(typeof(int));
local.SetLocalSymInfo("myLocal");
il.MarkSequencePoint(srcDoc, 7, 0, 7, 11);
...
il.Emit(OpCodes.Ret);
MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();
il2.BeginScope();
...
il2.EndScope();
...
tb.CreateType();
MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out _, out MetadataBuilder pdbBuilder);
MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken);
DebugDirectoryBuilder debugDirectoryBuilder = GeneratePdb(pdbBuilder, metadataBuilder.GetRowCounts(), entryPointHandle);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage, subsystem: Subsystem.WindowsCui),
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
debugDirectoryBuilder: debugDirectoryBuilder,
entryPoint: entryPointHandle);
BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
}
static DebugDirectoryBuilder GeneratePdb(MetadataBuilder pdbBuilder, ImmutableArray<int> rowCounts, MethodDefinitionHandle entryPointHandle)
{
BlobBuilder portablePdbBlob = new BlobBuilder();
PortablePdbBuilder portablePdbBuilder = new PortablePdbBuilder(pdbBuilder, rowCounts, entryPointHandle);
BlobContentId pdbContentId = portablePdbBuilder.Serialize(portablePdbBlob);
// In case saving PDB to a file
using FileStream fileStream = new FileStream("MyAssemblyEmbeddedSource.pdb", FileMode.Create, FileAccess.Write);
portablePdbBlob.WriteContentTo(fileStream);
DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder();
debugDirectoryBuilder.AddCodeViewEntry("MyAssemblyEmbeddedSource.pdb", pdbContentId, portablePdbBuilder.FormatVersion);
// In case embedded in PE:
// debugDirectoryBuilder.AddEmbeddedPortablePdbEntry(portablePdbBlob, portablePdbBuilder.FormatVersion);
return debugDirectoryBuilder;
}
Darüber hinaus können Sie die CustomDebugInformation Methode hinzufügen, indem Sie die MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) Methode aus der pdbBuilder
Instanz aufrufen, um erweiterte PDB-Informationen zur Quelleinbettung und Quellindizierung hinzuzufügen.
private static void EmbedSource(MetadataBuilder pdbBuilder)
{
byte[] sourceBytes = File.ReadAllBytes("MySourceFile2.cs");
BlobBuilder sourceBlob = new BlobBuilder();
sourceBlob.WriteBytes(sourceBytes);
pdbBuilder.AddCustomDebugInformation(MetadataTokens.DocumentHandle(1),
pdbBuilder.GetOrAddGuid(new Guid("0E8A571B-6926-466E-B4AD-8AB04611F5FE")), pdbBuilder.GetOrAddBlob(sourceBlob));
}
Hinzufügen von Ressourcen mit PersistedAssemblyBuilder
Sie können aufrufen MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) , um beliebig viele Ressourcen hinzuzufügen. Datenströme müssen mit einem BlobBuilder verkettet werden, den Sie an das ManagedPEBuilder Argument übergeben. Das folgende Beispiel zeigt, wie Sie Ressourcen erstellen und an die erstellte Assembly anfügen.
public static void SetResource()
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
ab.DefineDynamicModule("MyModule");
MetadataBuilder metadata = ab.GenerateMetadata(out BlobBuilder ilStream, out _);
using MemoryStream stream = new MemoryStream();
ResourceWriter myResourceWriter = new ResourceWriter(stream);
myResourceWriter.AddResource("AddResource 1", "First added resource");
myResourceWriter.AddResource("AddResource 2", "Second added resource");
myResourceWriter.AddResource("AddResource 3", "Third added resource");
myResourceWriter.Close();
BlobBuilder resourceBlob = new BlobBuilder();
resourceBlob.WriteBytes(stream.ToArray());
metadata.AddManifestResource(ManifestResourceAttributes.Public, metadata.GetOrAddString("MyResource"), default, (uint)resourceBlob.Count);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll),
metadataRootBuilder: new MetadataRootBuilder(metadata),
ilStream: ilStream,
managedResources: resourceBlob);
BlobBuilder blob = new BlobBuilder();
peBuilder.Serialize(blob);
using var fileStream = new FileStream("MyAssemblyWithResource.dll", FileMode.Create, FileAccess.Write);
blob.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.