Compartilhar via


Simplificando a implantação e resolvendo o inferno da DLL com o .NET Framework

 

Steven Pratschner
Microsoft Corporation

Atualizado em novembro de 2001

Resumo: Este artigo apresenta o conceito de um assembly e descreve como o .NET Framework usa assemblies para resolver problemas de controle de versão e implantação. (16 páginas impressas)

Sumário

Introdução
Demonstração do problema
Características da solução
Assemblies: os blocos de construção
Controle de versão e compartilhamento
Política de Versão
Implantação
Resumo

Introdução

O Microsoft® .NET Framework apresenta vários novos recursos destinados a simplificar a implantação de aplicativos e resolver a DLL Hell. Os usuários finais e os desenvolvedores estão familiarizados com os problemas de controle de versão e implantação que podem surgir com os sistemas baseados em componentes atuais. Por exemplo, praticamente todos os usuários finais instalaram um novo aplicativo em seu computador, apenas para descobrir que um aplicativo existente para de funcionar misteriosamente. A maioria dos desenvolvedores também passou um tempo com Regedit, tentando manter todas as entradas do Registro necessárias consistentes para ativar uma classe COM.

As diretrizes de design e as técnicas de implementação usadas no .NET Framework para resolver o Inferno da DLL são criadas no trabalho realizado no Microsoft Windows® 2000, conforme descrito por Rick Anderson em O Fim do Inferno da DLL, e por David D'Souza, BJ Whalen e Peter Wilson em Implementando o compartilhamento lado a lado de componentes em aplicativos (expandido). O .NET Framework estende esse trabalho anterior fornecendo recursos, incluindo isolamento do aplicativo e componentes lado a lado para aplicativos criados com código gerenciado na plataforma .NET. Além disso, observe que o Windows XP fornece os mesmos recursos de isolamento e controle de versão para código não gerenciado, incluindo classes COM e DLLs do Win32 (consulte How To Build and Service Isolated Applications and Side-by-Side Assemblies for Windows XP for details).

Este artigo apresenta o conceito de um assembly e descreve como o .NET usa assemblies para resolver problemas de controle de versão e implantação. Em particular, discutiremos como os assemblies são estruturados, como eles são nomeados e como os compiladores e o CLR (Common Language Runtime) usam assemblies para registrar e impor dependências de versão entre partes de um aplicativo. Também discutiremos como aplicativos e administradores podem personalizar o comportamento de controle de versão por meio do que chamamos de políticas de versão.

Depois que os assemblies forem introduzidos e descritos, vários cenários de implantação serão apresentados, fornecendo uma amostragem das várias opções de empacotamento e distribuição disponíveis no .NET Framework.

Demonstração do problema

Controle de versão

Do ponto de vista do cliente, o problema de controle de versão mais comum é o que chamamos de Inferno da DLL. Simplesmente declarado, DLL Hell refere-se ao conjunto de problemas causados quando vários aplicativos tentam compartilhar um componente comum, como uma DLL (biblioteca de vínculo dinâmico) ou uma classe COM (Component Object Model). No caso mais típico, um aplicativo instalará uma nova versão do componente compartilhado que não é compatível com versões anteriores já no computador. Embora o aplicativo que acabou de ser instalado funcione bem, os aplicativos existentes que dependiam de uma versão anterior do componente compartilhado podem não funcionar mais. Em alguns casos, a causa do problema é ainda mais sutil. Por exemplo, considere o cenário em que um usuário baixa um controle Microsoft ActiveX® como um efeito colateral da visita a algum site da Web. Quando o controle for baixado, ele substituirá todas as versões existentes do controle que estavam presentes no computador. Se um aplicativo que foi instalado no computador usar esse controle, ele também poderá parar de funcionar.

Em muitos casos, há um atraso significativo antes que um usuário descubra que um aplicativo parou de funcionar. Como resultado, muitas vezes é difícil lembrar quando uma alteração foi feita no computador que poderia ter afetado o aplicativo. Um usuário pode se lembrar de ter instalado algo há uma semana, mas não há correlação óbvia entre essa instalação e o comportamento que ele está vendo agora. Para piorar as coisas, há poucas ferramentas de diagnóstico disponíveis hoje para ajudar o usuário (ou a pessoa de suporte que está ajudando) a determinar o que está errado.

O motivo para esses problemas é que as informações de versão sobre os diferentes componentes de um aplicativo não são registradas ou impostas pelo sistema. Além disso, as alterações feitas no sistema em nome de um aplicativo normalmente afetarão todos os aplicativos no computador— a criação de um aplicativo hoje completamente isolado das alterações não é fácil.

Um motivo pelo qual é difícil criar um aplicativo isolado é que o ambiente de tempo de execução atual normalmente permite a instalação de apenas uma única versão de um componente ou de um aplicativo. Essa restrição significa que os autores de componentes devem escrever seu código de uma maneira que permaneça compatível com versões anteriores, caso contrário, correm o risco de quebrar aplicativos existentes quando instalam um novo componente. Na prática, escrever código que seja compatível para sempre com versões anteriores é extremamente difícil, se não impossível. No .NET, a noção de lado a lado é fundamental para a história de controle de versão. Lado a lado é a capacidade de instalar e executar várias versões do mesmo componente no computador ao mesmo tempo. Com componentes que dão suporte lado a lado, os autores não estão necessariamente vinculados à manutenção da compatibilidade estrita com versões anteriores porque diferentes aplicativos são livres para usar diferentes versões de um componente compartilhado.

Implantação e instalação

A instalação de um aplicativo hoje é um processo de várias etapas. Normalmente, a instalação de um aplicativo envolve a cópia de vários componentes de software para o disco e a criação de uma série de entradas do Registro que descrevem esses componentes para o sistema.

A separação entre as entradas no Registro e os arquivos no disco torna muito difícil replicar aplicativos e desinstalá-los. Além disso, a relação entre as várias entradas necessárias para descrever totalmente uma classe COM no registro é muito flexível. Essas entradas geralmente incluem entradas para coclasses, interfaces, typelibs e IDs de aplicativo DCOM, para não menção entradas feitas para registrar extensões de documento ou categorias de componente. Muitas vezes, você acaba mantendo-os em sincronia manualmente.

Por fim, esse volume do Registro é necessário para ativar qualquer classe COM. Isso complica drasticamente o processo de implantação de aplicativos distribuídos porque cada computador cliente deve ser tocado para fazer as entradas apropriadas do Registro.

Esses problemas são causados principalmente pela descrição de um componente que está sendo mantido separado do próprio componente. Em outras palavras, os aplicativos não são auto-descritos nem autossuficientes.

Características da solução

O .NET Framework deve fornecer os seguintes recursos básicos para resolver os problemas descritos:

  • Os aplicativos devem ser autodescrevendo. Os aplicativos que são auto-descritos removem a dependência do Registro, permitindo a instalação de impacto zero e simplificando a desinstalação e a replicação.
  • As informações de versão devem ser registradas e impostas. O suporte ao controle de versão deve ser integrado à plataforma para garantir que a versão adequada de uma dependência seja carregada em tempo de execução.
  • Deve se lembrar do "último bem conhecido". Quando um aplicativo é executado com êxito, a plataforma deve se lembrar do conjunto de componentes, incluindo suas versões, que funcionaram juntos. Além disso, devem ser fornecidas ferramentas que permitam que os administradores reverter facilmente aplicativos a esse estado de "último bem conhecido".
  • Suporte para componentes lado a lado. Permitir que várias versões de um componente sejam instaladas e executadas no computador simultaneamente permite que os chamadores especifiquem qual versão gostariam de carregar em vez de uma versão "forçada" sem saber. O .NET Framework dá um passo a lado, permitindo que várias versões da própria estrutura coexistam em um único computador. Isso simplifica drasticamente a história de atualização, pois um administrador pode optar por executar diferentes aplicativos em diferentes versões do .NET Framework, se necessário.
  • Isolamento do aplicativo. O .NET Framework deve facilitar e, de fato, o padrão, gravar aplicativos que não podem ser afetados por alterações feitas no computador em nome de outros aplicativos.

Assemblies: os blocos de construção

Assemblies são os blocos de construção usados pelo .NET Framework para resolver os problemas de controle de versão e implantação descritos. Assemblies são a unidade de implantação para tipos e recursos. Em muitos aspectos, um assembly equivale a uma DLL no mundo atual; em essência, os assemblies são "DLLs lógicas".

Os assemblies são autodescrevendo por meio de metadados chamados de manifesto. Assim como o .NET usa metadados para descrever tipos, ele também usa metadados para descrever os assemblies que contêm os tipos.

Assemblies são muito mais do que a implantação. Por exemplo, o controle de versão no .NET é feito no nível do assembly— nada menor, como um módulo ou um tipo, é versão. Além disso, assemblies são usados para compartilhar código entre aplicativos. O assembly no qual um tipo está contido faz parte da identidade do tipo.

O sistema de segurança de acesso a código usa assemblies no núcleo de seu modelo de permissões. O autor de um assembly registra no manifesto o conjunto de permissões necessárias para executar o código e o administrador concede permissões para codificar com base no assembly no qual o código está contido.

Por fim, os assemblies também são fundamentais para o sistema de tipos e o sistema de tempo de execução, pois estabelecem um limite de visibilidade para tipos e servem como um escopo de tempo de execução para resolver referências a tipos.

Manifestos do assembly

Especificamente, um manifesto inclui os seguintes dados sobre o assembly:

  • Identidade. A identidade de um assembly consiste em quatro partes: um nome de texto simples, um número de versão, uma cultura opcional e uma chave pública opcional se o assembly foi criado para compartilhamento (consulte a seção em Assemblies Compartilhados abaixo).
  • Lista de arquivos. Um manifesto inclui uma lista de todos os arquivos que compõem o assembly. Para cada arquivo, o manifesto registra seu nome e um hash criptográfico de seu conteúdo no momento em que o manifesto foi criado. Esse hash é verificado em tempo de execução para garantir que a unidade de implantação seja consistente.
  • Assemblies referenciados. As dependências entre assemblies são armazenadas no manifesto do assembly de chamada. As informações de dependência incluem um número de versão, que é usado em tempo de execução para garantir que a versão correta da dependência seja carregada.
  • Tipos e recursos exportados. As opções de visibilidade disponíveis para tipos e recursos incluem "visível somente dentro do meu assembly" e "visível para chamadores fora do meu assembly".
  • Solicitações de permissão. As solicitações de permissão para um assembly são agrupadas em três conjuntos: 1) aqueles necessários para que o assembly seja executado, 2) aqueles desejados, mas o assembly ainda terá alguma funcionalidade, mesmo que não sejam concedidas e 3) aquelas que o autor nunca deseja que o assembly seja concedido.

A ferramenta il disassembler (Ildasm) SDK é útil para examinar o código e os metadados em um assembly. A Figura 1 é um manifesto de exemplo, conforme exibido por Ildasm. A diretiva .assembly identifica o assembly e as diretivas extern .assembly contêm as informações sobre outros assemblies dos quais este depende.

Figura 1. Manifesto de exemplo, conforme exibido pelo Il Disassembler

Estrutura do assembly

Até agora, os assemblies foram descritos principalmente como um conceito lógico. Esta seção ajuda a tornar os assemblies mais concretos descrevendo como eles são representados fisicamente.

Em geral, os assemblies consistem em quatro elementos: os metadados do assembly (manifesto), os metadados que descrevem os tipos, o código il (linguagem intermediária) que implementa os tipos e um conjunto de recursos. Nem todos eles estão presentes em cada assembly. Somente o manifesto é estritamente necessário, mas tipos ou recursos são necessários para fornecer ao assembly qualquer funcionalidade significativa.

Há várias opções para como esses quatro elementos podem ser "empacotados". Por exemplo, a Figura 2 mostra uma única DLL que contém o assembly inteiro: o manifesto, os metadados de tipo, o código IL e os recursos.

Figura 2. DLL que contém todos os elementos do assembly

Como alternativa, o conteúdo de um assembly pode ser distribuído em vários arquivos. Na Figura 3, o autor optou por separar algum código de utilitário em uma DLL diferente e manter um arquivo de recurso grande (neste caso, um JPEG) em seu arquivo original. Uma das razões pelas quais isso pode ser feito é otimizar o download de código. O .NET Framework baixará um arquivo somente quando for referenciado, portanto, se o assembly contiver código ou recursos acessados com pouca frequência, dividi-los em arquivos individuais aumentará a eficiência do download. Outro cenário comum no qual vários arquivos são usados é criar um assembly que consiste em código de mais de uma linguagem. Nesse caso, você compilaria cada arquivo (módulo) separadamente e os agruparia em um assembly usando a ferramenta Assembly Linker fornecida no SDK do .NET Framework (al.exe).

Figura 3. Elementos de assembly espalhados por vários arquivos

Controle de versão e compartilhamento

Uma das principais causas da DLL Hell é o modelo de compartilhamento atualmente usado em sistemas baseados em componentes. Por padrão, os componentes de software individuais são compartilhados por vários aplicativos no computador. Por exemplo, sempre que um programa de instalação copia uma DLL para o diretório do sistema ou registra uma classe no registro COM, esse código potencialmente terá um efeito em outros aplicativos em execução no computador. Em particular, se um aplicativo existente usou uma versão anterior desse componente compartilhado, esse aplicativo começará automaticamente a usar a nova versão. Se o componente compartilhado for estritamente compatível com versões anteriores, isso pode ser bom, mas, em muitos casos, manter a compatibilidade com versões anteriores é difícil, se não impossível. Se a compatibilidade com versões anteriores não for mantida ou não puder ser mantida, isso geralmente resultará em aplicativos que são interrompidos como um efeito colateral de outros aplicativos que estão sendo instalados.

Uma diretriz de design principal no .NET é a de componentes isolados (ou assemblies). Isolar um assembly significa que um assembly só pode ser acessado por um aplicativo— ele não é compartilhado por vários aplicativos no computador e não pode ser afetado por alterações feitas no sistema por outros aplicativos. O isolamento dá a um desenvolvedor controle absoluto sobre o código que é usado por seu aplicativo. Assemblies isolados ou privados do aplicativo são o padrão em aplicativos .NET. A tendência para componentes isolados começou no Microsoft Windows 2000 com a introdução do arquivo .local. Esse arquivo foi usado para fazer com que o Carregador do SISTEMA Operacional e o COM procurassem o diretório do aplicativo primeiro ao tentar localizar o componente solicitado. (Consulte o artigo relacionado no Biblioteca MSDN, Implementando o compartilhamento de componentes lado a lado em aplicativos.)

No entanto, há casos em que o compartilhamento de um assembly entre aplicativos é necessário. Claramente, não faria sentido que cada aplicativo carregasse sua própria cópia de System.Windowns.Forms, System.Web ou um controle de Web Forms comum.

No .NET, compartilhar código entre aplicativos é uma decisão explícita. Assemblies compartilhados têm alguns requisitos adicionais. Especificamente, os assemblies compartilhados devem dar suporte lado a lado para que várias versões do mesmo assembly possam ser instaladas e executadas no mesmo computador, ou mesmo dentro do mesmo processo, ao mesmo tempo. Além disso, os assemblies compartilhados têm requisitos de nomenclatura mais rigorosos. Por exemplo, um assembly compartilhado deve ter um nome globalmente exclusivo.

A necessidade de isolamento e compartilhamento nos leva a pensar em dois "tipos" de assemblies. Essa é uma categorização bastante flexível, pois não há diferenças estruturais reais entre os dois, mas sim a diferença é em como eles serão usados: seja privado para um aplicativo ou compartilhado entre muitos.

assemblies Application-Private

Um assembly privado do aplicativo é um assembly que só é visível para um aplicativo. Esperamos que esse seja o caso mais comum no .NET. Os requisitos de nomenclatura para assemblies privados são simples: os nomes de assembly só devem ser exclusivos dentro do aplicativo. Não há necessidade de um nome globalmente exclusivo. Manter os nomes exclusivos não é um problema porque o desenvolvedor do aplicativo tem controle total sobre quais assemblies são isolados para o aplicativo.

Assemblies privados de aplicativo são implantados dentro da estrutura de diretório do aplicativo no qual são usados. Assemblies privados podem ser colocados diretamente no diretório do aplicativo ou em um subdiretório dele. O CLR localiza esses assemblies por meio de um processo chamado investigação. A investigação é simplesmente um mapeamento do nome do assembly para o nome do arquivo que contém o manifesto.

Especificamente, o CLR usa o nome do assembly registrado na referência do assembly, acrescenta ".dll" e procura esse arquivo no diretório do aplicativo. Há algumas variantes nesse esquema em que o Runtime procurará subdiretórios nomeados pelo assembly ou em subdiretórios nomeados pela cultura do assembly. Por exemplo, um desenvolvedor pode optar por implantar o assembly que contém recursos localizados em alemão em um subdiretório chamado "de" e em espanhol em um diretório chamado "es". (Consulte o Guia do SDK do .NET Framework para obter detalhes.)

Conforme descrito, cada manifesto do assembly inclui informações de versão sobre suas dependências. Essas informações de versão não são impostas para assemblies privados porque o desenvolvedor tem controle total sobre os assemblies implantados no diretório do aplicativo.

Assemblies compartilhados

O .NET Framework também dá suporte ao conceito de um assembly compartilhado. Um assembly compartilhado é usado por vários aplicativos no computador. Com o .NET, compartilhar código entre aplicativos é uma decisão explícita. Os assemblies compartilhados têm alguns requisitos adicionais destinados a evitar os problemas de compartilhamento que experimentamos hoje. Além do suporte para descrever lado a lado, os assemblies compartilhados têm requisitos de nomenclatura muito mais rigorosos. Por exemplo, um assembly compartilhado deve ter um nome globalmente exclusivo. Além disso, o sistema deve fornecer "proteção do nome", ou seja, impedindo que alguém reutilize o nome do assembly de outro. Por exemplo, digamos que você seja um fornecedor de um controle de grade e tenha lançado a versão 1 do assembly. Como autor, você precisa de garantia de que ninguém mais pode liberar um assembly alegando ser a versão 2 ou o controle de grade. O .NET Framework dá suporte a esses requisitos de nomenclatura por meio de uma técnica chamada nomes fortes (descrita em detalhes na próxima seção).

Normalmente, um autor de aplicativo não tem o mesmo grau de controle sobre os assemblies compartilhados usados pelo aplicativo. Como resultado, as informações de versão são verificadas em cada referência a um assembly compartilhado. Além disso, o .NET Framework permite que aplicativos e administradores substituam a versão de um assembly usado pelo aplicativo especificando políticas de versão.

Assemblies compartilhados não são necessariamente implantados de forma privada em um aplicativo, embora essa abordagem ainda seja viável, especialmente se a implantação de xcopy for um requisito. Além de um diretório de aplicativo privado, um assembly compartilhado também pode ser implantado no Cache de Assembly Global ou em qualquer URL, desde que uma base de código que descreva o local do assembly seja fornecida no arquivo de configuração do aplicativo. O cache de assembly global é um repositório em todo o computador para assemblies que são usados por mais de um aplicativo. Conforme descrito, a implantação no cache não é um requisito, mas há algumas vantagens em fazer isso. Por exemplo, o armazenamento lado a lado de várias versões de um assembly é fornecido automaticamente. Além disso, os administradores podem usar o repositório para implantar correções de bugs ou patches de segurança que desejam que todos os aplicativos no computador usem. Por fim, há algumas melhorias de desempenho associadas à implantação no cache de assembly global. O primeiro envolve a verificação de assinaturas de nome forte, conforme descrito na seção Nome Forte abaixo. A segunda melhoria de desempenho envolve o conjunto de trabalho. Se vários aplicativos estiverem usando o mesmo assembly simultaneamente, carregar esse assembly do mesmo local no disco aproveitará o comportamento de compartilhamento de código fornecido pelo sistema operacional. Por outro lado, carregar o mesmo assembly de vários locais diferentes (diretórios de aplicativos) resultará no carregamento de muitas cópias do mesmo código. A adição de um assembly ao cache no computador de um usuário final normalmente é realizada usando um programa de instalação baseado no Windows Installer ou em alguma outra tecnologia de instalação. Os assemblies nunca acabam no cache como um efeito colateral da execução de algum aplicativo ou da navegação até uma página da Web. Em vez disso, instalar um assembly no cache requer uma ação explícita por parte do usuário. O Windows Installer 2.0, que é fornecido com o Windows XP e o Visual Studio .NET, foi aprimorado para entender completamente o conceito de assemblies, o cache de assembly e aplicativos isolados. Isso significa que você poderá usar todos os recursos do Windows Installer, como instalação sob demanda e reparo de aplicativos, com seus aplicativos .NET.

Geralmente, não é prático criar um pacote de instalação sempre que você deseja adicionar um assembly ao cache em computadores de desenvolvimento e teste. Como resultado, o SDK do .NET inclui algumas ferramentas para trabalhar com o cache de assembly. A primeira é uma ferramenta chamada gacutil que permite adicionar assemblies ao cache e removê-los posteriormente. Use a opção /i para adicionar um assembly ao cache:

gacutil /i:myassembly.dll 
See the .NET Framework SDK documentation for a full description of the 
      options supported by gacutil.

As outras ferramentas são uma Extensão do Windows Shell que permite manipular o cache usando a Explorer do Windows e a Ferramenta de Configuração do .NET Framework. A Extensão do Shell pode ser acessada navegando até o subdiretório "assembly" no diretório do Windows. A Ferramenta de Configuração do .NET Framework pode ser encontrada na seção Ferramentas Administrativas do Painel de Controle.

A Figura 4 mostra uma exibição do cache de assembly global usando a Extensão do Shell.

Figura 4. Cache de assembly global

Nomes fortes

Nomes fortes são usados para habilitar os requisitos de nomenclatura mais rigorosos associados a assemblies compartilhados. Nomes fortes têm três gols:

  • Exclusividade do nome. Os assemblies compartilhados devem ter nomes globalmente exclusivos.
  • Impedir falsificação de nome. Os desenvolvedores não querem que outra pessoa libere uma versão subsequente de um de seus assemblies e afirmam falsamente que ela veio de você, seja por acidente ou intencionalmente.
  • Forneça a identidade na referência. Ao resolver uma referência a um assembly, nomes fortes são usados para garantir que o assembly carregado veio do editor esperado.

Nomes fortes são implementados usando a criptografia de chave pública padrão. Em geral, o processo funciona da seguinte maneira: o autor de um assembly gera um par de chaves (ou usa um existente), assina o arquivo que contém o manifesto com a chave privada e disponibiliza a chave pública para os chamadores. Quando são feitas referências ao assembly, o chamador registra a chave pública correspondente à chave privada usada para gerar o nome forte. A Figura 5 descreve como esse processo funciona no momento do desenvolvimento, incluindo como as chaves são armazenadas nos metadados e como a assinatura é gerada.

O cenário é um assembly chamado "Main", que faz referência a um assembly chamado "MyLib". MyLib tem um nome compartilhado. As etapas importantes são descritas da seguinte maneira.

Figura 5. Processo para implementar nomes compartilhados

  1. O desenvolvedor invoca um compilador passando um par de chaves e o conjunto de arquivos de origem para o assembly. O par de chaves é gerado com uma ferramenta do SDK chamada SN. Por exemplo, o comando a seguir gera um novo par de chaves e o salva em um arquivo:

    Sn –k MyKey.snk
    The key pair is passed to the compiler using the custom attribute 
            System.Reflection.AssemblyKeyFileAttribute as follows:
    
       <assembly:AssemblyKeyFileAttribute("TestKey.snk")>
    
  2. Quando o compilador emite o assembly, a chave pública é registrada no manifesto como parte da identidade do assembly. Incluir a chave pública como parte da identidade é o que dá ao assembly um nome globalmente exclusivo.

  3. Depois que o assembly for emitido, o arquivo que contém o manifesto será assinado com a chave privada. A assinatura resultante é armazenada no arquivo .

  4. Quando Main é gerado pelo compilador, a chave pública do MyLib é armazenada no manifesto do Main como parte da referência a MyLib.

Em tempo de execução, há duas etapas que o .NET Framework executa para garantir que nomes fortes estejam dando ao desenvolvedor os benefícios necessários. Primeiro, a assinatura de nome forte do MyLib é verificada somente quando o assembly é instalado no cache de assembly global— a assinatura não é verificada novamente quando o arquivo é carregado por um aplicativo. Se o assembly compartilhado não for implantado no cache de assembly global, a assinatura será verificada sempre que o arquivo for carregado. Verificar a assinatura garante que o conteúdo do MyLib não tenha sido alterado desde que o assembly foi criado. A segunda etapa é verificar se a chave pública armazenada como parte da referência de Main a MyLib corresponde à chave pública que faz parte da identidade do MyLib. Se essas chaves forem idênticas, o autor de Main poderá ter certeza de que a versão do MyLib que foi carregada veio do mesmo editor que criou a versão do MyLib com a qual Main foi criado. Essa equivalência chave marcar é feita em tempo de execução, quando a referência de Main para MyLib é resolvida.

O termo "assinatura" geralmente traz o Microsoft Authenticode® à mente. É importante entender que nomes fortes e Authenticode não estão relacionados de forma alguma. As duas técnicas têm objetivos diferentes. Em particular, Authenticode implica um nível de confiança associado a um editor, enquanto nomes fortes não. Não há certificados ou autoridades de assinatura de terceiros associadas a nomes fortes. Além disso, a assinatura de nome forte geralmente é feita pelo próprio compilador como parte do processo de build.

Outra consideração que vale a pena observar é o processo de "assinatura com atraso". Geralmente, o autor de um assembly não tem acesso à chave privada necessária para fazer a assinatura completa. A maioria das empresas mantém essas chaves em lojas bem protegidas que só podem ser acessadas por algumas pessoas. Como resultado, o .NET Framework fornece uma técnica chamada "assinatura de atraso" que permite que um desenvolvedor crie um assembly com apenas a chave pública. Nesse modo, o arquivo não está realmente assinado porque a chave privada não é fornecida. Em vez disso, o arquivo é assinado posteriormente usando o utilitário SN. Consulte Atrasar a assinatura de um assembly no SDK do .NET Framework para obter detalhes sobre como usar a assinatura com atraso.

Política de Versão

Conforme descrito recentemente, cada manifesto do assembly registra informações sobre a versão de cada dependência em que foi criado. No entanto, há alguns cenários em que o autor ou administrador do aplicativo pode querer executar com uma versão diferente de uma dependência em tempo de execução. Por exemplo, os administradores devem ser capazes de implantar versões de correção de bug sem exigir que todos os aplicativos sejam recompilados para obter a correção. Além disso, os administradores devem ser capazes de especificar que uma versão específica de um assembly nunca será usada se uma falha de segurança ou outro bug grave for encontrado. O .NET Framework permite essa flexibilidade na associação de versão por meio de políticas de versão.

Números de versão do assembly

Cada assembly tem um número de versão de quatro partes como parte de sua identidade (ou seja, a versão 1.0.0.0 de algum assembly e a versão 2.1.0.2 são identidades completamente diferentes no que diz respeito ao carregador de classe). Incluir a versão como parte da identidade é essencial para distinguir diferentes versões de um assembly para fins lado a lado.

As partes do número de versão são principais, secundárias, build e revisão. Não há semântica aplicada às partes do número de versão. Ou seja, o CLR não infere compatibilidade ou qualquer outra característica de um assembly com base em como o número de versão é atribuído. Como desenvolvedor, você pode alterar qualquer parte desse número como achar melhor. Embora não haja semântica aplicada ao formato do número de versão, as organizações individuais provavelmente acharão útil estabelecer convenções sobre como o número de versão é alterado. Isso ajuda a manter a consistência em toda a organização e facilita a determinação de itens como o qual o build de um assembly específico veio. Uma convenção típica é a seguinte:

Principal ou menor. As alterações na parte principal ou secundária do número de versão indicam uma alteração incompatível. Nessa convenção, então, a versão 2.0.0.0 seria considerada incompatível com a versão 1.0.0.0. Exemplos de uma alteração incompatível seriam uma alteração nos tipos de alguns parâmetros de método ou na remoção de um tipo ou método completamente.

Desenvolver. O número de build normalmente é usado para distinguir entre builds diários ou versões compatíveis menores.

Revisão. As alterações no número de revisão normalmente são reservadas para um build incremental necessário para corrigir um bug específico. Às vezes, você ouvirá isso chamado de número de "correção de bug de emergência", pois a revisão é o que geralmente é alterado quando uma correção para um bug específico é enviada a um cliente.

Política de Versão Padrão

Ao resolver referências a assemblies compartilhados, o CLR determina qual versão da dependência carregar quando se depara com uma referência a esse assembly no código. A política de versão padrão no .NET é extremamente simples. Ao resolver uma referência, o CLR usa a versão do manifesto do assembly de chamada e carrega a versão da dependência com exatamente o mesmo número de versão. Dessa forma, o chamador obtém a versão exata contra a qual ele foi criado e testado. Essa política padrão tem a propriedade de que protege os aplicativos do cenário em que um aplicativo diferente instala uma nova versão de um assembly compartilhado da qual um aplicativo existente depende. Lembre-se de que, antes do .NET, os aplicativos existentes começariam a usar o novo componente compartilhado por padrão. No entanto, no .NET, a instalação de uma nova versão de um assembly compartilhado não afeta os aplicativos existentes.

Política de Versão Personalizada

Pode haver momentos em que a associação à versão exata com a qual o aplicativo foi enviado não é o que você deseja. Por exemplo, um administrador pode implantar uma correção de bug crítica em um assembly compartilhado e desejar que todos os aplicativos usem essa nova versão, independentemente da versão com a qual foram criados. Além disso, o fornecedor de um assembly compartilhado pode ter enviado uma versão de serviço para um assembly existente e gostaria que todos os aplicativos começassem a usar a versão do serviço em vez da versão original. Esses cenários e outros têm suporte no .NET Framework por meio de políticas de versão.

As políticas de versão são declaradas em arquivos XML e são simplesmente uma solicitação para carregar uma versão de um assembly em vez de outra. Por exemplo, a política de versão a seguir direciona o CLR para carregar a versão 5.0.0.1 em vez da versão 5.0.0.0 de um assembly chamado MarineCtrl:

 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

Além de redirecionar de um número de versão específico para outro, você também pode redirecionar de um intervalo de versões para outra versão. Por exemplo, a política a seguir redireciona todas as versões de 0.0.0.0 a 5.0.0.0 de MarineCtrl para a versão 5.0.0.1:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

Níveis de política de versão

Há três níveis em que a política de versão pode ser aplicada no .NET: política específica do aplicativo, política de editor e política em todo o computador.

Política específica do aplicativo. Cada aplicativo tem um arquivo de configuração opcional que pode especificar o desejo do aplicativo de associar a uma versão diferente de um assembly dependente. O nome do arquivo de configuração varia de acordo com o tipo de aplicativo. Para arquivos executáveis, o nome do arquivo de configuração é o nome do executável + uma extensão ".config". Por exemplo, o arquivo de configuração para "myapp.exe" seria "myapp.exe.config". Os arquivos de configuração para aplicativos ASP.NET são sempre "web.config".

Política do Publicador. Embora a política específica do aplicativo seja definida pelo desenvolvedor de aplicativos ou pelo administrador, a política do editor é definida pelo fornecedor do assembly compartilhado. A política do publicador é a declaração de compatibilidade do fornecedor em relação a diferentes versões de seu assembly. Por exemplo, digamos que o fornecedor de um controle de Windows Forms compartilhado envie uma versão de serviço que contém várias correções de bugs para o controle. O controle original era a versão 2.0.0.0 e a versão da versão do serviço é 2.0.0.1. Como a nova versão contém apenas correções de bug (sem alterações interruptivas na API), o fornecedor de controle provavelmente emitiria a política do editor com a nova versão que faz com que os aplicativos existentes que usaram 2.0.0.0 agora comecem a usar 2.0.0.1. A política do publicador é expressa em XML da mesma forma que a política de todo o aplicativo e de todo o computador, mas ao contrário desses outros níveis de política, a política do editor é distribuída como um assembly em si. O principal motivo para isso é garantir que a organização que libera a política para um assembly específico seja a mesma organização que lançou o assembly em si. Isso é feito exigindo que o assembly original e o assembly de política recebam um nome forte com o mesmo par de chaves.

Política em todo o computador. O nível de política final é a política de todo o computador (às vezes chamada de política de administrador). A política em todo o computador é armazenada em machine.config que está localizado no subdiretório "config" no diretório de instalação .NET Framework. O diretório de instalação é %windir%\microsoft.net\framework\%runtimeversion%. As instruções de política feitas em machine.config afetam todos os aplicativos em execução no computador. A política em todo o computador é usada pelos Administradores para forçar todos os aplicativos em um determinado computador a usar uma versão específica de um assembly. O cenário de mais comentários em que isso é usado é quando uma segurança ou outra correção de bug crítica foi implantada no cache de assembly global. Depois de implantar o assembly fixo, o Administrador usaria a política de versão em todo o computador para garantir que os aplicativos não usem a versão antiga e interrompida do assembly.

Avaliação da política

A primeira coisa que o CLR faz ao associar a um assembly fortemente nomeado é determinar a qual versão do assembly associar. O processo começa lendo o número de versão do assembly desejado que foi registrado no manifesto do assembly que está fazendo a referência. Em seguida, a política é avaliada para determinar se algum dos níveis de política contém um redirecionamento para uma versão diferente. Os níveis de política são avaliados para começar com a política de aplicativo, seguidos pelo editor e, por fim, pelo administrador.

Um redirecionamento encontrado em qualquer nível substitui qualquer instrução feita por um nível anterior. Por exemplo, digamos que o assembly A faça referência ao assembly B. A referência a B no manifesto de A é para a versão 1.0.0.0. Além disso, a política de editor enviada com o assembly B redireciona a referência de 1.0.0.0 para 2.0.0.0. Além disso, há uma política de versão é o arquivo de configuração em todo o computador que direciona a referência para a versão 3.0.0.0. Nesse caso, a instrução feita no nível do computador substituirá a instrução feita no nível do editor.

Ignorando a política do publicador

Devido à ordem em que os três tipos de política são aplicados, o redirecionamento de versão do editor (política do editor) pode substituir a versão registrada nos metadados do assembly de chamada e qualquer política específica do aplicativo que foi definida. No entanto, forçar um aplicativo a sempre aceitar a recomendação de um editor sobre controle de versão pode levar de volta à DLL Hell. Afinal, o Inferno da DLL é causado principalmente pela dificuldade de manter a compatibilidade com versões anteriores em componentes compartilhados. Para evitar ainda mais o cenário em que um aplicativo é interrompido pela instalação de uma nova versão de um componente compartilhado, o sistema de política de versão no .NET permite que um aplicativo individual ignore a política do editor. Em outras palavras, um aplicativo pode recusar a recomendação do editor sobre qual versão usar. Um aplicativo pode ignorar a política do editor usando o elemento "publisherPolicy" no arquivo de configuração do aplicativo:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<publisherPolicy apply="no"/>

</assemblyBinding>

Definindo políticas de versão com a Ferramenta de Configuração do .NET

Felizmente, o .NET Framework é fornecido com uma ferramenta de administração gráfica para que você não precise se preocupar com a edição manual de arquivos de política XML. A ferramenta dá suporte à política de versão de todo o aplicativo e de todo o computador. Você encontrará a ferramenta na seção Ferramentas Administrativas do Painel de Controle. A tela inicial da ferramenta de administração se parece com a Figura 6:

Figura 6. A ferramenta Administração

As etapas a seguir descrevem como definir a política específica do aplicativo:

  1. Adicione seu aplicativo ao nó Aplicativos no modo de exibição de árvore. Clique com o botão direito do mouse no nó Aplicativos e clique em Adicionar. A caixa de diálogo Adicionar mostra uma lista de aplicativos .NET a serem escolhidos. Se o aplicativo não estiver na lista, você poderá adicioná-lo clicando em Outros.

  2. Escolha o assembly para o qual você deseja definir a política. Clique com o botão direito do mouse no nó Assemblies Configurados e clique em Adicionar. Uma das opções é escolher um assembly na lista de assemblies referenciados pelo aplicativo e exibir a caixa de diálogo a seguir, conforme mostrado na Figura 7. Escolha um assembly e clique em Selecionar.

    Figura 7. Escolhendo um assembly

  3. Na caixa de diálogo Propriedades , insira as informações da política de versão. Clique na guia Política de Associação e insira os números de versão desejados na tabela, conforme mostrado na Figura 8.

    Figura 8. Guia Política de Associação

Implantação

A implantação envolve pelo menos dois aspectos diferentes: empacotar o código e distribuir os pacotes para os vários clientes e servidores nos quais o aplicativo será executado. Um objetivo principal do .NET Framework é simplificar a implantação (especialmente o aspecto de distribuição) tornando viável a instalação de impacto zero e a implantação de xcopy. A natureza autodescritiva dos assemblies nos permite remover nossa dependência no registro, tornando a instalação, a desinstalação e a replicação muito mais simples. No entanto, há cenários em que xcopy não é suficiente ou apropriado como um mecanismo de distribuição. Para esses casos, o .NET Framework fornece amplos serviços de download de código e integração com o Windows Installer.

Empacotamento

Há três opções de empacotamento disponíveis na primeira versão do .NET Framework:

  • As-built (DLLs e EXEs). Em muitos cenários, nenhuma embalagem especial é necessária. Um aplicativo pode ser implantado no formato produzido pela ferramenta de desenvolvimento. Ou seja, uma coleção de DLLs e EXEs.
  • Arquivos cab. Os arquivos Cab podem ser usados para compactar seu aplicativo para downloads mais eficientes.
  • Pacotes do Windows Installer. O Microsoft Visual Studio .NET e outras ferramentas de instalação permitem que você crie pacotes do Windows Installer (arquivos .msi). O Windows Installer permite que você aproveite o reparo do aplicativo, a instalação sob demanda e outros recursos de gerenciamento de aplicativos do Microsoft Windows.

Cenários de distribuição

Os aplicativos .NET podem ser distribuídos de várias maneiras, incluindo xcopy, download de código e por meio do Windows Installer.

Para muitos aplicativos, incluindo aplicativos Web e Serviços Web, a implantação é tão simples quanto copiar um conjunto de arquivos para o disco e executá-los. A desinstalação e a replicação são tão fáceis quanto excluir os arquivos ou copiá-los.

O .NET Framework fornece amplo suporte para download de código usando um navegador da Web. Várias melhorias foram feitas nesta área, incluindo:

  • Impacto zero. Nenhuma entrada do Registro é feita no computador.
  • Download incremental. Partes de um assembly são baixadas somente conforme são referenciadas.
  • Baixe isolado para o aplicativo. O código baixado em nome de um aplicativo não pode afetar outras pessoas no computador. Um objetivo principal do nosso suporte de download de código é evitar o cenário em que um usuário baixa uma nova versão de algum componente compartilhado como um efeito colateral da navegação para um determinado site da Web e ter essa nova versão afeta negativamente outros aplicativos.
  • Nenhuma caixa de diálogo Authenticode. O sistema de segurança de acesso ao código é usado para permitir que o código móvel seja executado com um nível parcial de confiança. Os usuários nunca serão apresentados com caixas de diálogo pedindo que eles tomem uma decisão sobre se confiam no código.

Por fim, o .NET é totalmente integrado ao Windows Installer e aos recursos de gerenciamento de aplicativos do Windows.

Resumo

O .NET Framework permite a instalação de impacto zero e aborda a DLL Hell. Assemblies são as unidades de implantação autodescritivas e versões usadas para habilitar esses recursos.

A capacidade de criar aplicativos isolados é crucial porque permite que aplicativos sejam criados que não serão afetados por alterações feitas no sistema por outros aplicativos. O .NET Framework incentiva esse tipo de aplicativo por meio de assemblies privados de aplicativo implantados dentro da estrutura de diretório do aplicativo.

Lado a lado é uma parte central da história de compartilhamento e controle de versão no .NET. Lado a lado permite que várias versões de um assembly sejam instaladas e executadas no computador simultaneamente e permite que cada aplicativo solicite uma versão específica desse assembly.

O CLR registra informações de versão entre partes de um aplicativo e usa essas informações em tempo de execução para garantir que a versão adequada de uma dependência seja carregada. As políticas de versão podem ser usadas por desenvolvedores de aplicativos e administradores para fornecer alguma flexibilidade na escolha de qual versão de um determinado assembly é carregada.

Há várias opções de empacotamento e distribuição fornecidas pelo .NET Framework, incluindo o Windows Installer, download de código e xcopy simples.