Partilhar via


Processo de execução gerenciado

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

  1. Escolhendo um compilador. Para obter os benefícios fornecidos pelo common language runtime, você deve usar um ou mais compiladores de linguagem destinados ao tempo de execução.
  2. Compilando seu código para linguagem intermediária. A compilação traduz seu código-fonte em linguagem intermediária comum (CIL) e gera os metadados necessários.
  3. Compilando CIL para código nativo. No momento da execução, um compilador just-in-time (JIT) traduz o CIL em código nativo. Durante esta compilação, o código deve passar por um processo de verificação que examina o CIL e os metadados para descobrir se o código pode ser determinado como seguro para tipo.
  4. Código em execução. O common language runtime fornece a infraestrutura que permite a execução e os serviços que podem ser usados durante a execução.

Escolha um compilador

Para obter os benefícios fornecidos pelo Common Language Runtime (CLR), você deve usar um ou mais compiladores de linguagem destinados ao tempo de execução, como Visual Basic, C#, Visual C++, F# ou um dos muitos compiladores de terceiros, como um compilador Eiffel, Perl ou COBOL.

Por ser um ambiente de execução multilíngüe, o tempo de execução suporta uma ampla variedade de tipos de dados e recursos de linguagem. O compilador de linguagem que você usa determina quais recursos de tempo de execução estão disponíveis e você projeta seu código usando esses recursos. Seu compilador, não o tempo de execução, estabelece a sintaxe que seu código deve usar. Se o componente deve ser completamente utilizável por componentes escritos em outros idiomas, os tipos exportados do componente devem expor apenas os recursos de idioma incluídos na Common Language Specification (CLS). Você pode usar o CLSCompliantAttribute atributo para garantir que seu código seja compatível com CLS. Para obter mais informações, consulte Independência de idioma e componentes independentes de idioma.

Compilar para CIL

Ao compilar para código gerenciado, o compilador traduz seu código-fonte em linguagem intermediária comum (CIL), que é um conjunto de instruções independente da CPU que pode ser convertido eficientemente 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, o CIL deve ser convertido 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 suportada, o mesmo conjunto de CIL pode ser compilado em JIT e executado em qualquer arquitetura suportada.

Quando um compilador produz CIL, ele também produz metadados. Os metadados descrevem os tipos em seu código, incluindo a definição de cada tipo, as assinaturas dos membros de cada tipo, os membros aos quais seu código faz referência e outros dados que o tempo de execução usa no tempo de execução. A CIL e os metadados estão contidos em um arquivo executável portátil (PE) que se baseia e estende o Microsoft PE publicado e o COFF (Common Object File Format) usado historicamente para conteúdo executável. Esse formato de arquivo, que acomoda CIL ou código nativo, bem como metadados, permite que o sistema operacional reconheça imagens de Common Language Runtime. 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 IDL (Interface Definition Language). O tempo de execução localiza e extrai os metadados do arquivo conforme necessário durante a execução.

Compilar CIL para código nativo

Antes de executar a linguagem intermediária comum (CIL), ela deve ser compilada em relação ao Common Language Runtime para código nativo para a arquitetura da máquina de destino. O .NET fornece duas maneiras de executar essa conversão:

Compilação pelo compilador JIT

A compilação JIT converte CIL em código nativo sob demanda em 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 suportada, os desenvolvedores podem criar um conjunto de assemblies CIL que podem ser compilados em JIT e executados em computadores diferentes com arquiteturas de máquina diferentes. No entanto, se o código gerenciado chamar 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 que algum código nunca seja chamado durante a execução. Em vez de usar tempo e memória para converter toda 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 seja acessível para chamadas subsequentes no contexto desse processo. O carregador cria e anexa um stub a 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 JIT vão diretamente para o código nativo.

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

Como o compilador JIT converte a CIL de um assembly em código nativo quando os métodos individuais definidos nesse assembly são chamados, isso afeta negativamente o desempenho em tempo de execução. Na maioria dos casos, essa diminuição do desempenho é aceitável. Mais importante ainda, o código gerado pelo compilador JIT está vinculado ao processo que desencadeou a compilação. Ele não pode ser compartilhado entre vários processos. Para permitir que o código gerado seja compartilhado entre várias invocações de um aplicativo ou entre vários processos que compartilham um conjunto de assemblies, o Common Language Runtime oferece suporte a um modo de compilação antecipada. Esse modo de compilação antecipada usa o Ngen.exe (Native Image Generator) para converter assemblies CIL em código nativo, assim como o compilador JIT. No entanto, o funcionamento do Ngen.exe difere do compilador JIT de três maneiras:

  • Ele executa a conversão de CIL para código nativo antes de executar o aplicativo em vez de enquanto o aplicativo está em execução.
  • Ele compila um assembly inteiro de cada vez, em vez de um método de cada vez.
  • Ele persiste o código gerado no cache de imagem nativo como um arquivo no disco.

Verificação de código

Como parte de sua 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, o que significa que ele acessa apenas os locais de memória que está autorizado a acessar. A segurança de tipo ajuda a isolar objetos uns dos outros e ajuda a protegê-los de corrupção inadvertida ou maliciosa. Ele também fornece garantia de que as restrições de segurança no código podem ser aplicadas de forma confiável.

O tempo de execução depende do fato de que as seguintes instruções são verdadeiras para o código que é verificável tipo seguro:

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

Durante o processo de verificação, o código CIL é examinado na tentativa de confirmar que o código pode acessar locais de memória e métodos de chamada somente através de tipos definidos corretamente. Por exemplo, o código não pode permitir que os campos de um objeto sejam acessados de uma maneira que permita que os locais de memória sejam saturados. Além disso, a verificação inspeciona o código para determinar se a CIL foi gerada corretamente, porque a CIL incorreta pode levar a uma violação das regras de segurança do tipo. O processo de verificação passa por um conjunto bem definido de código tipo-seguro, e passa apenas o código que é tipo seguro. No entanto, alguns códigos seguros para tipos podem não passar na verificação devido a algumas limitações do processo de verificação, e algumas linguagens, por design, não produzem código seguro para tipos verificável. Se o código de tipo seguro for exigido pela diretiva de segurança, mas o código não passar na verificação, uma exceção será lançada quando o código for executado.

Executar código

O common language runtime fornece a infraestrutura que permite a execução gerenciada e serviços que podem ser usados durante a execução. Antes que um método possa ser executado, ele deve ser compilado para código específico do processador. Cada método para o qual a CIL foi gerada é compilado em JIT quando é chamado pela primeira vez e, em seguida, executado. Na próxima vez que o método for executado, o código nativo compilado por JIT existente será executado. O processo de compilação JIT e, em seguida, a execução do código é repetido até que a execução seja 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 a depuração entre idiomas e suporte aprimorado à implantação e controle de versão.

No Microsoft Windows Vista, o carregador do sistema operacional verifica se há módulos gerenciados examinando um pouco no cabeçalho COFF. O bit que está sendo definido indica um módulo gerenciado. Se o carregador detetar módulos gerenciados, ele carregará mscoree.dll e _CorValidateImage_CorImageUnloading notificará o carregador quando as imagens do módulo gerenciado forem carregadas e descarregadas. _CorValidateImage executa as seguintes ações:

  1. Garante que o código é válido código gerenciado.
  2. Altera o ponto de entrada na imagem para um ponto de entrada no tempo de execução.

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

Consulte também