Delen via


Persistente dynamische assembly's in .NET

In dit artikel vindt u aanvullende opmerkingen in de referentiedocumentatie voor deze API.

De AssemblyBuilder.Save API is oorspronkelijk niet overgezet naar .NET (Core), omdat de implementatie sterk afhankelijk is van systeemeigen Windows-code die ook niet is overgezet. Nieuw in .NET 9 voegt de PersistedAssemblyBuilder klasse een volledig beheerde Reflection.Emit implementatie toe die ondersteuning biedt voor opslaan. Deze implementatie is niet afhankelijk van de bestaande, runtimespecifieke Reflection.Emit implementatie. Dat wil gezegd, er zijn nu twee verschillende implementaties in .NET, die kunnen worden uitgevoerd en persistent zijn. Als u de persistente assembly wilt uitvoeren, moet u deze eerst opslaan in een geheugenstroom of een bestand en vervolgens weer laden.

Voorheen PersistedAssemblyBuilderkon u alleen een gegenereerde assembly uitvoeren en deze niet opslaan. Omdat de assembly alleen in het geheugen was, was het moeilijk om fouten op te sporen. Voordelen van het opslaan van een dynamische assembly in een bestand zijn:

  • U kunt de gegenereerde assembly verifiëren met hulpprogramma's zoals ILVerify of decompileren en deze handmatig onderzoeken met hulpprogramma's zoals ILSpy.
  • De opgeslagen assembly kan rechtstreeks worden geladen, hoeft niet opnieuw te worden gecompileerd, waardoor de opstarttijd van de toepassing kan worden verlaagd.

Gebruik de PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) constructor om een PersistedAssemblyBuilder exemplaar te maken. De coreAssembly parameter wordt gebruikt om basisruntimetypen op te lossen en kan worden gebruikt voor het omzetten van referentieassemblyversiebeheer:

  • Als Reflection.Emit wordt gebruikt om een assembly te genereren die alleen wordt uitgevoerd op dezelfde runtimeversie als de runtimeversie waarop de compiler wordt uitgevoerd (meestal in-proc), kan de kernassembly gewoon typeof(object).Assemblyzijn. In het volgende voorbeeld ziet u hoe u een assembly maakt en opslaat in een stream en deze uitvoert met de huidige runtime-assembly:

    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 }));
    }
    
  • Als Reflection.Emit wordt gebruikt om een assembly te genereren die is gericht op een specifieke TFM, opent u de referentieassembly's voor de opgegeven TFM met behulp MetadataLoadContext van en gebruikt u de waarde van de eigenschap MetadataLoadContext.CoreAssembly voor coreAssembly. Met deze waarde kan de generator worden uitgevoerd op één .NET Runtime-versie en een andere .NET Runtime-versie gebruiken. U moet typen gebruiken die door het MetadataLoadContext exemplaar worden geretourneerd wanneer u naar kerntypen verwijst. Zoek bijvoorbeeld in plaats van typeof(int)het System.Int32 type op MetadataLoadContext.CoreAssembly naam:

    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
    }
    

Invoerpunt instellen voor een uitvoerbaar bestand

Als u het invoerpunt voor een uitvoerbaar bestand wilt instellen of andere opties voor het assemblybestand wilt instellen, kunt u de public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) methode aanroepen en de ingevulde metagegevens gebruiken om de assembly te genereren met de gewenste opties, bijvoorbeeld:

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

Symbolen verzenden en PDB genereren

De metagegevens van symbolen worden ingevuld in de pdbBuilder outparameter wanneer u de GenerateMetadata(BlobBuilder, BlobBuilder) methode voor een PersistedAssemblyBuilder exemplaar aanroept. Een assembly maken met een draagbare PDB:

  1. Maak ISymbolDocumentWriter exemplaren met de ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) methode. Wanneer u de IL van de methode verzendt, verzendt u ook de bijbehorende symboolgegevens.
  2. Maak een PortablePdbBuilder exemplaar met behulp van het pdbBuilder exemplaar dat door de GenerateMetadata(BlobBuilder, BlobBuilder) methode is geproduceerd.
  3. Serialiseer de PortablePdbBuilder in een Bloben schrijf de Blob naar een PDB-bestandsstroom (alleen als u een zelfstandige PDB genereert).
  4. Maak een DebugDirectoryBuilder exemplaar en voeg een DebugDirectoryBuilder.AddCodeViewEntry (zelfstandige PDB) of DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
  5. Stel het optionele debugDirectoryBuilder argument in bij het maken van het PEBuilder exemplaar.

In het volgende voorbeeld ziet u hoe u symboolgegevens verzendt en een PDB-bestand genereert.

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

Verder kunt u toevoegen CustomDebugInformation door de MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) methode aan te roepen vanuit het pdbBuilder exemplaar om geavanceerde PDB-gegevens voor het insluiten van bronnen en bronindexering toe te voegen.

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

Resources toevoegen met PersistentedAssemblyBuilder

U kunt aanroepen MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) om zoveel resources toe te voegen als nodig is. Streams moeten worden samengevoegd in een BlobBuilder stroom die u doorgeeft aan het ManagedPEBuilder argument. In het volgende voorbeeld ziet u hoe u resources maakt en deze koppelt aan de assembly die is gemaakt.

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

Notitie

De metagegevenstokens voor alle leden worden ingevuld in de Save bewerking. Gebruik de tokens van een gegenereerd type en de bijbehorende leden niet voordat u opslaat, omdat ze standaardwaarden hebben of uitzonderingen genereren. Het is veilig om tokens te gebruiken voor typen waarnaar wordt verwezen, niet gegenereerd.

Sommige API's die niet belangrijk zijn voor het verzenden van een assembly, worden niet geïmplementeerd; Is bijvoorbeeld GetCustomAttributes() niet geïmplementeerd. Met de runtime-implementatie kunt u deze API's gebruiken nadat u het type hebt gemaakt. Voor de persistente AssemblyBuilder, gooien NotSupportedException ze of NotImplementedException. Als u een scenario hebt waarvoor deze API's zijn vereist, moet u een probleem indienen in de dotnet/runtime-opslagplaats.

Zie voor een alternatieve manier om assemblybestanden MetadataBuilderte genereren.