Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Artikel ini menyediakan keterangan tambahan untuk dokumentasi referensi untuk API ini.
API AssemblyBuilder.Save 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, kelas PersistedAssemblyBuilder menambahkan implementasi Reflection.Emit
yang dikelola sepenuhnya yang mendukung penghematan. Implementasi ini tidak memiliki dependensi pada implementasi Reflection.Emit
khusus runtime yang sudah ada sebelumnya. Artinya, sekarang ada dua implementasi yang berbeda di .NET, dapat dijalankan dan bertahan. Untuk menjalankan rakitan yang sudah dipersisten, pertama-tama simpan ke dalam aliran memori atau file, kemudian muat kembali.
Sebelum PersistedAssemblyBuilder
, Anda hanya dapat menjalankan rakitan yang dihasilkan dan tidak menyimpannya. Karena assembly hanya berada dalam memori, sulit untuk di-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 instans PersistedAssemblyBuilder
, gunakan konstruktor PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>). Parameter coreAssembly
digunakan untuk menyelesaikan jenis runtime dasar dan dapat digunakan untuk memecahkan versi rakitan referensi.
Jika
Reflection.Emit
digunakan untuk menghasilkan rakitan yang hanya akan dijalankan pada versi runtime yang sama dengan versi runtime yang sedang berjalan pada kompilator (umumnya dalam proses), perakitan inti dapat dibuat lebih sederhana dengantypeof(object).Assembly
. Contoh berikut menunjukkan cara membuat dan menyimpan assembly ke stream dan menjalankannya menggunakan assembly 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 yang diberikan 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 yang dikembalikan oleh instansMetadataLoadContext
saat mereferensikan jenis inti. Misalnya, alih-alihtypeof(int)
, temukan jenisSystem.Int32
diMetadataLoadContext.CoreAssembly
berdasarkan 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 program yang dapat dieksekusi
Untuk mengatur titik entri untuk executable atau untuk mengatur opsi lain untuk file assembly, Anda dapat memanggil metode public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
dan menggunakan metadata yang diisi untuk menghasilkan assembly dengan opsi yang diinginkan, misalnya:
public static void SetEntryPoint()
{
PersistedAssemblyBuilder ab = new(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);
ManagedPEBuilder peBuilder = new(
header: PEHeaderBuilder.CreateExecutableHeader(),
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
mappedFieldData: fieldData,
entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));
BlobBuilder peBlob = new();
peBuilder.Serialize(peBlob);
// Create the executable:
using FileStream fileStream = new("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
}
Memancarkan simbol dan menghasilkan PDB
Metadata simbol akan terisi ke dalam parameter keluar pdbBuilder
saat Anda memanggil metode GenerateMetadata(BlobBuilder, BlobBuilder) pada instans PersistedAssemblyBuilder
. Untuk membuat rakitan dengan PDB portabel:
- Buat instans ISymbolDocumentWriter dengan metode ModuleBuilder.DefineDocument(String, Guid, Guid, Guid). Saat menghasilkan IL metode, juga hasilkan info simbol yang sesuai.
- Buat instans PortablePdbBuilder menggunakan instans
pdbBuilder
yang dihasilkan oleh metode GenerateMetadata(BlobBuilder, BlobBuilder). - Serialisasi
PortablePdbBuilder
ke dalam Blob, dan tulisBlob
ke dalam aliran file PDB (hanya jika Anda menghasilkan PDB mandiri). - Buat instans DebugDirectoryBuilder dan tambahkan DebugDirectoryBuilder.AddCodeViewEntry (PDB mandiri) atau DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
- Atur argumen
debugDirectoryBuilder
opsional saat membuat instans PEBuilder.
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 metode MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) dari instans pdbBuilder
untuk menambahkan penyematan sumber dan informasi PDB tingkat lanjut 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 satu BlobBuilder yang kemudian Anda masukkan ke dalam argumen ManagedPEBuilder. Contoh berikut menunjukkan cara membuat sumber daya dan melampirkannya ke rakitan yang dibuat.
public static void SetResource()
{
PersistedAssemblyBuilder ab = new(new AssemblyName("MyAssembly"), typeof(object).Assembly);
ab.DefineDynamicModule("MyModule");
MetadataBuilder metadata = ab.GenerateMetadata(out BlobBuilder ilStream, out _);
using MemoryStream stream = new();
ResourceWriter myResourceWriter = new(stream);
myResourceWriter.AddResource("AddResource 1", "First added resource");
myResourceWriter.AddResource("AddResource 2", "Second added resource");
myResourceWriter.AddResource("AddResource 3", "Third added resource");
myResourceWriter.Close();
byte[] data = stream.ToArray();
BlobBuilder resourceBlob = new();
resourceBlob.WriteInt32(data.Length);
resourceBlob.WriteBytes(data);
metadata.AddManifestResource(
ManifestResourceAttributes.Public,
metadata.GetOrAddString("MyResource.resources"),
implementation: default,
offset: 0);
ManagedPEBuilder peBuilder = new(
header: PEHeaderBuilder.CreateLibraryHeader(),
metadataRootBuilder: new MetadataRootBuilder(metadata),
ilStream: ilStream,
managedResources: resourceBlob);
BlobBuilder blob = new();
peBuilder.Serialize(blob);
// Create the assembly:
using FileStream fileStream = new("MyAssemblyWithResource.dll", FileMode.Create, FileAccess.Write);
blob.WriteContentTo(fileStream);
}
Contoh berikut menunjukkan cara membaca sumber daya dari rakitan yang dibuat.
public static void ReadResource()
{
Assembly readAssembly = Assembly.LoadFile(Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
"MyAssemblyWithResource.dll"));
// Use ResourceManager.GetString() to read the resources.
ResourceManager rm = new("MyResource", readAssembly);
Console.WriteLine("Using ResourceManager.GetString():");
Console.WriteLine($"{rm.GetString("AddResource 1", CultureInfo.InvariantCulture)}");
Console.WriteLine($"{rm.GetString("AddResource 2", CultureInfo.InvariantCulture)}");
Console.WriteLine($"{rm.GetString("AddResource 3", CultureInfo.InvariantCulture)}");
// Use ResourceSet to enumerate the resources.
Console.WriteLine();
Console.WriteLine("Using ResourceSet:");
ResourceSet resourceSet = rm.GetResourceSet(CultureInfo.InvariantCulture, createIfNotExists: true, tryParents: false);
foreach (DictionaryEntry entry in resourceSet)
{
Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
}
// Use ResourceReader to enumerate the resources.
Console.WriteLine();
Console.WriteLine("Using ResourceReader:");
using Stream stream = readAssembly.GetManifestResourceStream("MyResource.resources")!;
using ResourceReader reader = new(stream);
foreach (DictionaryEntry entry in reader)
{
Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
}
}
Nota
Token metadata untuk semua anggota diisi pada operasi Save. Jangan gunakan token dari tipe yang dihasilkan dan anggotanya sebelum menyimpan, karena akan memiliki nilai default atau mengakibatkan 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 AssemblyBuilder
yang bertahan, mereka melempar NotSupportedException
atau NotImplementedException
. Jika Anda memiliki skenario yang memerlukan API tersebut, ajukan masalah di repositori dotnet/runtime.
Untuk cara alternatif untuk menghasilkan file perakitan, lihat MetadataBuilder.