Rakitan dinamis yang bertahan di .NET
Artikel ini menyediakan keterangan tambahan untuk dokumentasi referensi untuk API ini.
AssemblyBuilder.Save API awalnya tidak di-port ke .NET (Core) karena implementasinya sangat bergantung pada kode asli khusus Windows yang juga tidak di-port. Baru di .NET 9, PersistedAssemblyBuilder kelas menambahkan implementasi terkelola penuh Reflection.Emit
yang mendukung penghematan. Implementasi ini tidak memiliki dependensi pada implementasi khusus Reflection.Emit
runtime yang sudah ada sebelumnya. Artinya, sekarang ada dua implementasi yang berbeda di .NET, dapat dijalankan dan bertahan. Untuk menjalankan rakitan yang bertahan, pertama-tama simpan ke aliran memori atau file, lalu muat kembali.
Sebelum PersistedAssemblyBuilder
, Anda hanya dapat menjalankan rakitan yang dihasilkan dan tidak menyimpannya. Karena assembly hanya dalam memori, sulit untuk debug. Keuntungan menyimpan rakitan dinamis ke file adalah:
- Anda dapat memverifikasi rakitan yang dihasilkan dengan alat seperti ILVerify, atau mendekompilasi dan memeriksanya secara manual dengan alat seperti ILSpy.
- Rakitan yang disimpan dapat dimuat secara langsung, tidak perlu dikompilasi lagi, yang dapat mengurangi waktu mulai aplikasi.
Untuk membuat PersistedAssemblyBuilder
instans, gunakan PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) konstruktor. Parameter coreAssembly
digunakan untuk mengatasi jenis runtime dasar dan dapat digunakan untuk menyelesaikan penerapan versi perakitan referensi:
Jika
Reflection.Emit
digunakan untuk menghasilkan rakitan yang hanya akan dijalankan pada versi runtime yang sama dengan versi runtime yang dijalankan pengkompilasi (biasanya dalam proc), rakitan inti dapat hanyatypeof(object).Assembly
. Contoh berikut menunjukkan cara membuat dan menyimpan rakitan ke aliran dan menjalankannya dengan rakitan runtime saat ini: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 })); }
Jika
Reflection.Emit
digunakan untuk menghasilkan rakitan yang menargetkan TFM tertentu, buka rakitan referensi untuk TFM tertentu menggunakanMetadataLoadContext
dan gunakan nilai properti MetadataLoadContext.CoreAssembly untukcoreAssembly
. Nilai ini memungkinkan generator berjalan pada satu versi runtime .NET dan menargetkan versi runtime .NET yang berbeda. Anda harus menggunakan jenis yangMetadataLoadContext
dikembalikan oleh instans saat mereferensikan jenis inti. Misalnya, alih-alihtypeof(int)
, temukanSystem.Int32
jenis berdasarkanMetadataLoadContext.CoreAssembly
nama: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 }
Mengatur titik masuk untuk executable
Untuk mengatur titik entri untuk executable atau untuk mengatur opsi lain untuk file assembly, Anda dapat memanggil public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
metode dan menggunakan metadata yang diisi untuk menghasilkan rakitan dengan opsi yang diinginkan, misalnya:
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);
}
Memancarkan simbol dan menghasilkan PDB
Metadata simbol diisi ke parameter pdbBuilder
keluar saat Anda memanggil GenerateMetadata(BlobBuilder, BlobBuilder) metode pada PersistedAssemblyBuilder
instans. Untuk membuat rakitan dengan PDB portabel:
- Buat ISymbolDocumentWriter instans dengan ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) metode . Saat memancarkan IL metode, juga memancarkan info simbol yang sesuai.
- Buat PortablePdbBuilder instans menggunakan instans yang
pdbBuilder
dihasilkan oleh GenerateMetadata(BlobBuilder, BlobBuilder) metode . - Serialisasi
PortablePdbBuilder
ke dalam Blob, dan tulisBlob
ke dalam aliran file PDB (hanya jika Anda membuat PDB mandiri). - Buat DebugDirectoryBuilder instans dan tambahkan DebugDirectoryBuilder.AddCodeViewEntry (PDB mandiri) atau DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
- Atur argumen opsional
debugDirectoryBuilder
saat membuat PEBuilder instans.
Contoh berikut menunjukkan cara memancarkan info simbol dan menghasilkan file 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;
}
Selanjutnya, Anda dapat menambahkan CustomDebugInformation dengan memanggil MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) metode dari pdbBuilder
instans untuk menambahkan penyematan sumber dan informasi PDB lanjutan pengindeksan sumber.
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));
}
Menambahkan sumber daya dengan PersistedAssemblyBuilder
Anda dapat memanggil MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) untuk menambahkan sumber daya sebanyak yang diperlukan. Aliran harus digabungkan menjadi salah satu BlobBuilder yang Anda teruskan ke ManagedPEBuilder argumen. Contoh berikut menunjukkan cara membuat sumber daya dan melampirkannya ke rakitan yang dibuat.
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);
}
Catatan
Token metadata untuk semua anggota diisi pada Save operasi. Jangan gunakan token jenis yang dihasilkan dan anggotanya sebelum menyimpan, karena mereka akan memiliki nilai default atau melemparkan pengecualian. Aman untuk menggunakan token untuk jenis yang dirujuk, bukan dihasilkan.
Beberapa API yang tidak penting untuk memancarkan rakitan tidak diimplementasikan; misalnya, GetCustomAttributes()
tidak diimplementasikan. Dengan implementasi runtime, Anda dapat menggunakan API tersebut setelah membuat jenisnya. Untuk yang bertahan AssemblyBuilder
, mereka melemparkan NotSupportedException
atau NotImplementedException
. Jika Anda memiliki skenario yang memerlukan API tersebut , ajukan masalah di repositori dotnet/runtime.
Untuk cara alternatif untuk membuat file rakitan, lihat MetadataBuilder.
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk