Aracılığıyla paylaş


.NET'te kalıcı dinamik derlemeler

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

AssemblyBuilder.Save API'si başlangıçta .NET (Core)'a taşınmadı çünkü uygulama büyük ölçüde Windows'a özgü, taşınmamış yerel koda dayanıyordu. .NET 9, tamamen yönetilen ve kaydetmeyi destekleyen bir Reflection.Emit uygulaması sağlayan PersistedAssemblyBuilder sınıfını ekledi. Bu uygulamanın önceden var olan, çalışma zamanına özgü Reflection.Emit uygulamasına bağımlılığı yoktur. Yani, artık .NET'te iki farklı uygulama vardır: çalıştırılabilir ve kalıcı. Kalıcı derlemeyi çalıştırmak için önce bir bellek akışına veya dosyaya kaydedin, sonra yeniden yükleyin.

PersistedAssemblyBuilderönce yalnı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, yeniden derlenmesi gerekmeden doğrudan yüklenebilir ve bu da uygulama başlatma süresini azaltabilir.

PersistedAssemblyBuilder örneği oluşturmak için PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) oluşturucuyu 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:

  • Reflection.Emit 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, çekirdek derleme yalnızca 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(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), [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, [5, 10]));
    }
    
  • Belirli bir TFM'yi hedefleyen bir derleme oluşturmak için kullanılırsa, kullanarak verilen TFM için başvuru derlemelerini açın ve için metadataloadcontext.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 başvururken MetadataLoadContext örneği tarafından döndürülen türleri kullanmanız gerekir. Örneğin, typeof(int)yerine ada göre System.Int32'de MetadataLoadContext.CoreAssembly türünü bulun.

    public static void CreatePersistedAssemblyBuilderCoreAssemblyWithMetadataLoadContext(string refAssembliesPath)
    {
        PathAssemblyResolver resolver = new(Directory.GetFiles(refAssembliesPath, "*.dll"));
        using MetadataLoadContext context = new(resolver);
        Assembly coreAssembly = context.CoreAssembly;
        PersistedAssemblyBuilder ab = new(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 public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) yöntemini çağırabilir ve doldurulan meta verileri kullanarak derlemeyi istenen seçeneklerle oluşturabilirsiniz, örneğin:

public static void SetEntryPoint()
{
    PersistedAssemblyBuilder ab = new(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);

    ManagedPEBuilder peBuilder = new(
                    header: PEHeaderBuilder.CreateExecutableHeader(),
                    metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
                    ilStream: ilStream,
                    mappedFieldData: fieldData,
                    entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));

    BlobBuilder peBlob = new();
    peBuilder.Serialize(peBlob);

    // Create the executable:
    using FileStream fileStream = new("MyAssembly.exe", FileMode.Create, FileAccess.Write);
    peBlob.WriteContentTo(fileStream);
}

Sembolleri yayma ve PDB oluşturma

Simgelerin meta verileri, pdbBuilder örneğinde GenerateMetadata(BlobBuilder, BlobBuilder) yöntemini çağırdığınızda, PersistedAssemblyBuilder out parametresine doldurulur. Taşınabilir PDB ile derleme oluşturmak için:

  1. ISymbolDocumentWriter yöntemiyle ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) örnekleri oluşturun. Yöntemin IL'sini yayarken, ilgili sembol bilgilerini de yayar.
  2. PortablePdbBuilder yöntemi tarafından üretilen pdbBuilder örneğini kullanarak bir GenerateMetadata(BlobBuilder, BlobBuilder) örneği oluşturun.
  3. PortablePdbBuilder'ı bir Blobolarak serileştirin ve Blob'yi PDB dosya akışına yazın (sadece bağımsız bir PDB üretiyorsanız).
  4. bir DebugDirectoryBuilder örneği oluşturun ve bir DebugDirectoryBuilder.AddCodeViewEntry (tek başına PDB) veya DebugDirectoryBuilder.AddEmbeddedPortablePdbEntryekleyin.
  5. debugDirectoryBuilder örneği oluştururken isteğe bağlı PEBuilder bağımsız değişkenini 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 bilgileri eklemek için CustomDebugInformation örneğinden MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) yöntemini çağırarak pdbBuilder ekleyebilirsiniz.

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

MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) çağırarak gerektiği kadar kaynak ekleyebilirsiniz. Akışlar, BlobBuilder bağımsız değişkenine geçirdiğiniz tek bir ManagedPEBuilder olarak 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(new AssemblyName("MyAssembly"), typeof(object).Assembly);
    ab.DefineDynamicModule("MyModule");
    MetadataBuilder metadata = ab.GenerateMetadata(out BlobBuilder ilStream, out _);

    using MemoryStream stream = new();
    ResourceWriter myResourceWriter = new(stream);
    myResourceWriter.AddResource("AddResource 1", "First added resource");
    myResourceWriter.AddResource("AddResource 2", "Second added resource");
    myResourceWriter.AddResource("AddResource 3", "Third added resource");
    myResourceWriter.Close();

    byte[] data = stream.ToArray();
    BlobBuilder resourceBlob = new();
    resourceBlob.WriteInt32(data.Length);
    resourceBlob.WriteBytes(data);

    metadata.AddManifestResource(
        ManifestResourceAttributes.Public,
        metadata.GetOrAddString("MyResource.resources"),
        implementation: default,
        offset: 0);        

    ManagedPEBuilder peBuilder = new(
                    header: PEHeaderBuilder.CreateLibraryHeader(),
                    metadataRootBuilder: new MetadataRootBuilder(metadata),
                    ilStream: ilStream,
                    managedResources: resourceBlob);

    BlobBuilder blob = new();
    peBuilder.Serialize(blob);

    // Create the assembly:
    using FileStream fileStream = new("MyAssemblyWithResource.dll", FileMode.Create, FileAccess.Write);
    blob.WriteContentTo(fileStream);
}

Aşağıdaki örnekte, oluşturulan derlemedeki kaynakların nasıl okunduğu gösterilmektedir.

public static void ReadResource()
{
    Assembly readAssembly = Assembly.LoadFile(Path.Combine(
        Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
        "MyAssemblyWithResource.dll"));

    // Use ResourceManager.GetString() to read the resources.
    ResourceManager rm = new("MyResource", readAssembly);
    Console.WriteLine("Using ResourceManager.GetString():");
    Console.WriteLine($"{rm.GetString("AddResource 1", CultureInfo.InvariantCulture)}");
    Console.WriteLine($"{rm.GetString("AddResource 2", CultureInfo.InvariantCulture)}");
    Console.WriteLine($"{rm.GetString("AddResource 3", CultureInfo.InvariantCulture)}");

    // Use ResourceSet to enumerate the resources.
    Console.WriteLine();
    Console.WriteLine("Using ResourceSet:");
    ResourceSet resourceSet = rm.GetResourceSet(CultureInfo.InvariantCulture, createIfNotExists: true, tryParents: false);
    foreach (DictionaryEntry entry in resourceSet)
    {
        Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
    }

    // Use ResourceReader to enumerate the resources.
    Console.WriteLine();
    Console.WriteLine("Using ResourceReader:");
    using Stream stream = readAssembly.GetManifestResourceStream("MyResource.resources")!;
    using ResourceReader reader = new(stream);
    foreach (DictionaryEntry entry in reader)
    {
        Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
    }
}

Not Alın

Tüm üyeler için meta veri belirteçleri Save işleminde 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, üretilmeyen türler için belirteç kullanmak güvenlidir.

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

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