分享方式:


.NET 中保存的動態元件

本文提供此 API 參考文件的補充備註。

AssemblyBuilder.Save API 原本不會移植到 .NET (Core),因為實作嚴重依賴也未移植的 Windows 特定機器碼。 .NET 9 的新功能,類別 PersistedAssemblyBuilder 會新增完全受控 Reflection.Emit 實作,以支持儲存。 此實作不相依於預先存在的運行時間特定 Reflection.Emit 實作。 也就是說,現在 .NET 中有兩個不同的實作,可執行和保存。 若要執行保存的元件,請先將它儲存到記憶體數據流或檔案中,然後將它載入回去。

在 之前 PersistedAssemblyBuilder,您只能執行產生的元件,而無法儲存它。 因為元件只是記憶體內部,所以很難進行偵錯。 將動態元件儲存至檔案的優點如下:

  • 您可以使用 ILVerify 之類的工具來驗證產生的元件,或使用 ILSpy 之類的工具手動檢查它。
  • 您可以直接載入已儲存的元件,而不需要再次編譯,這可能會減少應用程式啟動時間。

若要建立 PersistedAssemblyBuilder 實例,請使用 建構函 PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) 式。 coreAssembly參數可用來解析基底運行時間類型,並可用於解析參考元件版本設定:

  • 如果 Reflection.Emit 用來產生只會在與編譯程式執行所在的執行時間版本相同的執行時間版本上執行的元件(通常是在程式內),則核心元件可能只是 typeof(object).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 }));
    }
    
  • 如果使用 Reflection.Emit 來產生以特定 TFM 為目標的元件,請使用 MetadataLoadContext 開啟指定 TFM 的參考元件,並使用 的 coreAssemblyMetadataLoadContext.CoreAssembly 屬性值。 這個值可讓產生器在一個 .NET 運行時間版本上執行,並以不同的 .NET 運行時間版本為目標。 參考核心類型時, MetadataLoadContext 您應該使用 實例傳回的類型。 例如,而不是 typeof(int),依名稱尋找 System.Int32 中的 MetadataLoadContext.CoreAssembly 類型:

    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
    }
    

設定可執行文件的進入點

若要設定可執行文件的進入點,或設定元件檔的其他選項,您可以呼叫 public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) 方法,並使用填入的元數據產生具有所需選項的元件,例如:

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

發出符號併產生 PDB

當您在 pdbBuilder 實例上PersistedAssemblyBuilder呼叫 GenerateMetadata(BlobBuilder, BlobBuilder) 方法時,符號元數據會填入 out 參數。 若要建立具有可攜式 PDB 的元件:

  1. 使用 ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) 方法建立ISymbolDocumentWriter實例。 發出方法的 IL 時,也會發出對應的符號資訊。
  2. PortablePdbBuilder使用 pdbBuilder 方法所產生的 GenerateMetadata(BlobBuilder, BlobBuilder) 實例建立實例。
  3. 將串行化 PortablePdbBuilderBlob,並將 寫入 Blob PDB 檔案數據流中(只有在您產生獨立 PDB 時)。
  4. 建立 DebugDirectoryBuilder 實體並新增 DebugDirectoryBuilder.AddCodeViewEntry (獨立 PDB) 或 DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry
  5. 在建立 PEBuilder 實例時設定選擇性debugDirectoryBuilder自變數。

下列範例示範如何發出符號信息併產生 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;
}

此外,您可以從 CustomDebugInformation 實例呼叫 MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) 方法 pdbBuilder ,以新增來源內嵌和來源索引進階 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));
}

使用 PersistedAssemblyBuilder 新增資源

您可以呼叫 MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) 來視需要新增盡可能多的資源。 數據流必須串連至 BlobBuilder 您傳入自變數的 ManagedPEBuilder 數據流。 下列範例示範如何建立資源,並將它附加至所建立的元件。

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

注意

作業上會填入所有成員的 Save 元數據令牌。 儲存之前,請勿使用所產生型別及其成員的令牌,因為它們會有預設值或擲回例外狀況。 針對參考的類型使用令牌,而不是產生令牌是安全的。

不會實作對發出元件而言不重要的一些 API;例如, GetCustomAttributes() 未實作 。 使用運行時間實作,您可以在建立類型之後使用這些 API。 對於儲存的 AssemblyBuilder,它們會擲回 NotSupportedExceptionNotImplementedException。 如果您有需要這些 API 的案例,請在 dotnet/runtime 存放庫中提出問題

如需產生元件檔的替代方式,請參閱 MetadataBuilder