Desenvolvimento - Visual Basic .NET
Geração de Assemblies em Run-Time
Todo o código de qualquer linguagem .NET é convertido para a forma de bytecodes chamada IL (ou MSIL ou ainda CIL). IL significa Intermediate Language - linguagem intermediária...
por Washington Coutinho Corrêa JuniorTodo o código de qualquer linguagem .NET é
convertido para a forma de bytecodes chamada IL (ou MSIL ou ainda CIL). IL
significa Intermediate Language - linguagem intermediária. Note que existe o IL
e o IL Assembly, sendo coisas distintas. O primeiro é formado por códigos
binários, enquanto que o segundo é sob a forma de texto puro (como qualquer
outra linguagem). Pode-se programar diretamente em IL Assembly e depois compilar
o código através do compilador ilasm.exe. O contrário também pode ser feito, ou
seja, possuindo um código compilado ver o seu respectivo código IL Assembly,
através do aplicativo ildasm.exe. Todavia, a .NET Framework fornece classes (em
System.Reflection.Emit) que permitem a geração do código IL (não o fonte, mas
sim o já compilado) de forma bastante confortável. O que isso significa? Que
apesar de ser estaticamente compilada, a .NET pode ser tão poderosa quanto uma
linguagem de natureza dinâmica. Os assemblies gerados podem ser carregados
durante a execução (mesmo imediatamente após terem sido gerados), lembrando que
um assembly é um bloco de código compilado.
Com a possibilidade de gerar novos
tipos (Types) dentro desses assemblies, tem-se
então a possibilidade de carregar dinamicamente esses tipos (classes) em tempo
de execução. Com a relativa simplicidade de criação desses assemblies oferecida
pela framework e os opcodes disponíveis na IL tem-se a capacidade de criar novas
linguagens para a .NET Framework com uma das já existentes (por exemplo, VB.NET
ou C#).
A outra possibilidade é compilar o código de uma das linguagens
existentes, sem ter que usar o compilador propriamente dito. Ou seja, nada
impede a criação de "scripts" em C# ou VB.NET, que seriam executados e
compilados na hora, pelo seu programa, permitindo ainda a modificação livre
deles, além da recompilação e reexecução.
Outro detalhe muito interessante é que a geração e o carregamento dos assemblies
não precisa ficar restrito apenas a execução no momento, podendo ser gravado em
disco. Sim, sob a forma de um executável (.exe) ou de uma biblioteca (.dll), e
creio eu que esta seja uma característica desejável para quem quer criar sua
própria linguagem de programação, ainda mais com a possibilidade de utilização
das bibliotecas da CLR. Aliás, é justamente esse ponto que explorarei no código
presente neste artigo. Futuramente farei outro explicando a criação e execução
dos assemblies em tempo de execução, como se fossem scripts.
O código abaixo, em VB.NET, gera um executável chamado "teste.exe" na pasta
"c:\programa" (tenha certeza de que esta pasta existe, pois o código não
verificará isso). Este executável simplesmente escreve "teste" no console. Ele
tem um módulo, uma classe e uma rotina chamada Main(), que é o ponto de entrada
para a execução. Sua versão em C# segue logo abaixo.
Código em VB.NET: |
Imports
System Imports System.Reflection Imports System.Reflection.Emit Public Module Teste Public Sub Main() Dim nome As New AssemblyName() Dim domínio As AppDomain Dim construtor As AssemblyBuilder Dim módulo As ModuleBuilder Dim classe As TypeBuilder Dim método As MethodBuilder Dim il As ILGenerator nome.Name = "teste_assembly" Dim arquivoEXE As String = "teste3.exe" Dim pastaDestino As String = "C:\programa" "Obtendo a thread atual domínio = System.Threading.Thread.GetDomain() "Construtor para assemblies dinâmicos construtor = domínio.DefineDynamicAssembly(nome, AssemblyBuilderAccess.Save, pastaDestino) "Criando o módulo módulo = construtor.DefineDynamicModule("teste_module", arquivoEXE) "Definindo a classe (não criando... isso está mais abaixo) classe = módulo.DefineType("Teste") "Parâmetros do método (no caso, nenhum) Dim parâmetros() As Type "Declarando o método Main (com tipo de retorno como void e os parâmetros acima (que, no caso, não tem)) método = classe.DefineMethod("Main", MethodAttributes.Public Or MethodAttributes.Static, System.Type.GetType("void"), parâmetros) "Inicializando o gerador de IL il = método.GetILGenerator() "Emitindo os códigos que formarão o método il.EmitWriteLine("teste") "Escrever "teste" na tela: o mesmo que System.Console.Writeline("teste") il.Emit(OpCodes.Ret) "Opcode para o return (é necessário!) "Criando a classe classe.CreateType() "Definindo o ponto de entrada do assembly (é necessário para a formação de EXEs; para DLLs não o é) construtor.SetEntryPoint(método) "Salvando o assembly construtor.Save(arquivoEXE) End Sub End Module |
Código em C#: |
using
System; using System.Reflection; using System.Reflection.Emit; public class Teste { public static void Main() { AssemblyName nome = new AssemblyName(); AppDomain domínio; AssemblyBuilder construtor; ModuleBuilder módulo; TypeBuilder classe; MethodBuilder método; ILGenerator il; nome.Name = "teste_assembly"; string arquivoEXE = "teste2.exe"; string pastaDestino = "C:/programa"; //Obtendo a thread atual domínio = System.Threading.Thread.GetDomain(); //Construtor para assemblies dinâmicos construtor = domínio.DefineDynamicAssembly(nome, AssemblyBuilderAccess.Save, pastaDestino); //Criando o módulo módulo = construtor.DefineDynamicModule("teste_module", arquivoEXE); //Definindo a classe (não criando... isso está mais abaixo) classe = módulo.DefineType("Teste"); //Parâmetros do método Type[] parâmetros = null; //Declarando o método Main (com tipo de retorno como void e os parâmetros acima (que, no caso, não tem)) método = classe.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static, System.Type.GetType("void"), parâmetros); //Inicializando o gerador de IL il = método.GetILGenerator(); //Emitindo os códigos que formarão o método il.EmitWriteLine("teste"); //Escrever "teste" na tela: o mesmo que System.Console.Writeline("teste") il.Emit(OpCodes.Ret); //Opcode para o return (é necessário!) //Criando a classe classe.CreateType(); //Definindo o ponto de entrada do assembly (é necessário para a formação de EXEs; para DLLs não o é) construtor.SetEntryPoint(método); //Salvando o assembly construtor.Save(arquivoEXE); } } |
O principal atrativo desse código é o objeto "il", que é o responsável por gerar as instruções a serem executadas. Através do método Emit(), podemos especificar os opcodes da IL a serem executados. No caso acima, o EmitWriteline() já faz automaticamente o trabalho de dois Emit()s diferentes, como um atalho para (em VB.NET):
Código em VB.NET: |
il.Emit(Emit.OpCodes.Ldstr,
"teste")
"Carregando a string
"teste" na pilha Dim métodoWriteline As MethodInfo = GetType(Console).GetMethod("WriteLine", New Type() {GetType(String)}) il.Emit(Emit.OpCodes.Call, métodoWriteline) "Fazendo a chamada do método WriteLine |
Note que foi necessário obter a assinatura
completa do método WriteLine(). Através do EmitWriteline() isso já é obtido
automaticamente.
Quaisquer erros, sugestões ou dúvidas são mui bem apreciadas por parte do autor,
contactável através do e-mail: washingtonj@openlink.com.br. Happy dotNettin"!
Assim como usei o AssemblyBuilder, ModuleBuilder, TypeBuilder e o MethodBuilder
para criar, respectivamente, o assembly, o módulo, a classe (tipo) e um método,
existem outros builders (como para os atributos, por exemplo). Daí em diante,
basta explorar as possibilidades do namespace System.Reflection.Emit. Um ponto
de partida é dar uma olhada no código-fonte do MyC, um exemplo de um compilador
C que acompanha o SDK da Framework. Foi a partir dele e da criação de um simples
executável em VB.NET (cujo código IL Assembly pude investigar através do ildasm)
que cheguei ao código acima. O ideal seria estudar a IL Assembly separadamente,
como uma linguagem a parte, mas sempre que necessário, pode-se fazer o desejado
em C# ou VB.NET (ou qualquer outra linguagem .NET familiar), e dar uma olhadela
no IL Assembly (através do ildasm) do mesmo a fim de saber quais os opcodes
usados para tal tarefa.
- Entity Framework 4: Repositório GenéricoVisual Basic .NET
- As edições 14 da Easy .net Magazine e 88 da .net Magazine já estão disponíveis.ADO.NET
- Postando no Twiiter com .NET e Migre.meC#
- Setup ApplicationsVisual Basic .NET
- Problemas na manipulação de arquivos do MS Excel com .NETVisual Basic .NET