Kelas System.Reflection.Emit.AssemblyBuilder

Artikel ini menyediakan keterangan tambahan untuk dokumentasi referensi untuk API ini.

Rakitan dinamis adalah rakitan yang dibuat menggunakan API Pancaran Pantulan. Rakitan dinamis dapat mereferensikan jenis yang ditentukan dalam rakitan dinamis atau statis lainnya. Anda dapat menggunakan AssemblyBuilder untuk menghasilkan rakitan dinamis dalam memori dan menjalankan kodenya selama eksekusi aplikasi yang sama. Di .NET 9 kami menambahkan yang baru PersistedAssemblyBuilder dengan implementasi pantulan yang dikelola sepenuhnya yang memungkinkan Anda menyimpan rakitan ke dalam file. Di .NET Framework, Anda dapat melakukan keduanya—menjalankan rakitan dinamis dan menyimpannya ke file. Rakitan dinamis yang dibuat untuk menyimpan disebut rakitan yang bertahan , sementara rakitan khusus memori reguler disebut sementara atau dapat dijalankan. Dalam .NET Framework, rakitan dinamis dapat terdiri dari satu atau beberapa modul dinamis. Di .NET Core dan .NET 5+, rakitan dinamis hanya dapat terdiri dari satu modul dinamis.

Cara Anda membuat AssemblyBuilder instans berbeda untuk setiap implementasi, tetapi langkah-langkah lebih lanjut untuk menentukan modul, jenis, metode, atau enum, dan untuk menulis IL, sangat mirip.

Rakitan dinamis yang dapat dijalankan di .NET

Untuk mendapatkan objek yang dapat AssemblyBuilder dijalankan, gunakan AssemblyBuilder.DefineDynamicAssembly metode . Rakitan dinamis dapat dibuat menggunakan salah satu mode akses berikut:

Mode akses harus ditentukan dengan memberikan nilai yang sesuai AssemblyBuilderAccess dalam panggilan ke AssemblyBuilder.DefineDynamicAssembly metode ketika rakitan dinamis ditentukan dan tidak dapat diubah nanti. Runtime menggunakan mode akses rakitan dinamis untuk mengoptimalkan representasi internal assembly.

Contoh berikut menunjukkan cara membuat dan menjalankan rakitan:

public void CreateAndRunAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
    ModuleBuilder mob = ab.DefineDynamicModule("MyModule");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder mb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
}

Rakitan dinamis yang bertahan di .NET

AssemblyBuilder.Save API awalnya tidak di-port ke .NET (Core) karena implementasinya sangat bergantung pada kode asli khusus Windows yang juga tidak di-port. Di .NET 9 kami 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, karena Anda hanya dapat menjalankan rakitan yang dihasilkan dan tidak menyimpannya, sulit untuk men-debug rakitan dalam memori ini. 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 public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null) 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 menargetkan TFM tertentu, buka rakitan referensi untuk TFM tertentu menggunakan MetadataLoadContext dan gunakan nilai properti MetadataLoadContext.CoreAssembly untuk coreAssembly. Nilai ini memungkinkan generator berjalan pada satu versi runtime .NET dan menargetkan versi runtime .NET yang berbeda.

  • Jika Reflection.Emit digunakan untuk menghasilkan rakitan yang hanya akan dijalankan pada versi runtime yang sama dengan versi runtime yang dijalankan kompilator (biasanya dalam proc), perakitan inti dapat berupa typeof(object).Assembly. Rakitan referensi tidak diperlukan dalam kasus ini.

Contoh berikut menunjukkan cara membuat dan menyimpan rakitan ke aliran dan menjalankannya:

public void CreateSaveAndRunAssembly(string assemblyPath)
{
    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 }));
}

Untuk mengatur titik masuk untuk executable dan/atau 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 assembly dengan opsi yang diinginkan, misalnya:

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); 

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.

Rakitan dinamis yang bertahan di .NET Framework

Di .NET Framework, rakitan dan modul dinamis dapat disimpan ke file. Untuk mendukung fitur ini, AssemblyBuilderAccess enumerasi mendeklarasikan dua bidang tambahan: Save dan RunAndSave.

Modul dinamis dalam rakitan dinamis yang dapat dipertahankan disimpan ketika rakitan dinamis disimpan menggunakan Save metode . Untuk menghasilkan executable, SetEntryPoint metode harus dipanggil untuk mengidentifikasi metode yang merupakan titik masuk ke assembly. Rakitan disimpan sebagai DLL secara default, kecuali SetEntryPoint metode meminta pembuatan aplikasi konsol atau aplikasi berbasis Windows.

Contoh berikut menunjukkan cara membuat, menyimpan, dan menjalankan perakitan menggunakan .NET Framework.

public void CreateRunAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder mob = ab.DefineDynamicModule("MyAssembly.dll");
    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);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
    ab.Save("MyAssembly.dll");
}

Beberapa metode pada kelas dasar Assembly , seperti GetModules dan GetLoadedModules, tidak akan berfungsi dengan benar saat dipanggil dari AssemblyBuilder objek. Anda dapat memuat rakitan dinamis yang ditentukan dan memanggil metode pada rakitan yang dimuat. Misalnya, untuk memastikan bahwa modul sumber daya disertakan dalam daftar modul yang dikembalikan, panggil GetModules pada objek yang dimuat Assembly . Jika rakitan dinamis berisi lebih dari satu modul dinamis, nama file manifes perakitan harus cocok dengan nama modul yang ditentukan sebagai argumen pertama dengan DefineDynamicModule metode .

Penandatanganan rakitan dinamis yang menggunakan KeyPair tidak efektif sampai rakitan disimpan ke disk. Jadi, nama yang kuat tidak akan berfungsi dengan rakitan dinamis sementara.

Rakitan dinamis dapat mereferensikan jenis yang ditentukan dalam rakitan lain. Rakitan dinamis sementara dapat dengan aman mereferensikan jenis yang ditentukan dalam rakitan dinamis sementara lainnya, rakitan dinamis yang dapat dipertahankan, atau rakitan statis. Namun, runtime bahasa umum tidak memungkinkan modul dinamis yang dapat dipertahankan untuk mereferensikan jenis yang ditentukan dalam modul dinamis sementara. Ini karena ketika modul dinamis yang bertahan dimuat setelah disimpan ke disk, runtime tidak dapat menyelesaikan referensi ke jenis yang ditentukan dalam modul dinamis sementara.

Pembatasan pemancaran ke domain aplikasi jarak jauh

Beberapa skenario mengharuskan rakitan dinamis dibuat dan dijalankan di domain aplikasi jarak jauh. Pancaran pantulan tidak memungkinkan rakitan dinamis dipancarkan langsung ke domain aplikasi jarak jauh. Solusinya adalah memancarkan rakitan dinamis di domain aplikasi saat ini, menyimpan perakitan dinamis yang dipancarkan ke disk, lalu memuat rakitan dinamis ke domain aplikasi jarak jauh. Domain jarak jauh dan aplikasi hanya didukung di .NET Framework.