Системы. Рефлексия ion. Класс Emit.AssemblyBuilder
В этой статье приводятся дополнительные замечания к справочной документации по этому API.
Динамическая сборка — это сборка, созданная с помощью API-интерфейсов Рефлексия ion Emit. Динамическая сборка может ссылаться на типы, определенные в другой динамической или статической сборке. Вы можете использовать AssemblyBuilder для создания динамических сборок в памяти и выполнения кода во время выполнения того же приложения. В .NET 9 мы добавили новый PersistedAssemblyBuilder
с полностью управляемой реализацией отражения, которая позволяет сохранить сборку в файл. В платформа .NET Framework можно выполнить оба действия: запустить динамическую сборку и сохранить ее в файле. Динамическая сборка, созданная для сохранения, называется сохраняемой сборкой, а обычная сборка только для памяти называется временной или запущенной. В платформа .NET Framework динамическая сборка может состоять из одного или нескольких динамических модулей. В .NET Core и .NET 5+ динамическая сборка может состоять только из одного динамического модуля.
Способ создания экземпляра отличается для каждой AssemblyBuilder реализации, но дальнейшие шаги по определению модуля, типа, метода или перечисления, а также для записи IL довольно похожи.
Запускаемые динамические сборки в .NET
Чтобы получить объект runnable AssemblyBuilder , используйте AssemblyBuilder.DefineDynamicAssembly этот метод. Динамические сборки можно создать с помощью одного из следующих режимов доступа:
-
Динамическая сборка, представленная приложением, AssemblyBuilder может использоваться для выполнения созданного кода.
AssemblyBuilderAccess.RunAndCollect
Динамическая сборка, представленная модулятором AssemblyBuilder , может использоваться для выполнения созданного кода и автоматически удаляется сборщиком мусора.
Режим доступа необходимо указать, указав соответствующее AssemblyBuilderAccess значение в вызове AssemblyBuilder.DefineDynamicAssembly метода при определении динамической сборки и не может быть изменено позже. Среда выполнения использует режим доступа динамической сборки для оптимизации внутреннего представления сборки.
В следующем примере показано, как создать и запустить сборку:
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 }));
}
Сохраняемые динамические сборки в .NET
AssemblyBuilder.Save API изначально не был перенесен в .NET (Core), так как реализация сильно зависит от собственного кода Windows, который также не был перенесен. В .NET 9 мы добавили полностью управляемую Reflection.Emit
реализацию, которая поддерживает сохранение. Эта реализация не зависит от существующей реализации для конкретной Reflection.Emit
среды выполнения. То есть теперь в .NET существуют две разные реализации, доступные для запуска и сохраняемые. Чтобы запустить сохраненную сборку, сначала сохраните ее в поток памяти или файл, а затем загрузите его обратно. Прежде чем PersistedAssemblyBuilder
запускать только созданную сборку и не сохранять ее, было трудно выполнить отладку этих сборок в памяти. Преимущества сохранения динамической сборки в файл:
- Вы можете проверить созданную сборку с помощью таких средств, как ILVerify, или декомпилировать и вручную проверить ее с помощью таких средств, как ILSpy.
- Сохраненная сборка может быть загружена напрямую, не нужно скомпилировать повторно, что может снизить время запуска приложения.
Чтобы создать PersistedAssemblyBuilder
экземпляр, используйте public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null)
конструктор. Параметр coreAssembly
используется для разрешения базовых типов среды выполнения и может использоваться для разрешения ссылочных версий сборки:
Если
Reflection.Emit
используется для создания сборки, предназначенной для конкретного TFM, откройте эталонные сборки для заданного TFM с помощьюMetadataLoadContext
и используйте значение свойства MetadataLoadContext.CoreAssembly дляcoreAssembly
. Это значение позволяет генератору работать в одной версии среды выполнения .NET и использовать другую версию среды выполнения .NET.Если
Reflection.Emit
используется для создания сборки, которая будет выполняться только в той же версии среды выполнения, что и версия среды выполнения, в которую выполняется компилятор (обычно в proc), базовая сборка может бытьtypeof(object).Assembly
. В этом случае ссылочные сборки не нужны.
В следующем примере показано, как создать и сохранить сборку в потоке и запустить ее:
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 }));
}
Чтобы задать точку входа для исполняемого файла или задать другие параметры для файла сборки, можно вызвать public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
метод и использовать заполненные метаданные для создания сборки с нужными параметрами, например:
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);
Примечание.
Маркеры метаданных для всех членов заполняются операцией Save . Не используйте маркеры созданного типа и его членов перед сохранением, так как они будут иметь значения по умолчанию или исключение. Безопасно использовать маркеры для типов, на которые ссылаются, а не создаются.
Некоторые API, которые не важны для создания сборки, не реализуются; Например, GetCustomAttributes()
не реализуется. Реализация среды выполнения позволяет использовать эти API после создания типа. Для сохраненных AssemblyBuilder
, они бросают NotSupportedException
или NotImplementedException
. Если у вас есть сценарий, требующий этих API, отправьте проблему в репозитории dotnet/runtime.
Альтернативный способ создания файлов сборок см. в статье MetadataBuilder.
Сохраняемые динамические сборки в платформа .NET Framework
В платформа .NET Framework динамические сборки и модули можно сохранить в файлах. Для поддержки этой функции AssemblyBuilderAccess перечисление объявляет два дополнительных поля: Save и RunAndSave.
Динамические модули в сохраняемой динамической сборке сохраняются при сохранении динамической сборки с помощью Save метода. Чтобы создать исполняемый файл, SetEntryPoint необходимо вызвать метод, чтобы определить метод, который является точкой входа в сборку. Сборки сохраняются как библиотеки DLL по умолчанию, если SetEntryPoint метод не запрашивает создание консольного приложения или приложения под управлением Windows.
В следующем примере показано, как создать, сохранить и запустить сборку с помощью платформа .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");
}
Некоторые методы базового Assembly класса, такие как GetModules
и GetLoadedModules
, не будут работать правильно при вызове из AssemblyBuilder объектов. Вы можете загрузить определенную динамическую сборку и вызвать методы в загруженной сборке. Например, чтобы убедиться, что модули ресурсов включены в возвращаемый Assembly список модулей, вызовите GetModules
загруженный объект. Если динамическая сборка содержит несколько динамических модулей, имя файла манифеста сборки должно совпадать с именем модуля, указанным в качестве первого аргумента DefineDynamicModule метода.
Подписывание динамической KeyPair сборки не действует, пока сборка не будет сохранена на диске. Поэтому строгие имена не будут работать с временными динамическими сборками.
Динамические сборки могут ссылаться на типы, определенные в другой сборке. Временная динамическая сборка может безопасно ссылаться на типы, определенные в другой временной динамической сборке, сохраняемой динамической сборке или статической сборке. Однако среда CLR не позволяет сохраняемому динамическому модулю ссылаться на тип, определенный в временном динамическом модуле. Это связано с тем, что при загрузке сохраняемого динамического модуля после сохранения на диск среда выполнения не может разрешать ссылки на типы, определенные в временном динамическом модуле.
Ограничения на выдачу доменов удаленных приложений
Для некоторых сценариев требуется создать динамическую сборку и выполнить ее в домене удаленного приложения. Рефлексия инициации не позволяет динамической сборке создаваться непосредственно в домен удаленного приложения. Решение состоит в том, чтобы выпустить динамическую сборку в текущем домене приложения, сохранить динамическую сборку на диск, а затем загрузить динамическую сборку в домен удаленного приложения. Домены удаленного взаимодействия и приложений поддерживаются только в платформа .NET Framework.
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по