Desenvolvimento - C#

O processo de compilação no C#

Os fontes em C# tem extensão “cs”. Todos os fontes em um “projeto” são compilados diretamente para um único “assembly” (.EXE ou .DLL). Não existe um arquivo intermediário (.OBJ ou .DCU) como no Delphi...

por Fabio Camara



Os fontes em C# tem extensão “cs”. Todos os fontes em um “projeto” são compilados diretamente para um único “assembly” (.EXE ou .DLL). Não existe um arquivo intermediário (.OBJ ou .DCU) como no Delphi.

Qualquer fonte pode referenciar outro fonte no projeto com a sintaxe “namespace.classe.membro”. Note que podemos incluir “namespaces” dentro de “namespaces”. Não é necessário declarar previamente os identificadores ou usar “using”. Mais uma vez, insisto em que você entenda que “using” em C# não é igual a “uses” no Delphi. É importante compreender isto para permitir um aprendizado coerente da linguagem C#.

Se o namespace vai gerar um .EXE, uma de suas classes deve ter um método public static int Main(string[] args) que será o ponto de entrada do executável.

Assemblies

Os executáveis “.NET” são arquivos .EXE ou .DLL no formato “PE”, o mesmo formato usado no Windows 32, mas com algumas diferenças. Eles são chamados coletivamente de “Assemblies” e contém basicamente o seguinte:

  • Informações de versão detalhadas tanto sobre o próprio módulo como os demais módulos referenciados (“Manifest”);

  • Informações detalhadas de todos os tipos implementados no arquivo (“Metadata”);

  • Código MSIL – Microsoft Intermediate Language. Este código é compilado em tempo de carga (ou instalação) para a CPU do computador;

Um executável ou DLL pode tanto chamar classes da biblioteca ou de outros executáveis como também criar classes derivadas de classes localizadas em outros executáveis. Este é um sonho antigo da orientação a objetos que nunca foi realmente realizado, apesar de várias tentativas (Taligent e Next, dentre outras).

A menor unidade de instalação / distribuição (deployment) em .Net é o assembly. Como os assemblies são autodescritores, o método mais fácil de instalá-los é copiar o assembly para a pasta de destino desejada. Então, ao tentar executar um aplicativo contido no assembly, o manifest instruirá o run-time .Net quanto aos módulos que estão contidos no assembly. Além disso, o assembly contém referências a quaisquer assemblies externos que são necessários para o aplicativo.

Ambiente Gerenciado

Todo código desenvolvido para .NET Framework é dito “gerenciado”. As principais características deste ambiente gerenciado são:

  • O executável não contém código Intel x86 e sim código MSIL com chamadas à biblioteca de classes;

  • O gerenciamento de memória é feito automaticamente com o uso de um coletor de lixo (garbage collector);

  • As atribuições têm o tipo validado; a princípio o sistema de tipos é inviolável;

  • Operações que podem comprometer a segurança, como abrir um arquivo, exigem permissões especiais.

As qualidades acima permitem rodar programas de origem duvidosa, por exemplo da Internet, sem que estes programas possam comprometer a segurança e integridade do sistema.

Verificabilidade

Todo programa criado pelo compilador C# é dito “verificável”. Isto quer dizer que o compilador JIT pode, em tempo de execução / compilação, verificar e garantir que o programa não faça nenhuma operação que possa comprometer a segurança e integridade do sistema.

Pode parecer estranho, mas existem instruções MSIL capazes de abrir brechas na segurança do sistema, como por exemplo, para manuseio direto de ponteiros ou “casts” inseguros. Estas instruções são necessárias em alguns casos, como por exemplo para que a própria biblioteca chame a API do Windows. Programas que contém estas instruções são ditos “não-verificáveis”.

O compilador C# pode criar programas não-verificáveis, incluindo manipulação direta de ponteiros, com a opção “/unsafe”. Já o compilador C++ sempre gera código não-verificável. Evidentemente é necessário um privilégio especial de segurança para rodar programas não-verificáveis.

É perfeitamente possível criar programas bastante úteis sem violar os critérios de “verificabilidade” e, conseqüentemente, segurança.

Examinando os Assemblies

Você pode observar o conteúdo de um executável .NET com a ferramenta “ILDASM.EXE”. Observe as informações de tipo exibidas. No caso temos uma classe chamada Module1 com um método Main dentro:

Veja o Manifest, com informações de versão tanto do próprio executável (embaixo, versão 0.0.0.0), como de outros assemblies (DLLs) referenciadas pelo programa.

Você pode visualizar o código IL clicando sobre o método Main:

Nota sobre descompilação

Qualquer programa, escrito para qualquer linguagem e voltado para qualquer CPU pode ser descompilado, dada uma quantidade de esforço maior ou menor. Por “descompilado” queremos dizer que podemos criar um fonte em linguagem de alto nível que uma vez compilado funciona da mesma forma que o programa executável que dispúnhamos anteriormente. Isto pode ser feito com executáveis Windows, mas os programas .NET (e também Java) podem ser descompilados mais facilmente.

Uma das grandes vantagens dos executáveis .NET é que eles podem rodar em um “ambiente gerenciado”, onde os programas não podem causar danos ao computador do usuário. Este ambiente gerenciado depende basicamente do seguinte para funcionar:

  • Código em “linguagem intermediária” (MSIL). Este código pode ser “verificado” em tempo de execução pelo compilador “JIT”, de forma que não possa causar danos ao computador do cliente ou efetuar operações não permitidas pelas configurações de segurança, como ler um arquivo qualquer do disco ou estabelecer comunicação via Internet com um site não autorizado.

  • Informação de tipo em tempo de execução (“metadata”) de forma a validar os “casts” e manter a integridade do sistema de tipos.

  • A primeira compilação (C# para IL) não deve otimizar o código, pois as otimizações podem impedir que o compilador JIT verifique o código como também atrapalhar outras otimizações a serem feitas pelo JIT.

Evidentemente, os fatores acima facilitam a descompilação dos programas. Note que estes recursos têm como efeito principal o aumento da segurança para os usuários finais e é também uma característica do Java.

Dito isto, como impedir que alguém que tenha os executáveis possa descompilá-los? Existem várias maneiras:

  1. Colocar parte da funcionalidade do aplicativo em WebServices ou mesmo servidores de aplicativos (COM+/remoting). Como os usuários não têm acesso aos executáveis, não há como descompilá-los. Esta maneira é absolutamente garantida.

  2. Escrever parte do programa como código não gerenciado, quer como DLL, objeto COM ou mesmo em código não-gerenciado dentro de programas C++ .NET.

  3. Escrever parte do código em Assembler IL usando recursos que dificultam a descompilação, como o uso de identificadores não suportados pelas linguagens C# ou VB.NET.

  4. Usar um “obfuscator” como o Demeanor (http://www.wiseowl.com) ou o DotFuscator (http://www.preemptive.com/) para alterar o código de forma a impedir a descompilação.
A grande visão do .NET é que teremos servidores de aplicativos acessados através da Web ou de aplicativos “SmartClients” (WinForms) e não os aplicativos “Client/Server” atuais. Na medida em que os aplicativos passem a depender de serviços para o seu funcionamento, a questão da descompilação do cliente deixa de ser muito relevante. Aliás, se formos comparar com a Web, os WinForms estão substituindo aplicativos escritos em HTML e JavaScript, que enviam o fonte para o cliente de qualquer forma, sem possibilidade de uso de “obfuscators”.

Executando Programas

Em tempo de execução (ou instalação, em alguns casos), o código do assembly é finalmente compilado, método a método, para o ambiente final com um “JIT- Just In Time Compiler”. Isto traz as seguintes vantagens:

  • Independência de CPUs e sistema operacional: basta que exista um runtime do “.NET” para o programa rodar.

  • O sistema de runtime pode fazer verificações de segurança impossíveis caso o código fosse realmente executável. Os programas podem rodar em uma espécie de “caixa de areia” e não danificar o sistema hospedeiro. O código executável é chamado de “managed code”.

  • A compilação final traz performance melhor que a obtida em ambientes interpretados.

Pré-Compilação

É possível - embora não recomendável - compilar previamente um arquivo executável para código nativo com o comando NGEN:

O executável será colocado em um local chamado “Global Assembly Cache”:

Ao chamarmos o programa do diretório original, a versão do GAC será usada.

Observe alguns pontos importantes:

  • A compilação somente poderá ser feita no computador no qual o programa será executado. Não existe a possibilidade de executar esta compilação no computador do desenvolvedor e depois “instalar a imagem executável” no computador do cliente;

  • O executável continua exigindo o .NET Framework presente no cliente;

  • Nem tudo pode ser compilado; alguns trechos podem ser deixados em IL para serem compilados em tempo de execução;

  • A imagem nativa nunca é gerenciada automaticamente; este é sempre um processo manual feito com o utilitário NGEN.EXE (veja as opções /delete e /show);

  • Normalmente só podemos colocar programas com “strong names” no GAC, mas o runtime cria um nome “mais ou menos forte” baseado em um “checksum” e data para colocá-lo no GAC.

  • Quando um assembly é compilado com o NGEN.EXE, diversas informações são registradas:

  • Tipo da CPU;

  • Versão do sistema operacional;

  • Identificação do assembly, uma espécie de “checksum + data e hora”. Uma recompilação muda esta identidade;

  • Exata identificação de todos os assemblies referenciados;

  • Fatores de segurança.

Se qualquer uma destas informações mudarem, a imagem nativa deixará de ser usada e o programa será compilado com “JIT”. A imagem nativa nunca é atualizada automaticamente.Vantagens e desvantagens

A única vantagem de compilar previamente um executável é que sua carga e execução serão um pouco mais rápidas. Por outro lado, a chance da imagem nativa ser ignorada é relativamente grande, especialmente por mudanças no ambiente de segurança e privilégios dos usuários. É interessante observar que a instalação do .NET Framework pré-compila alguns, mas não todos assemblies, como pode ser observado em “C:\WINNT\assembly”:

A minha recomendação é que você NÃO deve se preocupar em pré-compilar os executáveis, a princípio. Nos casos que a execução mostrar-se lenta, você pode experimentar a pré-compilação.

Opções de Compilação

Em project / Properties existem diversas opções de compilação para os projetos. Conforme a imagem a seguir, você pode clicar do lado direito e observar as diversas opções que você pode configurar.

Outra forma de acessar as propriedades do projeto é através do toolbox “Solution Explorer”, clique em cima do seu projeto com o botão direito do mouse e selecione properties.

Verificações em tempo de compilação

Durante a compilação são feitas diversas verificações no fonte, não só sintáticas como também semânticas. O compilador detecta erros de:

  • Uso de variáveis não atribuídas;

  • Parâmetro out não atribuído na função de chamada;

  • Parâmetro ref não atribuído na função de chamada;

  • Existência de código inatingível;

  • Possibilidade de uma função não receber o valor de retorno;

  • Switch/Case sem break nas opções;

Comentários

Os comentários são textos livres colocados dentro do programa, usados para explicar o que o código faz.

Comentários XML (Exclusivo C#)

O C# permite que se escreva comentário de uma maneira estruturada, em XML. Enquanto os comentários em C# são iniciados com “//”,os comentários iniciados com “///” indicam documentação que pode ser extraída pelo compilador e manipulada. Esta manipulação inclui:

  • Usar o arquivo XML diretamente em um programa, por exemplo inserindo em uma base de dados

  • Aplicar uma “style sheet” para exibir o texto formatado;

O próprio Visual Studio pode também criar páginas HTML a partir dos comentários em XML.

Incluindo comentários

Os comentários em XML são colocados antes de elementos como:

  • Tipos, incluindo classes;

  • Variáveis;

  • Métodos;

  • Propriedades;

  • Delegates.

  • Note o seguinte:

  • A documentação começa com ///;

  • O Visual Studio .NET cria automaticamente um esqueleto apropriado ao se digitar “///” antes do elemento;

  • O documento deve ser “bem formatado”; isto basicamente significa que as tags devem estar equilibradas, com início e fim;

  • A tag <param> descreve parâmetros;

  • A tag <summary> é usada pelo editor do Visual Studio para fornecer informação de tipo ou de um membro;

  • As tags com significado especial são: <c>, <param>, <see>, <code>, <paramref>, <seealso>, <example>, <permission>, <summary>, <exception>, <remarks>, <value>, <list>, <returns>.

Veja um exemplo de classe com comentários em XML:
namespace BizObj {
       /// <summary>
       /// Do simple arithimetic
       /// </summary>
       public class Contas {
             double _N1;
             double _N2;
             /// <summary>
             /// Default constructor
             /// </summary>
             public Contas() {
                    _N1 = 0;
                    _N2 = 0;
             }
             /// <summary>
             /// Assign two values
             /// </summary>
             /// <param name="x">One value</param>
             /// <param name="y">Another value</param>
             public void Assign(double x, double y) {
                    _N1 = x;
                    _N2 = y;
             }
             /// <summary>
             /// Add the numbers
             /// </summary>
             /// <returns></returns>
             public double Add() {
                    return _N1 + _N2;
             }
             /// <summary>
             /// Multiply the numbers
             /// </summary>
             /// <returns></returns>
             public double Multiply() {
                    return _N1 * _N2;
             }
             /// <summary>
             /// Get the first number
             /// </summary>
             public double N1 {
                    get {
                           return _N1;
                    }
             }
             /// <summary>
             /// Get the second number
             /// </summary>
             public double N2 {
                    get {
                           return _N2;
                    }
             }
       }
}

Extraindo a documentação em arquivo à parte

Para extrair a documentação em um arquivo à parte, rode o compilador de linha de comando em uma sessão em modo console, por exemplo:

C>csc Class1.cs /doc:Class1.xml

A opção “/doc” indica a extração do arquivo XML. Veja o arquivo XML gerado a partir do programa acima:

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Class1</name>
    </assembly>
    <members>
        <member name="T:BizObj.Contas">
            <summary>
            Do simple arithimetic
            </summary>
        </member>
        <member name="M:BizObj.Contas.#ctor">
            <summary>
            A class that is going to be wrapped as COM must have a public constructor without parameters
            </summary>
        </member>
        <member name="M:BizObj.Contas.Assign(System.Single,System.Single)">
            <summary>
            Assign two values
            </summary>
            <param name="x">One value</param>
            <param name="y">Another value</param>
        </member>
        <member name="M:BizObj.Contas.Add">
            <summary>
            Add the numbers
            </summary>
            <returns></returns>
        </member>
        <member name="M:BizObj.Contas.Multiply">
            <summary>
            Multiply the numbers
            </summary>
            <returns></returns>
        </member>
        <member name="P:BizObj.Contas.N1">
            <summary>
            Get the first number
            </summary>
        </member>
        <member name="P:BizObj.Contas.N2">
            <summary>
            Get the second number
            </summary>
        </member>
    </members>
</doc>
Aplicando uma Style Sheet XSL

Para associar um “schema XML” (arquivo XSL), colocando um código parecido com o seguinte na segunda linha do arquivo XML gerado, como por exemplo:

<?xml-stylesheet type="text/xsl" href="Doc.xsl"?>

Gerando páginas HTML no Visual Studio .NET

Você pode também criar a documentação em HTML através do menu “Tools | Build Comment Web Pages” do Visual Studio .NET. Veja uma página criada para o exemplo anterior, visto de dentro do Visual Studio .NET:

Fabio Camara

Fabio Camara - MVP VSTS, MCT, MCP, MCSD, MCTS, MCPITP, MCPD, MSF Practitioner, Certified SCRUM Master, Certified ITIL Foundations. Escreveu mais de 15 livros nesta última década. Atua como consultor de produtividade em desenvolvimento de projetos e professor de disciplinas ágeis de engenharia de software. Pode ser localizado no site http://www.fcamara.com.br.