Aracılığıyla paylaş


.NET'te kalıcı dinamik derlemeler

Bu makale, bu API'nin başvuru belgelerine ek açıklamalar sağlar.

Uygulama AssemblyBuilder.Save , windows'a özgü yerel koda da büyük ölçüde bağlı olduğundan, API başlangıçta .NET'e (Core) aktarılamıyordu. .NET 9'da yeni olan sınıfı, kaydetmeyi PersistedAssemblyBuilder destekleyen tam olarak yönetilen Reflection.Emit bir uygulama ekler. Bu uygulamanın önceden var olan, çalışma zamanına özgü Reflection.Emit uygulamaya bağımlılığı yoktur. Yani, artık .NET'te çalıştırılabilir ve kalıcı olan iki farklı uygulama vardır. Kalıcı derlemeyi çalıştırmak için önce bir bellek akışına veya dosyaya kaydedin, sonra yeniden yükleyin.

öncesinde PersistedAssemblyBuilderyalnızca oluşturulan bir derlemeyi çalıştırabilir ve kaydedemeyebilirsiniz. Derleme yalnızca bellek içi olduğundan hata ayıklaması zordu. Dinamik derlemeyi bir dosyaya kaydetmenin avantajları şunlardır:

  • Oluşturulan derlemeyi ILVerify gibi araçlarla doğrulayabilir veya derleyip ILSpy gibi araçlarla el ile inceleyebilirsiniz.
  • Kaydedilen derleme doğrudan yüklenebilir, yeniden derlemeye gerek yoktur ve bu da uygulama başlatma süresini azaltabilir.

Örnek PersistedAssemblyBuilder oluşturmak için oluşturucuyu PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) kullanın. coreAssembly parametresi temel çalışma zamanı türlerini çözümlemek için kullanılır ve başvuru derlemesi sürümünü çözümlemek için kullanılabilir:

  • Yalnızca derleyicinin üzerinde çalıştığı çalışma zamanı sürümüyle (genellikle in-proc) aynı çalışma zamanı sürümünde yürütülecek bir derleme oluşturmak için kullanılırsa Reflection.Emit , çekirdek derleme basit typeof(object).Assemblyolabilir. Aşağıdaki örnekte bir derlemenin nasıl oluşturulup akışa kaydedilip geçerli çalışma zamanı derlemesiyle nasıl çalıştırılabileceği gösterilmektedir:

    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 }));
    }
    
  • Belirli bir TFM'yi hedefleyen bir derleme oluşturmak için kullanılırsaReflection.Emit, kullanarak MetadataLoadContext verilen TFM için başvuru derlemelerini açın ve için coreAssemblyMetadataLoadContext.CoreAssembly özelliğinin değerini kullanın. Bu değer, oluşturucunun bir .NET çalışma zamanı sürümünde çalışmasına ve farklı bir .NET çalışma zamanı sürümünü hedeflemesine olanak tanır. Çekirdek türlerine MetadataLoadContext başvururken örnek tarafından döndürülen türleri kullanmanız gerekir. Örneğin, yerinetypeof(int), türü MetadataLoadContext.CoreAssembly ada göre bulunSystem.Int32:

    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
    }
    

Yürütülebilir dosya için giriş noktası ayarlama

Yürütülebilir dosyanın giriş noktasını ayarlamak veya derleme dosyası için diğer seçenekleri ayarlamak için yöntemini çağırabilir public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) ve doldurulan meta verileri kullanarak derlemeyi istenen seçeneklerle oluşturabilirsiniz, örneğin:

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

Sembolleri yayma ve PDB oluşturma

Sembol meta verileri, bir PersistedAssemblyBuilder örnekte yöntemini çağırdığınızda out parametresine GenerateMetadata(BlobBuilder, BlobBuilder) doldurulurpdbBuilder. Taşınabilir PDB ile derleme oluşturmak için:

  1. yöntemiyle ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) örnekler oluşturunISymbolDocumentWriter. Yöntemin IL'sini yayarken, ilgili sembol bilgilerini de yayar.
  2. yöntemi tarafından GenerateMetadata(BlobBuilder, BlobBuilder) oluşturulan örneği kullanarak pdbBuilder bir PortablePdbBuilder örnek oluşturun.
  3. olarak serileştirin PortablePdbBuilderBlobve bir PDB dosya akışına Blob yazın (yalnızca tek başına bir PDB oluşturuyorsanız).
  4. Bir DebugDirectoryBuilder örnek oluşturun ve bir (tek başına PDB) veya DebugDirectoryBuilder.AddEmbeddedPortablePdbEntryekleyin DebugDirectoryBuilder.AddCodeViewEntry .
  5. Örneği oluştururken PEBuilder isteğe bağlı debugDirectoryBuilder bağımsız değişkeni ayarlayın.

Aşağıdaki örnekte, sembol bilgilerini yayma ve PDB dosyası oluşturma gösterilmektedir.

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

Ayrıca, kaynak ekleme ve kaynak dizin oluşturma gelişmiş PDB bilgilerini eklemek için örnekten pdbBuilder yöntemini çağırarak MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) ekleyebilirsinizCustomDebugInformation.

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 ile kaynak ekleme

Gerektiği kadar kaynak eklemek için çağırabilirsiniz MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) . Akışlar bağımsız değişkenine geçirdiğiniz ManagedPEBuilder bir BlobBuilder birleştirilmelidir. Aşağıdaki örnek, kaynakların nasıl oluşturulacağını ve oluşturulan derlemeye nasıl eklendiğini gösterir.

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

Not

Tüm üyeler için meta veri belirteçleri işlemde Save doldurulur. Varsayılan değerlere sahip olacak veya özel durumlar oluşturacaklarından, kaydetmeden önce oluşturulan türün belirteçlerini ve üyelerini kullanmayın. Başvuruda kullanılan türler için belirteçleri kullanmak güvenlidir, oluşturulmaz.

Derleme yayma açısından önemli olmayan bazı API'ler uygulanmaz; örneğin uygulanmaz GetCustomAttributes() . Çalışma zamanı uygulamasıyla, türü oluşturduktan sonra bu API'leri kullanabildiniz. Kalıcı olan AssemblyBuilderiçin veya NotImplementedExceptionoluştururlarNotSupportedException. Bu API'leri gerektiren bir senaryonuz varsa dotnet/runtime deposunda bir sorun oluşturun.

Derleme dosyaları oluşturmanın alternatif bir yolu için bkz MetadataBuilder. .