Trvalá dynamická sestavení v .NET
Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.
Rozhraní AssemblyBuilder.Save API se původně nepředávalo do .NET (Core), protože implementace výrazně závisela na nativním kódu specifickém pro Windows, který se také nepředávalo. Novinkou v .NET 9 je, že PersistedAssemblyBuilder třída přidá plně spravovanou Reflection.Emit
implementaci, která podporuje ukládání. Tato implementace nemá žádnou závislost na existující implementaci specifické pro Reflection.Emit
modul runtime. To znamená, že v .NET existují dvě různé implementace, spustitelné a trvalé. Pokud chcete spustit trvalé sestavení, nejprve ho uložte do datového proudu paměti nebo souboru a pak ho načtěte zpět.
Před PersistedAssemblyBuilder
tím můžete spustit pouze vygenerované sestavení a ne uložit ho. Vzhledem k tomu, že sestavení bylo pouze v paměti, bylo obtížné ladit. Výhody ukládání dynamického sestavení do souboru jsou:
- Vygenerované sestavení můžete ověřit pomocí nástrojů, jako je ILVerify, nebo dekompilovat, a ručně ho prozkoumat pomocí nástrojů, jako je ILSpy.
- Uložené sestavení lze načíst přímo, není nutné znovu kompilovat, což může zkrátit dobu spuštění aplikace.
K vytvoření PersistedAssemblyBuilder
instance použijte PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) konstruktor. Tento coreAssembly
parametr slouží k překladu typů základního modulu runtime a lze ho použít k překladu referenčních verzí sestavení:
Pokud
Reflection.Emit
se používá k vygenerování sestavení, které se spustí pouze ve stejné verzi modulu runtime jako verze modulu runtime, na které kompilátor běží (obvykle v proc), základní sestavení může být jednodušetypeof(object).Assembly
. Následující příklad ukazuje, jak vytvořit a uložit sestavení do datového proudu a spustit ho s aktuálním sestavením runtime: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 })); }
Pokud
Reflection.Emit
se používá ke generování sestavení, které cílí na konkrétní TFM, otevřete referenční sestavení pro daný TFM pomocíMetadataLoadContext
a použijte hodnotu MetadataLoadContext.CoreAssembly vlastnost procoreAssembly
. Tato hodnota umožňuje generátoru spustit na jedné verzi modulu runtime .NET a cílit na jinou verzi modulu runtime .NET. Při odkazování na základní typy byste měli použít typy vrácenéMetadataLoadContext
instancí. Místotypeof(int)
toho vyhledejteSystem.Int32
typMetadataLoadContext.CoreAssembly
podle názvu: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 }
Nastavení vstupního bodu pro spustitelný soubor
Chcete-li nastavit vstupní bod spustitelného souboru nebo nastavit další možnosti pro soubor sestavení, můžete volat metodu public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
a použít vyplněná metadata k vygenerování sestavení s požadovanými možnostmi, například:
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);
}
Generování symbolů a generování PDB
Metadata symbolů se zaplní do výstupního parametru pdbBuilder
při volání GenerateMetadata(BlobBuilder, BlobBuilder) metody v PersistedAssemblyBuilder
instanci. Vytvoření sestavení s přenosným souborem PDB:
- Vytvořte ISymbolDocumentWriter instance pomocí ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) metody. Při generování IL metody také vygenerujte odpovídající informace o symbolu.
- Vytvořte PortablePdbBuilder instanci pomocí
pdbBuilder
instance vytvořené metodou GenerateMetadata(BlobBuilder, BlobBuilder) . - Serializujte
PortablePdbBuilder
do Blobsouboru a zapište hoBlob
do streamu souboru PDB (pouze pokud generujete samostatný PDB). - DebugDirectoryBuilder Vytvoření instance a přidání (samostatného DebugDirectoryBuilder.AddCodeViewEntry souboru PDB) nebo DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
- Při vytváření PEBuilder instance nastavte volitelný
debugDirectoryBuilder
argument.
Následující příklad ukazuje, jak vygenerovat informace o symbolu a vygenerovat soubor PDB.
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;
}
Dále můžete přidat CustomDebugInformation voláním MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) metody z pdbBuilder
instance pro přidání zdrojového vkládání a indexování zdrojů pokročilých informací PDB.
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));
}
Přidání prostředků pomocí PersistedAssemblyBuilder
Můžete volat MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) , abyste mohli přidat tolik prostředků, kolik potřebujete. Toky musí být zřetězeny do jednohoBlobBuilder, který předáte argumentuManagedPEBuilder. Následující příklad ukazuje, jak vytvořit prostředky a připojit ho k vytvořenému sestavení.
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);
}
Poznámka:
Tokeny metadat pro všechny členy jsou v operaci vyplněny Save . Nepoužívejte tokeny vygenerovaného typu a jeho členů před uložením, protože budou mít výchozí hodnoty nebo vyvolání výjimek. Je bezpečné používat tokeny pro typy, na které se odkazují, negenerované.
Některá rozhraní API, která nejsou důležitá pro generování sestavení, nejsou implementována. Například GetCustomAttributes()
není implementováno. Při implementaci modulu runtime jste po vytvoření typu mohli tato rozhraní API použít. Pro trvalé AssemblyBuilder
, hází NotSupportedException
nebo NotImplementedException
. Pokud máte scénář, který vyžaduje tato rozhraní API, vytvořte problém v úložišti dotnet/runtime.
Alternativní způsob generování souborů sestavení naleznete v tématu MetadataBuilder.
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro