Systému. Reflexe ion. Emit.AssemblyBuilder – třída

Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.

Dynamické sestavení je sestavení vytvořené pomocí rozhraní API pro generování Reflexe ionu. Dynamické sestavení může odkazovat na typy definované v jiném dynamickém nebo statickém sestavení. Můžete použít AssemblyBuilder k vygenerování dynamických sestavení v paměti a spuštění jejich kódu během stejného spuštění aplikace. V .NET 9 jsme přidali novou PersistedAssemblyBuilder s plně spravovanou implementací generování reflexe, která umožňuje uložit sestavení do souboru. V rozhraní .NET Framework můžete provést obojí – spustit dynamické sestavení a uložit ho do souboru. Dynamické sestavení vytvořené pro ukládání se nazývá trvalé sestavení, zatímco běžné sestavení jen pro paměť se nazývá přechodné nebo spustitelné. V rozhraní .NET Framework se dynamické sestavení může skládat z jednoho nebo více dynamických modulů. V .NET Core a .NET 5+ se dynamické sestavení může skládat pouze z jednoho dynamického modulu.

Způsob vytvoření AssemblyBuilder instance se pro každou implementaci liší, ale další kroky pro definování modulu, typu, metody nebo výčtu a pro psaní IL jsou poměrně podobné.

Spustitelná dynamická sestavení v .NET

K získání spustitelného AssemblyBuilder objektu použijte metodu AssemblyBuilder.DefineDynamicAssembly . Dynamická sestavení lze vytvořit pomocí jednoho z následujících režimů přístupu:

Režim přístupu musí být určen poskytnutím příslušné AssemblyBuilderAccess hodnoty ve volání AssemblyBuilder.DefineDynamicAssembly metody při definování dynamického sestavení a nelze ji později změnit. Modul runtime používá režim přístupu dynamického sestavení k optimalizaci interní reprezentace sestavení.

Následující příklad ukazuje, jak vytvořit a spustit sestavení:

public void CreateAndRunAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
    ModuleBuilder mob = ab.DefineDynamicModule("MyModule");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder mb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
}

Trvalá dynamická sestavení v .NET

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. V .NET 9 jsme přidali 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ředtím PersistedAssemblyBuilder, protože jste mohli spustit pouze vygenerované sestavení a ne uložit jej, bylo obtížné ladit tato sestavení v paměti. 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 public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null) 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á 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.

  • 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), může být typeof(object).Assemblyzákladní sestavení . Referenční sestavení nejsou v tomto případě nutná.

Následující příklad ukazuje, jak vytvořit a uložit sestavení do datového proudu a spustit ho:

public void CreateSaveAndRunAssembly(string assemblyPath)
{
    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 chcete nastavit vstupní bod pro spustitelný soubor nebo nastavit jiné možnosti pro soubor sestavení, můžete volat metodu public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) a použít vyplněná metadata pro generování sestavení s požadovanými možnostmi, například:

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

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.

Trvalá dynamická sestavení v rozhraní .NET Framework

V rozhraní .NET Framework lze dynamická sestavení a moduly ukládat do souborů. Pro podporu této funkce AssemblyBuilderAccess výčet deklaruje dvě další pole: Save a RunAndSave.

Dynamické moduly v trvalém dynamickém sestavení jsou uloženy při uložení dynamického sestavení pomocí Save metody. Chcete-li vygenerovat spustitelný soubor, SetEntryPoint musí být volána metoda, která je vstupním bodem sestavení. Sestavení se ve výchozím nastavení ukládají jako knihovny DLL, pokud SetEntryPoint metoda nepožádá o generování konzolové aplikace nebo aplikace založené na systému Windows.

Následující příklad ukazuje, jak vytvořit, uložit a spustit sestavení pomocí rozhraní .NET Framework.

public void CreateRunAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder mob = ab.DefineDynamicModule("MyAssembly.dll");
    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);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
    ab.Save("MyAssembly.dll");
}

Některé metody základní Assembly třídy, například GetModules a GetLoadedModules, nebudou při zavolání z AssemblyBuilder objektů fungovat správně. Můžete načíst definované dynamické sestavení a volat metody na načtené sestavení. Pokud chcete například zajistit, aby moduly prostředků byly zahrnuty do vráceného seznamu modulů, zavolejte GetModules načtený Assembly objekt. Pokud dynamické sestavení obsahuje více než jeden dynamický modul, měl by název souboru manifestu sestavení odpovídat názvu modulu, který je zadaný jako první argument metody DefineDynamicModule .

Podepisování dynamického sestavení pomocí KeyPair není účinné, dokud se sestavení neuloží na disk. Silné názvy proto nebudou fungovat s přechodnými dynamickými sestaveními.

Dynamická sestavení mohou odkazovat na typy definované v jiném sestavení. Přechodné dynamické sestavení může bezpečně odkazovat na typy definované v jiném přechodném dynamickém sestavení, trvalé dynamické sestavení nebo statické sestavení. Modul CLR (Common Language Runtime) však neumožňuje trvalý dynamický modul odkazovat na typ definovaný v přechodném dynamickém modulu. Důvodem je to, že když se trvalý dynamický modul načte po uložení na disk, modul runtime nemůže přeložit odkazy na typy definované v přechodném dynamickém modulu.

Omezení generování do vzdálených domén aplikací

Některé scénáře vyžadují vytvoření a spuštění dynamického sestavení ve vzdálené doméně aplikace. generování Reflexe ion neumožňuje, aby dynamické sestavení bylo generováno přímo do vzdálené domény aplikace. Řešením je generovat dynamické sestavení v aktuální doméně aplikace, uložit generované dynamické sestavení na disk a pak dynamické sestavení načíst do vzdálené domény aplikace. Vzdálené komunikace a domény aplikací jsou podporovány pouze v rozhraní .NET Framework.