Bagikan melalui


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 hanya typeof(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 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. Anda harus menggunakan jenis yang MetadataLoadContext dikembalikan oleh instans saat mereferensikan jenis inti. Misalnya, alih-alih typeof(int), temukan System.Int32 jenis berdasarkan MetadataLoadContext.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:

  1. Buat ISymbolDocumentWriter instans dengan ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) metode . Saat memancarkan IL metode, juga memancarkan info simbol yang sesuai.
  2. Buat PortablePdbBuilder instans menggunakan instans yang pdbBuilder dihasilkan oleh GenerateMetadata(BlobBuilder, BlobBuilder) metode .
  3. Serialisasi PortablePdbBuilder ke dalam Blob, dan tulis Blob ke dalam aliran file PDB (hanya jika Anda membuat PDB mandiri).
  4. Buat DebugDirectoryBuilder instans dan tambahkan DebugDirectoryBuilder.AddCodeViewEntry (PDB mandiri) atau DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
  5. 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.