Sdílet prostřednictvím


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 PersistedAssemblyBuildertí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še typeof(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 pro coreAssembly. 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ísto typeof(int)toho vyhledejte System.Int32 typ MetadataLoadContext.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:

  1. 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.
  2. Vytvořte PortablePdbBuilder instanci pomocí pdbBuilder instance vytvořené metodou GenerateMetadata(BlobBuilder, BlobBuilder) .
  3. Serializujte PortablePdbBuilder do Blobsouboru a zapište ho Blob do streamu souboru PDB (pouze pokud generujete samostatný PDB).
  4. DebugDirectoryBuilder Vytvoření instance a přidání (samostatného DebugDirectoryBuilder.AddCodeViewEntry souboru PDB) nebo DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
  5. 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.