Classe System.Reflection.Emit.AssemblyBuilder
Este artigo fornece observações complementares à documentação de referência para essa API.
Um assembly dinâmico é um assembly criado usando as APIs de emissão de reflexão. Um assembly dinâmico pode fazer referência a tipos definidos em outro assembly dinâmico ou estático. Você pode usar AssemblyBuilder para gerar assemblies dinâmicos na memória e executar seu código durante a execução do mesmo aplicativo. No .NET 9, adicionamos uma nova PersistedAssemblyBuilder
implementação totalmente gerenciada de emissão de reflexão que permite salvar o assembly em um arquivo. No .NET Framework, você pode fazer as duas coisas — executar o assembly dinâmico e salvá-lo em um arquivo. O assembly dinâmico criado para salvar é chamado de assembly persistente , enquanto o assembly regular somente memória é chamado de transitório ou executável. No .NET Framework, um assembly dinâmico pode consistir em um ou mais módulos dinâmicos. No .NET Core e no .NET 5+, um assembly dinâmico só pode consistir em um módulo dinâmico.
A maneira como você cria uma AssemblyBuilder instância difere para cada implementação, mas as etapas adicionais para definir um módulo, tipo, método ou enum e para escrever IL são bastante semelhantes.
Assemblies dinâmicos executáveis no .NET
Para obter um objeto executável AssemblyBuilder , use o AssemblyBuilder.DefineDynamicAssembly método. Os assemblies dinâmicos podem ser criados usando um dos seguintes modos de acesso:
-
O assembly dinâmico representado por um AssemblyBuilder pode ser usado para executar o código emitido.
AssemblyBuilderAccess.RunAndCollect
O assembly dinâmico representado por um AssemblyBuilder pode ser usado para executar o código emitido e é recuperado automaticamente pelo coletor de lixo.
O modo de acesso deve ser especificado fornecendo o valor apropriado AssemblyBuilderAccess na chamada para o AssemblyBuilder.DefineDynamicAssembly método quando o assembly dinâmico é definido e não pode ser alterado posteriormente. O tempo de execução usa o modo de acesso de um assembly dinâmico para otimizar a representação interna do assembly.
O exemplo a seguir demonstra como criar e executar um assembly:
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 }));
}
Assemblies dinâmicos persistentes no .NET
A AssemblyBuilder.Save API não foi originalmente portada para o .NET (Core) porque a implementação dependia muito do código nativo específico do Windows que também não foi portado. No .NET 9, adicionamos uma implementação totalmente gerenciada Reflection.Emit
que oferece suporte à salvamento. Essa implementação não tem dependência da implementação pré-existente, específica Reflection.Emit
do tempo de execução. Ou seja, agora existem duas implementações diferentes no .NET, executáveis e persistentes. Para executar o assembly persistente, primeiro salve-o em um fluxo de memória ou em um arquivo e, em seguida, carregue-o de volta. Antes PersistedAssemblyBuilder
, como você só podia executar um assembly gerado e não salvá-lo, era difícil depurar esses assemblies na memória. As vantagens de salvar um assembly dinâmico em um arquivo são:
- Você pode verificar o assembly gerado com ferramentas como ILVerify ou descompilá-lo e examiná-lo manualmente com ferramentas como ILSpy.
- O assembly salvo pode ser carregado diretamente, sem necessidade de compilar novamente, o que pode diminuir o tempo de inicialização do aplicativo.
Para criar uma PersistedAssemblyBuilder
instância, use o public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null)
construtor. O coreAssembly
parâmetro é usado para resolver tipos de tempo de execução base e pode ser usado para resolver o controle de versão do assembly de referência:
Se
Reflection.Emit
for usado para gerar um assembly destinado a um TFM específico, abra os assemblies de referência para o TFM fornecido usandoMetadataLoadContext
e use o valor da propriedade MetadataLoadContext.CoreAssembly paracoreAssembly
. Esse valor permite que o gerador seja executado em uma versão de tempo de execução do .NET e direcione uma versão de tempo de execução diferente do .NET.Se
Reflection.Emit
for usado para gerar um assembly que só será executado na mesma versão de tempo de execução que a versão de tempo de execução em que o compilador está sendo executado (normalmente no proc), o assembly principal pode sertypeof(object).Assembly
. Os assemblies de referência não são necessários nesse caso.
O exemplo a seguir demonstra como criar e salvar um assembly em um fluxo e executá-lo:
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 }));
}
Para definir o ponto de entrada para um executável e/ou definir outras opções para o arquivo assembly, você pode chamar o public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
método e usar os metadados preenchidos para gerar o assembly com as opções desejadas, por exemplo:
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);
Observação
Os tokens de metadados para todos os membros são preenchidos Save na operação. Não use os tokens de um tipo gerado e seus membros antes de salvar, pois eles terão valores padrão ou lançarão exceções. É seguro usar tokens para tipos que são referenciados, não gerados.
Algumas APIs que não são importantes para emitir um assembly não são implementadas; por exemplo, GetCustomAttributes()
não é implementado. Com a implementação de tempo de execução, você pôde usar essas APIs depois de criar o tipo. Para os persistentes AssemblyBuilder
, eles jogam NotSupportedException
ou NotImplementedException
. Se você tiver um cenário que exija essas APIs, registre um problema no repositório dotnet/runtime.
Para obter uma maneira alternativa de gerar arquivos de assembly, consulte MetadataBuilder.
Assemblies dinâmicos persistentes no .NET Framework
No .NET Framework, assemblies dinâmicos e módulos podem ser salvos em arquivos. Para oferecer suporte a esse recurso, a AssemblyBuilderAccess enumeração declara dois campos adicionais: Save e RunAndSave.
Os módulos dinâmicos no assembly dinâmico persistente são salvos quando o assembly dinâmico é salvo usando o Save método. Para gerar um executável, o SetEntryPoint método deve ser chamado para identificar o método que é o ponto de entrada para o assembly. Os assemblies são salvos como DLLs por padrão, a menos que o SetEntryPoint método solicite a geração de um aplicativo de console ou um aplicativo baseado no Windows.
O exemplo a seguir demonstra como criar, salvar e executar um assembly usando o .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");
}
Alguns métodos na classe base Assembly , como GetModules
e GetLoadedModules
, não funcionarão corretamente quando chamados a partir de AssemblyBuilder objetos. Você pode carregar o assembly dinâmico definido e chamar os métodos no assembly carregado. Por exemplo, para garantir que os módulos de recursos sejam incluídos na lista de módulos retornados, chame GetModules
o objeto carregado Assembly . Se um assembly dinâmico contiver mais de um módulo dinâmico, o nome do arquivo de manifesto do assembly deverá corresponder ao nome do módulo especificado como o primeiro argumento do DefineDynamicModule método.
A assinatura de um assembly dinâmico usando KeyPair não é eficaz até que o assembly seja salvo em disco. Assim, nomes fortes não funcionarão com montagens dinâmicas transitórias.
Os assemblies dinâmicos podem fazer referência a tipos definidos em outro assembly. Um assembly dinâmico transitório pode referenciar com segurança tipos definidos em outro assembly dinâmico transitório, um assembly dinâmico persistente ou um assembly estático. No entanto, o Common Language Runtime não permite que um módulo dinâmico persistente faça referência a um tipo definido em um módulo dinâmico transitório. Isso ocorre porque quando o módulo dinâmico persistente é carregado após ser salvo no disco, o tempo de execução não pode resolver as referências a tipos definidos no módulo dinâmico transitório.
Restrições à emissão para domínios de aplicativos remotos
Alguns cenários exigem que um assembly dinâmico seja criado e executado em um domínio de aplicativo remoto. A emissão de reflexão não permite que um assembly dinâmico seja emitido diretamente para um domínio de aplicativo remoto. A solução é emitir o assembly dinâmico no domínio do aplicativo atual, salvar o assembly dinâmico emitido no disco e carregar o assembly dinâmico no domínio do aplicativo remoto. Os domínios de aplicativo e comunicação remota são suportados somente no .NET Framework.
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de