Persistente dynamische assembly's in .NET
In dit artikel vindt u aanvullende opmerkingen in de referentiedocumentatie voor deze API.
De AssemblyBuilder.Save API is oorspronkelijk niet overgezet naar .NET (Core), omdat de implementatie sterk afhankelijk is van systeemeigen Windows-code die ook niet is overgezet. Nieuw in .NET 9 voegt de PersistedAssemblyBuilder klasse een volledig beheerde Reflection.Emit
implementatie toe die ondersteuning biedt voor opslaan. Deze implementatie is niet afhankelijk van de bestaande, runtimespecifieke Reflection.Emit
implementatie. Dat wil gezegd, er zijn nu twee verschillende implementaties in .NET, die kunnen worden uitgevoerd en persistent zijn. Als u de persistente assembly wilt uitvoeren, moet u deze eerst opslaan in een geheugenstroom of een bestand en vervolgens weer laden.
Voorheen PersistedAssemblyBuilder
kon u alleen een gegenereerde assembly uitvoeren en deze niet opslaan. Omdat de assembly alleen in het geheugen was, was het moeilijk om fouten op te sporen. Voordelen van het opslaan van een dynamische assembly in een bestand zijn:
- U kunt de gegenereerde assembly verifiëren met hulpprogramma's zoals ILVerify of decompileren en deze handmatig onderzoeken met hulpprogramma's zoals ILSpy.
- De opgeslagen assembly kan rechtstreeks worden geladen, hoeft niet opnieuw te worden gecompileerd, waardoor de opstarttijd van de toepassing kan worden verlaagd.
Gebruik de PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) constructor om een PersistedAssemblyBuilder
exemplaar te maken. De coreAssembly
parameter wordt gebruikt om basisruntimetypen op te lossen en kan worden gebruikt voor het omzetten van referentieassemblyversiebeheer:
Als
Reflection.Emit
wordt gebruikt om een assembly te genereren die alleen wordt uitgevoerd op dezelfde runtimeversie als de runtimeversie waarop de compiler wordt uitgevoerd (meestal in-proc), kan de kernassembly gewoontypeof(object).Assembly
zijn. In het volgende voorbeeld ziet u hoe u een assembly maakt en opslaat in een stream en deze uitvoert met de huidige runtime-assembly: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 })); }
Als
Reflection.Emit
wordt gebruikt om een assembly te genereren die is gericht op een specifieke TFM, opent u de referentieassembly's voor de opgegeven TFM met behulpMetadataLoadContext
van en gebruikt u de waarde van de eigenschap MetadataLoadContext.CoreAssembly voorcoreAssembly
. Met deze waarde kan de generator worden uitgevoerd op één .NET Runtime-versie en een andere .NET Runtime-versie gebruiken. U moet typen gebruiken die door hetMetadataLoadContext
exemplaar worden geretourneerd wanneer u naar kerntypen verwijst. Zoek bijvoorbeeld in plaats vantypeof(int)
hetSystem.Int32
type opMetadataLoadContext.CoreAssembly
naam: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 }
Invoerpunt instellen voor een uitvoerbaar bestand
Als u het invoerpunt voor een uitvoerbaar bestand wilt instellen of andere opties voor het assemblybestand wilt instellen, kunt u de public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
methode aanroepen en de ingevulde metagegevens gebruiken om de assembly te genereren met de gewenste opties, bijvoorbeeld:
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);
}
Symbolen verzenden en PDB genereren
De metagegevens van symbolen worden ingevuld in de pdbBuilder
outparameter wanneer u de GenerateMetadata(BlobBuilder, BlobBuilder) methode voor een PersistedAssemblyBuilder
exemplaar aanroept. Een assembly maken met een draagbare PDB:
- Maak ISymbolDocumentWriter exemplaren met de ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) methode. Wanneer u de IL van de methode verzendt, verzendt u ook de bijbehorende symboolgegevens.
- Maak een PortablePdbBuilder exemplaar met behulp van het
pdbBuilder
exemplaar dat door de GenerateMetadata(BlobBuilder, BlobBuilder) methode is geproduceerd. - Serialiseer de
PortablePdbBuilder
in een Bloben schrijf deBlob
naar een PDB-bestandsstroom (alleen als u een zelfstandige PDB genereert). - Maak een DebugDirectoryBuilder exemplaar en voeg een DebugDirectoryBuilder.AddCodeViewEntry (zelfstandige PDB) of DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
- Stel het optionele
debugDirectoryBuilder
argument in bij het maken van het PEBuilder exemplaar.
In het volgende voorbeeld ziet u hoe u symboolgegevens verzendt en een PDB-bestand genereert.
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;
}
Verder kunt u toevoegen CustomDebugInformation door de MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) methode aan te roepen vanuit het pdbBuilder
exemplaar om geavanceerde PDB-gegevens voor het insluiten van bronnen en bronindexering toe te voegen.
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));
}
Resources toevoegen met PersistentedAssemblyBuilder
U kunt aanroepen MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) om zoveel resources toe te voegen als nodig is. Streams moeten worden samengevoegd in een BlobBuilder stroom die u doorgeeft aan het ManagedPEBuilder argument. In het volgende voorbeeld ziet u hoe u resources maakt en deze koppelt aan de assembly die is gemaakt.
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);
}
Notitie
De metagegevenstokens voor alle leden worden ingevuld in de Save bewerking. Gebruik de tokens van een gegenereerd type en de bijbehorende leden niet voordat u opslaat, omdat ze standaardwaarden hebben of uitzonderingen genereren. Het is veilig om tokens te gebruiken voor typen waarnaar wordt verwezen, niet gegenereerd.
Sommige API's die niet belangrijk zijn voor het verzenden van een assembly, worden niet geïmplementeerd; Is bijvoorbeeld GetCustomAttributes()
niet geïmplementeerd. Met de runtime-implementatie kunt u deze API's gebruiken nadat u het type hebt gemaakt. Voor de persistente AssemblyBuilder
, gooien NotSupportedException
ze of NotImplementedException
. Als u een scenario hebt waarvoor deze API's zijn vereist, moet u een probleem indienen in de dotnet/runtime-opslagplaats.
Zie voor een alternatieve manier om assemblybestanden MetadataBuilderte genereren.