quarta-feira, 29 de julho de 2009

Por Dentro do IL

Prefácio:

Nossa proposta nesse espaço inicialmente seria de analisarmos a linguagem C#, sua sintaxe, implementações dos conceitos de orientação a objetos, etc. Para tal acredito que a implementação de programas console [aqueles que rodam no DOS], seja a melhor alternativa nesse primeiro momento, sem dúvida o Visual Studio é uma ferramenta poderosíssima, mas tentaremos não fazer uso do mesmo, para uma maior intimidade com a linguagem, o compilador, suas ferramentas e as diversas técnicas de desenvolvimento que o ambiente nos permite. Uma ressalva ao editor de programas, onde o Visual Studio tem uma interface bastante agradável (destaque para identação e visual das linhas de código), no entanto para os mais nostálgicos o uso do Edit é bem vindo.

Pré-requisitos:

Para execução de um programa .net o mínimo necessário é a instalação do Framework, disponível para download no site da Microsoft [http://msdn.microsoft.com/netframework/technologyinfo/howtoget/default.aspx]. Uma vez instalado temos disponíveis os compiladores VB e C#, para nosso caso o programa csc [digite csc /? para maiores detalhes de compilação]. Caso tenhamos apenas o framework instalado seria interessante adicionar à variável de ambiente Path, o caminho do programa csc, se tivermos o Visual Studio instalado utilize a opção Visual Studio Command Prompt, dentre as ferramentas do VS.

Finalmente:

Bom pessoal, nada mais batido como primeira lição nos escritos de qualquer linguagem de programação que o conhecido “Hello Word”, pois bem, não faremos diferente, no entanto, utilizaremos desse programa para passarmos algumas informações importantes para o entendimento de qualquer programa C#. Faremos uma viagem pelo universo do MSIL, a mágica da Microsoft para que os programas sejam multiplataformas, independente de linguagem, seguros, versionados, etc. O MSIL, nada mais é que uma linguagem intermediaria gerada no processo de compilação do código fonte, assim ao final desse processo temos um código intermediário e dependente do runtime do Framework ao invés dos executáveis “independentes”, até então gerado pelos compiladores [e ainda possível nos compiladores C++, e C# em situações especiais]. Esse código é formado por um conjunto de instruções em linguagem intermediária e por metadados, conhecido como MANIFESTO, onde temos descrito informações essenciais para execução do código, tais como definição de tipos, controle de versão [e ai o fim da terrível DLL HELL, ou incompatibilidade de versões], referencia à assemblies externos, dentre outras funcionalidades.

Esse formato do código, com dados e metadados, permite aos assemblies uma autodescrição o que dispensa a necessidade das antigas Type Libraries ou IDLs. Assim o próprio runtime do framework, localiza e obtém as informações necessárias para executar as instruções conforme preciso, tudo junto num mesmo lugar.

O processo de execução inicia-se com o runtime do Framework [o conhecido CLR], que carrega do código IL e o executa, como já dissemos, o código tem é gerenciado e seguro. No entanto, antes dessa execução o CLR, garante a validade do código para só então compilá-lo, gerando um código de fato executável compatível com o processador, através do just-in-time compiler [JITter] que é cacheado, e caso haja alteração no código fonte / IL o mesmo é atualizado. Durante a execução do código gerenciado, o mesmo recebe serviços como garbage collection [responsável pela uma “limpeza” periódica na memória], interoperabilidade com códigos não gerenciados, serviço de controle de versão, segurança, etc, ou seja nosso processo fica constantemente monitorando para garantir sua qualidade em performance, segurança, gerenciamento otimizado da memória e outras funcionalidades providas pelo Framework.
A seguir, analisaremos nosso programa, o compilaremos e analisaremos seu código intermediário.

using System; //Referenciando namespace System
namespace Imasters //Definindo namespace Imasters
{
class hello //Definindo classe hello
{
public static void Main() //Função principal
{
for(int i=0;i<5;i++)


Console.WriteLine("\tBem-Vindo à coluna - C# e Tecnologias .NET!!!");
}

}

}

Nada mais simples, um programa que exibe uma mensagem cinco vezes. Utilizando o velho notepad, salvemos um arquivo hello.cs. e vamos para o processo de compilação:

Compilando:
csc hello.cs [Será gerado um “executável”, hello.exe.]

Bom, como já dissemos, o código aparentemente é como outro qualquer, no entanto podemos visualizar seu IL através do utilitário ILDASM, executado a partir do diretório onde temos nosso executável. Ildasm hello.exe


Figura 1
Aqui temos claramente as seções do nosso código, acima o manifesto, com os metadados do programa, em seguida temos o namespace [veremos em artigos posteriores] Imasters, a classe hello e abaixo as subseções, com o construtor da classe e o método principal, Main.
















Figura 2

Na figura 2 temos os metadados dos nossos aplicativo.



Na linha, .assembly extern mscorlib, temos uma referencia ao assembly externo, no caso, o mscorlib [mscoree.dll, usado pelo sistema operacional para carregar o executável “intermediário”]. O compilador insere alguns parâmetros para identificação e segurança como, por exemplo, .publickeytoken, que é a identificação básica do assembly e .ver que corresponde à versão do mesmo.
Em seguida temos a seção .assembly, com seus parâmetros .hash, utilizado para garantir que o assembly referenciado não tenha sido modificado e .ver que representa a versão do nosso código, aqui não especificado, nas opções de compilação.

Finalmente temos a seção .module que identifica nosso assembly [hello.exe], aqui temos um MVID, que o identifica, e é regerado a cada compilação, informações de identificação e localização do arquivo físico.









Figura 3
Aqui já vemos nossos primeiros códigos MSIL, estamos diante do construtor de nossa classe, que a principio é executado para cada classe, podendo ou não ser modificado [maiores detalhes em artigos posteriores], interessante ressaltarmos a primeira linha, onde o atributo cil managed, o define como código gerenciável. As linhas IL_9999: são identificadores de instruções assembly, onde a primeira utiliza o opcode ldarg que carrega um argumento para pilha de execução, 0, indica o operador this, ou seja a própria classe, em seguida é feito uma chamada ao método System.Object e assim o controle é retornado ao método que o chamou.














Figura 4
Finalmente chegamos em nosso código propriamente dito. Mais uma vez vemos que o método é gerenciado pelo atributo cil managed. Aqui destaque para algumas diretivas: .entrypoint, que especifica Main como ponto de entrada do programa. A diretiva .maxstack que especifica o tamanho máximo na pilha para o método. Em seguida temos os códigos com as instruções do programa, destaque para ldstr que carrega uma string na pilha de execução, br.s, que desvia a seqüência de execução e blt.s, que desvia seqüência condicionalmente, call que faz uma chamada ao método estático System.ConsoleWriteLine e por fim o retorno ao método que iniciou o processo [no caso _CorExeMain da mscoree.dll].

É isso ai pessoal, temos uma referencia enorme de opcodes IL, que na verdade dificilmente precisaremos manipular [não que seja impossível], ainda assim acho válido entendermos o funcionamento dos nossos assemblies, e o interessante é que tudo no .NET funciona assim, seja uma o code-behind de uma página ASPX, um WebService, uma DLL no COM+, um aplicativo console, qualquer coisa [opa, menos os programas não gerenciados, que com ojá dissemos, podem ser codificados em C++, ou mesmo no nosso C#].

Segue um guia de referencia interessante sobre o conjunto de instruções MSIL.

Ate a próxima,
Caio Azevedo.