Processo de execução gerenciada

O processo de execução gerenciada inclui as seguintes etapas, que serão discutidas em detalhes mais adiante neste tópico:

  1. Escolhendo um compilador. Para obter as vantagens fornecidas pelo Common Language Runtime, você deve usar um ou mais compiladores de linguagem que selecionam o tempo de execução.
  2. Compilando o código para a linguagem intermediária. A compilação traduz seu código-fonte em Common Intermediate Language (CIL) e gera os metadados necessários.
  3. Compilação da CIL para código nativo. No momento da execução, um compilador just-in-time (JIT) converte a CIL em código nativo. Durante essa compilação, o código deve passar por um processo de verificação que examina a CIL e os metadados para descobrir se o código pode ser determinado como seguro para o tipo.
  4. Executando o código. O Common Language Runtime fornece a infraestrutura que permite que a execução ocorra e os serviços que podem ser usados durante a execução.

Escolha um compilador

Para obter as vantagens fornecidas pelo CLR (Common Language Runtime), você deve usar um ou mais compiladores de linguagem que direcionam o tempo de execução, como Visual Basic, C#, Visual C++, F# ou um dos muitos compiladores de terceiros como um Eiffel, um Perl ou um compilador COBOL.

Por ser um ambiente de execução multilíngue, o runtime dá suporte a uma grande variedade de tipos de dados e recursos de linguagem. O compilador de linguagem que você usa determina quais recursos do runtime estão disponíveis, e você cria seu código usando esses recursos. O seu compilador, e não o runtime, estabelece a sintaxe que seu código deve usar. Se o componente deve ser completamente utilizável por componentes escritos em outras linguagens, os tipos exportados do seu componente devem expor somente recursos de linguagem incluídos no CLS (Common Language Specification). Você pode usar o atributo CLSCompliantAttribute para garantir que seu código seja compatível com CLS. Para obter mais informações, consulte Independência de linguagem e componentes de linguagem independente.

Compilar para CIL

Ao compilar para o código gerenciado, o compilador traduz o código-fonte em Common Intermediate Language (CIL), que é um conjunto de instruções independente da CPU que pode ser eficientemente convertido em código nativo. A CIL inclui instruções para carregar, armazenar, inicializar e chamar métodos em objetos, bem como instruções para operações aritméticas e lógicas, fluxo de controle, acesso direto à memória, tratamento de exceções e outras operações. Antes que o código possa ser executado, a CIL deve ser convertida em código específico da CPU, geralmente por um compilador just-in-time (JIT). Como o Common Language Runtime fornece um ou mais compiladores JIT para cada arquitetura de computador que ele dá suporte, o mesmo conjunto de CIL pode ser compilado JIT e executado em qualquer arquitetura suportada.

Quando um compilador produz a CIL, ele também produz metadados. Metadados descrevem os tipos no seu código, incluindo a definição de cada tipo, as assinaturas de membros de cada tipo, os membros que seu código referencia e outros dados que o runtime usa no runtime. A CIL e os metadados estão contidos em um arquivo executável portátil (PE) que se baseia e amplia o Microsoft PE publicado e o formato de arquivo de objeto comum (COFF) usado historicamente para conteúdo executável. Esse formato de arquivo, que acomoda a CIL ou o código nativo, bem como os metadados, permite que o sistema operacional reconheça imagens da Common Intermediate Language. A presença de metadados no arquivo, juntamente com a CIL, permite que seu código se descreva, o que significa que não há necessidade de bibliotecas de tipos ou de linguagem IDL (IDL). O runtime localiza e extrai os metadados do arquivo conforme necessário durante a execução.

Compilar a CIL em código nativo

Antes de poder executar a Common Intermediate Language (CIL), ela deve ser compilada em relação ao Common Language Runtime para o código nativo da arquitetura do computador de destino. O .NET fornece duas maneiras de realizar essa conversão:

Compilação pelo compilador JIT

A compilação JIT converte a CIL em código nativo sob demanda no tempo de execução do aplicativo, quando o conteúdo de um assembly é carregado e executado. Como o Common Language Runtime fornece um compilador JIT para cada arquitetura de CPU com suporte, os desenvolvedores podem criar um conjunto de assemblies de CIL que podem ser compilados por JIT e executados em diferentes computadores com diferentes arquiteturas de máquina. No entanto, se seu código gerenciado chama APIs nativas específicas da plataforma ou uma biblioteca de classes específica da plataforma, ele será executado somente nesse sistema operacional.

A compilação JIT leva em conta a possibilidade de um código jamais ser chamado durante a execução. Em vez de usar tempo e memória para converter todo a CIL em um arquivo PE em código nativo, ele converte a CIL conforme necessário durante a execução e armazena o código nativo resultante na memória para que fique acessível para chamadas subsequentes no contexto desse processo. O carregador cria e anexa um stub para cada método em um tipo quando o tipo é carregado e inicializado. Quando um método é chamado pela primeira vez, o stub passa o controle para o compilador JIT, que converte a CIL desse método em código nativo e modifica o stub para apontar diretamente para o código nativo gerado. Portanto, as chamadas subsequentes para o método compilado por JIT vão diretamente para o código nativo.

Geração de código em tempo de instalação usando o NGen.exe.

Como o compilador JIT converte a CIL de um assembly em código nativo quando métodos individuais definidos nesse assembly são chamados, isso afeta negativamente o desempenho em tempo de execução. Na maioria dos casos, esse desempenho diminuído é aceitável. Mais importante, o código gerado pelo compilador JIT é associado ao processo que disparou a compilação. Ele não pode ser compartilhado por vários processos. Para permitir que o código gerado seja compartilhado por várias invocações de um aplicativo ou por vários processos que compartilham um conjunto de assemblies, o CLR dá suporte a um modo de compilação antecipado. Esse modo de compilação antecipada usa o Ngen.exe (Gerador de Imagem Nativa) para converter os assemblies da CIL em código nativo, da mesma forma que o compilador JIT. No entanto, a operação de Ngen.exe é diferente da operação do compilador JIT de três maneiras:

  • Ele realiza a conversão de CIL para código nativo antes de executar o aplicativo, e não durante a execução do aplicativo.
  • Ele cria um assembly inteiro por vez, em vez de um método de cada vez.
  • Ele mantém o código gerado no Cache de Imagem Nativa como um arquivo no disco.

Verificação de código

Como parte da compilação para código nativo, o código CIL deve passar por um processo de verificação, a menos que um administrador tenha estabelecido uma política de segurança que permita que o código ignore a verificação. A verificação examina a CIL e os metadados para descobrir se o código é seguro para o tipo, o que significa que ele acessa somente os locais de memória que está autorizado a acessar. A segurança de tipo ajuda a isolar objetos uns dos outros e a protegê-los de danos não intencionais ou corrupção mal-intencionada. Ela também dá garantia de que restrições de segurança no código possam ser aplicadas confiavelmente.

O runtime depende das seguintes declarações serem verdadeiras para o código que é comprovadamente fortemente tipado:

  • Uma referência a um tipo é estritamente compatível com o tipo que está sendo referenciado.
  • Somente operações definidas adequadamente são invocadas em um objeto.
  • Identidades são o que elas afirmam ser.

Durante o processo de verificação, o código da CIL é examinado em uma tentativa de confirmar que o código pode acessar locais de memória e chamar métodos somente por meio de tipos definidos corretamente. Por exemplo, o código não pode permitir que campos de um objeto sejam acessados de uma maneira que permita que locais de memória sejam saturados. Além disso, a verificação inspeciona o código para determinar se a CIL foi gerada corretamente, pois uma CIL incorreta pode levar a uma violação das regras de segurança de tipo. O processo de verificação passa um conjunto bem definido de código fortemente tipado, e ele passa apenas código fortemente tipado. Entretanto, alguns códigos fortemente tipados podem não passar nessa verificação devido às limitações do processo de verificação, e algumas linguagens, por projeto, não produzem código fortemente tipado verificável. Se o código fortemente tipado for exigido pela política de segurança mas o código não passar pela verificação, uma exceção será lançada quando o código for executado.

Executar código

O CLR fornece a infraestrutura que permite que a runtime gerenciada ocorra e os serviços que podem ser usados durante a runtime. Para um método ser executado, ele deve ser compilado para o código específico do processador. Cada método para o qual a CIL foi gerada é compilado pelo JIT quando é chamado pela primeira vez e, em seguida, executado. Na próxima vez em que o método for executado, o código nativo compilado por JIT existente será executado. O processo de compilação por JIT e a execução do código é repetido até a execução ser concluída.

Durante a execução, o código gerenciado recebe serviços como coleta de lixo, segurança, interoperabilidade com código não gerenciado, suporte à depuração entre linguagens e suporte avançado à implantação e ao controle de versão.

No Microsoft Windows Vista, o carregador do sistema operacional verifica módulos gerenciados examinando um bit no cabeçalho COFF. Se o bit estiver definido, o módulo será gerenciado. Se o carregador detecta módulos gerenciados, ele carrega mscoree.dll e _CorValidateImage, e _CorImageUnloading notifica o carregador quando as imagens do módulo gerenciado são carregadas e descarregadas. _CorValidateImage executa as seguintes ações:

  1. Garante que o código seja um código gerenciado válido.
  2. Altera o ponto de entrada na imagem para um ponto de entrada no ambiente de runtime.

No Windows 64 bits, _CorValidateImage modifica a imagem que está na memória transformando-a do formato PE32 para o formato PE32+.

Confira também