Entendendo a instalação de componentes - Zetta-Ømnis Soluções Tecnológicas
Entendendo a instalação de componentes
Última Atualização: Segunda, 13 de Julho de 2020 às 00:22 Acessos: 42364
Então, por que você deveria se preocupar, se um componente está funcionando corretamente? Sabemos que essa pergunta, proveniente do Axioma #19 do XGH não se aplica muito bem no munto real. Um programador responsável não quer apenas fazer com que seus programas funcionem, eles se interessam em entender COMO eles funcionam. A curiosidade é o motor que nos torna especialistas em nossa profissão e se você não tem curiosidade, lamento muito, mas seu lugar não é aqui e te aconselho a parar de ler imediatamente. Se você for um programador Delphi de verdade, ou se considera como tal, pode continuar!
Eu deveria ter escrito este artigo ANTES de começar a falar a respeito do Open Tools API, mas o OTA é tão interessante que eu não resisti e terminei publicando o artigo sobre ele de forma prematura. Prematura apenas porque um dos pré-requisitos do OTA é saber o mínimo sobre instalação de pacotes no Delphi. Bom, antes tarde do que nunca, vou falar a respeito disso.
A quem este artigo é destinado?
Este artigo é destinado a desenvolvedores comuns e desenvolvedores de componentes. Eu estou especificando bem isso porque tanto um como outro podem tirar proveito das explicações que vou fazer.
Desenvolvedores comuns precisam entender como os componentes são instalados a fim de poderem resolver eventuais problemas de instalação devido a componentes mal desenvolvidos (por desenvolvedores de componentes) e com documentação insuficiente.
Desenvolvedores de componente precisam entender como os componentes são instalados a fim de poderem desenvolver e distribuir seus componentes de forma correta, causando assim o mínimo de impacto aos usuários finais (desenvolvedores comuns).
O que é o Library Path? O que é o Search Path? Onde os arquivos são salvos e onde eles precisam estar? Estas são algumas das perguntas que eu pretendo responder neste artigo.
Considerações iniciais (O básico)
Antes de falar a respeito da instalação de componentes é necessário citar alguns conceitos que são relacionados aos componentes (e ao Delphi) e que muitos de vocês simplesmente ignoram porque querem apenas começar a programar o mais rápido possível. Primeiramente o termo "componente" que eu utilize no título deste artigo é errado. Usei-o propositalmente para chamar a atenção de programadores iniciantes, mas para falar a verdade este artigo trata da instalação de pacotes no Delphi. Calma! Não queira me matar, você não foi enganado. Vou explicar melhor.
Todo componente é encapsulado em um pacote, mas nem todo pacote contém componentes de fato! Quando você instala um componente, na verdade você está instalando um pacote e este pacote pode conter nenhum, um, ou mais componentes. Convenciona-se, para facilitar, dizer "instalar um componente", mas na verdade você está instalando um pacote que pode conter bem mais que o componente em si sendo instalado. Tenha sempre isso em mente a fim de não cometer erros ao referenciar componentes ou pacotes. Em suma, pacotes podem conter componentes
Tipos de arquivo que o Delphi manipula
Gostaria de fazer uma revisão sobre os tipos de arquivos que o Delphi gera ou manipula dentro do contexto dos pacotes. É importante conhecê-los para não cometer erros ao instalar componentes e, principalmente, conhecer seus significados a fim de saber como referenciá-los no Library Path. Sem mais delongas, ei-los:
Arquivos de código-fonte (arquivos de texto plano)
.dpk Delphi Package Project File - Este é o arquivo de projeto principal de um pacote. Em comparação com um projeto padrão do Delphi, este arquivo equivale ao arquivo .dpr (Delphi Project). O arquivo de projeto de um pacote contém várias configurações adicionais, ao contrário do arquivo .dpr. A maioria destas configurações pode ser modificada por meio da caixa de diálogo "Project Options". Algumas das configurações mais básicas encontram-se no item (ou aba) "Description" desta caixa de diálogo, cujos campos que merecem destaque são: Description, Usage options e LIB suffix. Falarei mais a respeito destas opções posteriormente neste artigo
.pas Pascal Source File - Todo e qualquer projeto Delphi, seja um pacote, um executável, ou uma biblioteca, normalmente possui ao menos um arquivo de fonte. Bibliotecas (dll) ou aplicações de console, quando muito simples, podem conter apenas o arquivo .dpr, sem qualquer arquivo .pas. O arquivo .dpr, pode conter o fonte completo, mas por organização você não vai querer que todo o seu programa seja escrito em um único arquivo, logo, é desejável sempre ter ao menos um arquivo .pas para organizar seu código-fonte. Arquivos .pas são compiláveis e ao serem compilados geram arquivos .dcu como resultado
.inc Include File - Um arquivo de inclusão pode ter qualquer extensão, mas convenciona-se que arquivos desta natureza tenham a extensão .inc. O uso mais comum dos arquivos de inclusão é o agrupamento de diretivas de compilação, mas eles são bem mais que isso. Arquivos de inclusão são arquivos de texto plano que podem conter basicamente qualquer coisa que você colocaria em um arquivo .pas, mas com uma diferença muito importante: arquivos usados como arquivos de inclusão NÃO SÃO COMPILADOS SOZINHOS como os arquivos .pas, eles precisam ser incluídos em algum fonte compilável. Esta característica, que a princípio parece ser uma desvantagem, na verdade permite que arquivos de inclusão possuam trechos completos de código, classes, constantes, variáveis e qualquer outro tipo de código-fonte que podem ser incluídos em pontos específicos de arquivos .pas, sem necessidade de replicar código.
Como um exemplo bem simples e, por isso mesmo, não tão usual, suponha que você possui uma mensagem que precisa ser exibida da mesma forma em vários locais distintos dentro do seu código-fonte. Você pode escrever um arquivo minhamensagem.inc da seguinte maneira:
Application.MessageBox('Eu fui definida em um arquivo .inc'
,'Mensagem do arquivo de inclusão'
,MB_ICONWARNING);
Posteriormente você pode em um código-fonte propriamente dito (arquivo .pas) inclur este seu arquivo .inc, em um ponto do código-fonte onde ele faz sentido, isto é, em um local onde o conteúdo do arquivo .inc, SE FOSSE DIGITADO, seria correto. Como no arquivo .inc há apenas a chamada a Application.MessageBox, nós podemos incluir nosso arquivo em um local onde a chamada a esta função pode ser aplicada, por exemplo, o clique de um botão:
{$i minhamensagem.inc}
Como se pode ver, para referenciar um arquivo de inclusão, se usa a diretiva {$I nomedoarquivo.inc}. A grande vantagem dos arquivos de inclusão só é percebida quando se entende que o trecho de código acima, na verdade é interpretado pelo compilador como:
Como se pode observar o conteúdo do arquivo de inclusão é "digitado" no local onde ele for incluído, logo, o ponto no código-fonte onde arquivos de inclusão podem ser colocados depende unicamente de seu conteúdo. Você poderia colocar uma classe inteira, com todos os seus métodos definidos, dentro de um arquivo de inclusão, no entanto ele não poderia ser incluído dentro do clique de um botão, pois se você substituir, na posição do include, o conteúdo do arquivo de inclusão, o fonte .pas não vai compilar.
Arquivos gerados (arquivos binários)
.dcu Delphi Compiled Unit - Todo arquivo .pas, ao ser compilado, gera um arquivo correspondente .dcu. Arquivos .dcu, em outras palavras, são a versão binária de um arquivo .pas. Linguagens como C e C++, possuem um arquivo chamado .obj que são gerados a partir de arquivos .c ou .cpp. Pode-se dizer que arquivos .dcu estão para arquivos .pas assim como arquivos .obj estão para arquivos .c ou .cpp. Arquivos .dcu são diferentes quando compilados por compiladores diferentes, isto é, se você tem um mesmo fonte .pas e compila este fonte no Delphi 2006 e no Delphi XE5, os arquivos .dcu gerados serão completamente diferentes, apesar de fazerem a mesma coisa. É este um dos motivos pelos quais não se pode instalar componentes sem fontes (sem arquivos .pas) em qualquer Delphi, porque os arquivos .pas, .bpl e .dcp são dependentes da versão do compilador e simplesmente são incompatíveis. Você só consegue instalar componentes sem fontes, quando estes 3 arquivos foram compilados exclusivamente para a versão do Delphi onde você pretende instalá-los
.bpl Borland Package Library - Arquivos de projeto .dpr geram arquivos .exe ou .dll. Arquivos de projeto .dpk geram sempre dois arquivos. Um deles é o arquivo .bpl o outro é o arquivo .dcp (vide próximo ítem). Arquivos .bpl estão para projetos .dpk assim como arquivos .exe e .dll estão para projetos .dpr, logo, os arquivos .bpl são o resultado final mais importante da compilação de um pacote. Eles se assemelham estruturalmente a uma dll e como tal podem ser carregados por executáveis (Runtime Packages) e, no seu uso mais simples, carregadas pelo próprio Delphi (Designtime Packages). O carregamento por executáveis não será coberto neste artigo, apenas o carregamento simples, pela IDE, o qual é essencial para o entendimento de como ocorre a instalação de pacotes. Quando um pacote é instalado, na verdade, nós estamos informando ao Delphi que aquele determinado arquivo .bpl gerado, deve ser carregado pela IDE. A instalação deste arquivo fará com que componentes apareçam na paleta de componentes e experts/wizards sejam registrados (vide artigo Open Tools API).
.dcp Delphi compiled package - O arquivo .dcp é gerado juntamente com o arquivo .bpl. Ele é um arquivo binário especial que contém o cabeçalho do pacote BPL e a concatenação de todos os arquivos .dcu deste pacote, bem como toda informação necessária requerida pelo compilador e pelo ligador (linker).
Como se pode observar, o arquivo .dcp é como se fosse um resumo binário de todo o código-fonte do pacote, por isso, uma característica muito interessante deste arquivo é que ele substitui a presença de todos os arquivos .dcu que ele contém! De fato, quando um pacote faz referência em sua cláusula requires (veja mais adiante) a um arquivo .dcp, este pacote pode usar units que estão neste arquivo .dcp, como se as units compiladas (.dcu) estivessem fisicamente disponíveis! Em suma, eu não preciso ter nenhum arquivo .dcu disponível, caso eu possua o arquivo .dcp que o contenha.
O papel do arquivo .dcp é muito relevante dentro desenvolvimento de pacotes no Delphi. Os arquivos .dpk possuem uma seção especial chamada requires, na qual outros pacotes são referenciados, veja:
Como se pode ver, este pacote possui 3 dependências de outros pacotes. Significa que dentro do pacote nós estamos usando componentes, funções ou recursos de uma forma geral que estão disponíveis em outros pacotes. No pacote do exemplo acima eu estou fazendo referências ao TClientDataSet, o qual é definido na unit DBClient que foi compilada no pacote dsnapXXX.bpl[1] e por isso eu preciso fazer referência direta ao "resumo" deste pacote. Para isso eu uso seu arquivo .dcp correspondente (dsnap.dcp[2]) na cláusula requires do .dpk, pois dentro de dsnap.dcp existe o arquivo DBClient.dcu.
Apesar de eu estar falando que o arquivo .dcu está dentro do arquivo .dcp, isso é apenas para facilitar o entendimento. Arquivos .dcp não são contêineres, logo, não é possível extrair um arquivo .dcu de dentro do arquivo .dcp. O arquivo .dcp possui todo conteúdo binário de todos os arquivos .dcu de um pacote. O Delphi, seu compilador e seu linker fazem referência a parte desse código de forma nomeada, usando o mesmo nome da unit original, por exemplo, dentro de um pacote que requer dsnap.dcp, caso em uma unit eu declare na cláusula uses DBClient, o Delphi vai saber que o código desta unit está disponível. No momento da compilação, o compilador entende que deve procurar o código binário de DBClient dentro de dsnap.dcp.
Arquivos .dcp são muito importantes quando precisamos nos referenciar ao nosso pacote a partir de outro, logo, isso não é algo que um iniciante no desenvolvimento de componentes vá fazer, portanto, não precisa se preocupar com os arquivos .dcp no momento. Apenas entenda que eles são necessários e não devem ser negligenciados.
Arquivos de recurso (arquivos binários e de texto plano)
.res / .dcr Resource File - Arquivos de recurso com a extensão .res ou .dcr são arquivos binários que podem conter ícones, cursores, imagens, strings, teclas de atalho, menus, caixas de diálogo, dados binários sem formato específico (raw) e informações de versão. Normalmente no Delphi você não precisa se preocupar com arquivos de recurso, pois eles são criados automaticamente e incluídos no binário final (.exe, .dll, .bpl), contudo, ao desenvolver componentes especificamente, eventualmente você vai precisar incluir ao menos recursos de imagem e ícones no seu BPL e a única forma de fazer isso é por meio do uso de arquivos de recurso. Você pode criar um arquivo .res / .dcr usando um editor de recursos e vincular este arquivo de recurso ao seu projeto, incluindo a diretiva {$R nomedoarquivo.res} ou {$R nomedoarquivo.dcr} em algum fonte do mesmo (ou no seu arquivo de projeto). A inclusão pode usar um arquivo de texto plano também (vide .rc abaixo). Não existem diferenças práticas entre um .dcr e um .res, apenas convenciona-se usar a extensão .dcr para indicar um arquivo de recurso que possui os ícones de um componente que serão apresentados na paleta de componente do Delphi. DCR significa "Delphi Component Resource"
.rc Compiler Resource File - Arquivos de recurso com a extensão .rc são arquivos de texto plano e que podem ser considerados "código-fonte" de recurso, pois é possível gerar um arquivo .res a partir de um arquivo .rc utilizando um compilador de recurso (Resource Compiler). A extensão .rc, portanto, remete ao nome Resource Compiler, já que este arquivo sempre precisa ser compilado antes de ser usado. Arquivos .rc podem conter exatamente os mesmos itens que um arquivo .res, a diferença é que estes ítens são incluídos no arquivo .rc de forma textual, "legível por humanos". Arquivos .rc não podem ser vinculados diretamente a um binário, tal como acontece com os arquivos .res, é necessário compilá-lo antes e, por este motivo, o Delphi possui uma sintaxe especial da diretiva {$R} que permite compilar e vincular um arquivo .rc. Utilizando {$R nomedoarquivo.res nomedoarquivo.rc}, nomedoarquivo.rc será automaticamente compilado e gerará o arquivo nomedoarquivo.res, o qual, finalmente, será vinculado pela diretiva
.dfm Delphi Form File - Você deve estar se perguntando porque arquivos .dfm estão incluídos nesta lista de recursos, bom, se segure na cadeira meu caro, mas arquivos .dfm são arquivos de recurso! Você não leu errado. Arquivos .dfm são arquivos de recurso especiais e exclusivos do Delphi e que contém referências a todas as propriedades publicadss do TForm e de todos os componentes nele incluídos. Propriedades publicadas, ou published, são propriedades que geram Informações de Tipo em Tempo de Execução, ou RTTI. Se você abrir um executável, por exemplo, em um editor de recurso, e for na seção RCData, você verá todos os seus formulários lá, veja:
Note que as propriedades publicadas do TForm aparecem, bem como as propriedades publicadas de outros componentes inseridos no TForm, tal como o TButton que se pode ver na imagem acima. Você deve estar pensando agora se é possível modificar algumas destas propriedades diretamente no arquivo binário usando o editor de recurso, e, bem, sim, isso é totalmente possível! Qualquer uma destas propriedades pode ser alterada e salva diretamente no arquivo binário, no caso no .exe, sem necessidade de recompilá-lo, e isso vai alterar o resultado da aplicação em tempo de execução, porém, devo alertar que algumas coisas não devem ser alteradas, sob pena de você inutilizar seu executável. Arquivos .dfm podem ser salvos de forma binária o que não é recomendável, mas mesmo quando eles são salvos desta forma, dentro do binário compilado sua representação sempre é textual. Isso significa que o modo binário de salvamento de arquivos .dfm só afeta de fato o arquivo em si e não a forma como ele é vinculado ao projeto finalizado (.exe, .dll ou .bpl).
Outra prova inegável de que arquivos .dfm são, também, arquivos de recuso, é a forma como eles são vinculados aos projetos. Se você criar um projeto novo e olhar o código-fonte do único formulário você verá a seguinte linha {$R *.dfm}. A diretiva {$R} você já conhece, ela serve para vincular um arquivo de recurso ao projeto, logo, o nome de arquivo que vem depois dela, é um arquivo de recurso. Mas tem algo diferente, você diz, "não existe um nome de arquivo, mas sim um caractere curinga com a extensão .dfm". Se você pensou isso você está errado. O asterisco parece, mas não é um caractere curinga e *.dfm não significa, portanto, todos os arquivos com extensão .dfm. A diretiva {$R} interpreta o asterisco como sendo o nome-base da unit atual, isto é, o nome da unit sem a extensão, logo, se sua unit se chama UFormPrincipal.pas, usar a diretiva {$R *.dfm} nela vai instruir ao Delphi para vincular ao projeto o arquivo UFormPrincipal.dfm. É por este motivo que toda vez que você salva a unit de um TForm, um arquivo .dfm de mesmo nome será gerado juntamente com ela. Se você alterar a diretiva e colocar o nome do arquivo .dfm completo, vai funcionar do mesmo modo, no entanto, isso é desencorajado, na maioria das situações, quando você não sabe o que está fazendo, pois, caso você altere o nome da unit, um novo .dfm será criado para ela, mas, internamente, ela estará vinculada a um outro arquivo .dfm, o que pode gerar uma grande confusão. O caractere especial "*" também pode ser usado ao fazer referência a um arquivo .res propriamente dito, tendo, pois, o mesmo significado!
Para saber um pouco mais sobre RTTI, arquivos .dfm e um uso inusitado dessa combinação de tecnologias, leia o artigo Serialização de Objetos & Persistência em Arquivos.
Arquivos gerados pela compilação de um pacote
Ao contrário de um executável ou de um biblioteca (dll) a compilação de um pacote gera 3 tipos de arquivo importantes:
Arquivo BPL - Cada pacote gera um e apenas um arquivo BPL. Arquivos BPL podem ser compilados de acordo com o uso que eles terão. Esta configuração chama-se "Usage Option" e está disponível no item (ou aba) "Description" da caixa de diálogo "Project Options". Existem 3 formas de uso para um BPL:
Designtime Only - BPLs desse tipo são desenvolvidos para serem instalados na IDE e por este motivo eles normalmente possuem apenas código que só faz sentido dentro da IDE. Este código inclui basicamente editores de componente, editores de propriedade e código de registro que normalmente é colocado dentro do procedure especial "Register". Não é comum desenvolver um BPL deste tipo que não contenha alguns destes códigos específicos. Se sua intenção é desenvolver um pacote que contenha apenas units e funções utilizáveis por projetos, você deve desenvolver um pacote "Runtime Only"
Runtime Only - BPLs desse tipo são desenvolvidos para serem usados como pacote em tempo de execução por executáveis ou internamente pela IDE em tempo de desenvolvimento por projetos. Este tipo de BPL não é instalável, então ele não pode conter códigos dos tipos mencionados no item anterior (Designtime Only). Pacotes deste tipo podem conter, portanto, todo o restante de possíveis códigos, incluindo o código de componentes que são instalados em pacotes do tipo Designtime Only
Designtime and Runtime - BPLs deste tipo possuem uma junção de características dos dois tipos anteriores. Em outras palavras, pacotes deste tipo contém componentes e também podem ser carregados por executáveis em tempo de execução
Arquivo DCP - Concatenação binária de todas as units de um pacote sendo compilado (BPL). Vide explicação sobre arquivos .dcp na tabela acima.
Arquivo DCU - Arquivos de fonte compilados (arquivos .pas compilados). Vide explicação sobre arquivos .dcu na tabela acima. Os arquivos .dcu são tão importantes em um pacote quanto o próprio arquivo .bpl que é gerado. Enquanto para um executável normal ou biblioteca, arquivos .dcu são apenas arquivos intermediários, para os pacotes estes arquivos são essenciais para que o componente possa ser usado. Arquivos .dcu de um pacote são SEMPRE arquivos intermediários de outros projetos que usam units desse pacote. De forma mais simples, arquivos .dcu de um componente são arquivos intermediários quando compilamos projetos que usam este componente e é por isso que os arquivos .dcu de um pacote precisam sempre estar disponíveis via "paths do Delphi" (leia mais adiante)
Como o Delphi encontra os arquivos de um pacote?
É de extrema importância conhecer como o Delphi funciona para poder saber resolver problemas comuns. Entender como o Delphi encontra os arquivos de um pacote é um requisito básico, seja você um desenvolvedor comum ou um desenvolvedor de componentes. Como desenvolvedor comum, por exemplo, se você já programa há algum tempo e já instalou componentes, certamente você já deve ter visto uma mensagem parecida com esta:
Unit MinhaUnit was compiled with a different version of MinhaOutraUnit.TMinhaClasse
Este é um dos erros mais frequentes causados por uma instalação inadequada de pacotes e pode ocorrer até mesmo dentro de projetos comuns (não pacotes), sendo mais raros nestes últimos. Ao entender como o Delphi encontra os arquivos de um projeto qualquer ficará mais fácil resolver problemas como este, bem como evitar que eles ocorram, fazendo sempre o uso correto das configurações de "Paths" que o Delphi considera.
A forma como o Delphi procura os arquivos de um pacote é a mesma forma que ele usa para encontrar os arquivos de qualquer projeto que nele se compila. Antes de explicar como isso é feito, é necessário entender que uma unit pode estar disponível de forma compilada (.dcu) ou código-fonte (.pas) e o Delphi tem preferências especiais quando encontra estes arquivos:
Se estiver disponível apenas o arquivo .pas ele será usado, sendo compilado normalmente
Se estiver disponível apenas o arquivo .dcu ele será usado, mas não será compilado, porque não é necessário
Se ambos os arquivos estiverem disponíveis, será usado sempre o arquivo .pas, o qual será compilado normalmente
De agora em diante ao me referenciar a uma unit eu estarei falando de um arquivo que representa uma unit do Delphi e que pode ser, ou um .dcu, ou um .pas. Isso servirá apenas para que eu não tenha que me referir aos dois arquivos, que no final representam a mesma coisa de formas diferentes.
Finalmente, conhecendo a ordem de preferência que o Delphi usa para selecionar uma unit, podemos agora verificar de forma simples e linear (em ordem de busca), como ele faz para achar as units que estão referenciadas nas cláusulas uses:
Procura dentre as units listadas explicitamente no arquivo .dpk
Procura no mesmo diretório que contém o arquivo .dpk
Procura dentre os caminhos listados no Search Path (projeto)
Procura dentre os caminhos listados no Library Path (global)
Caso uma unit não esteja em nenhum destes 4 lugares o seu projeto não vai compilar de jeito nenhum, mas gostaria de relatar aqui algo que constatei. Vou até deixar este relato destacado.
Nos testes que eu fiz para determinar os 4 locais de busca, aconteceu algo muito estranho. Eu compilei um projeto de teste com uma unit na mesma pasta do arquivo .dpk, em seguida a apaguei esperando receber um erro de compilação, já que a unit não existia mais. Para minha surpresa o projeto compilou normalmente e até mesmo o arquivo .dcu correspondente ao arquivo .pas que eu apaguei foi criado! Não consegui descobrir como o compilador fez isso, mas ao fechar e abrir o Delphi o comportamento foi o esperado, isto é, o projeto não mais compilou. Até onde eu sei, isso é um comportamento muito bizarro, portanto, fica como dica de boa prática, fechar o Delphi e abri-lo novamente, sempre que se mover ou apagar units, principalmente quando se estiver desenvolvendo componentes, já que é imprescindível que os arquivos sendo referenciados pelo projeto existam de fato e não por conta de algum cache sobrenatural
Além de units, um esquema de busca semelhante é usado para outros arquivos do projeto, tais como arquivos .res, .dfm e .inc. Estes arquivos são incluídos no projeto por diretivas especiais {$R}, para arquivos .res e .dfm, e {$I}, para arquivos .inc. Ambas as diretivas aceitam o caractere especial "*" (veja o significado deste caractere na explicação sobre arquivos .dfm, mais acima) e também um caminho que pode ser absoluto ou relativo. Vejamos alguns pormenores de cada uma destas formas de uso das diretivas:
Ao usar caminhos absolutos, caso este caminho contenha espaços, aspas simples devem ser usadas em volta do mesmo. Referências por caminhos absolutos, como é de se imaginar, não têm ambiguidades, logo não há dúvidas de que um arquivo referenciado desta forma deve existir impreterivelmente no local indicado e ponto final. Já ao usar caminhos relativos, será considerado como ponto de partida o diretório onde a unit com a referência a {$R} ou {$I} estiver. A regra de uso de aspas simples também se aplica aqui, bem como o fato de que o caminho é totalmente conhecido e sem ambiguidades
Ao usar o caractere * é necessário entender que ele se transforma meramente no nome da unit onde a referência a {$R} ou {$I} estiver, portanto, {$I *.inc} dentro de uma unit de nome teste.pas, na verdade deve ser lida como {$I teste.inc}
No segundo uso mostrado acima, como teremos apenas o nome de um arquivo, sem caminhos relativos ou completos, a busca por ele seguirá a seguinte regra:
Procura no mesmo diretório onde a unit com a referência a {$R} ou {$I} estiver
Foi detectado que o problema do cache sobrenatural descrito no quadro acima também afeta estes arquivos especiais, só que, ao contrário das units, o problema ocorreu ao remover um arquivo .inc que estava na mesma pasta de uma unit que o usava. Mesmo sem o arquivo .inc presente, ainda assim a compilação foi bem sucedida e o arquivo .inc carregado era aquele que estaria na pasta. Bizarro! Ao reiniciar o Delphi o comportamento foi o esperado mais uma vez, portanto, muito cuidado ao mover ou excluir tais arquivos. Lembre-se sempre de reiniciar o Delphi.
O Delphi não busca arquivos em caminhos listados no Path do Windows. Isso é uma má prática hoje em dia. Não polua seu sistema ou o de seus clientes com arquivos desnecessariamente. Apenas arquivos .bpl que são carregados pelo Delphi, precisam estar em caminhos listados no Path do Windows e mesmo assim estes caminhos são exclusivos no Delphi, ou seja, não espere encontrar arquivos .bpl dentro de pastas como C:\Windows\System32, C:\Windows\System ou C:\Windows\SysWOW64. A prática de colocar estes arquivos nestes caminhos de sistema foi felizmente abolida! Se você é um programador das antigas, já deve ter colocado algumas BPLs na pasta System ou System32, mas não faça mais isso! Existe uma pasta específica para este tipo de arquivo. Esta pasta pode ser configurada no mesmo local onde se encontram o Library Path e o Browsing Path (explicados posteriormente neste artigo). A configuração se chama Package Output Directory. Os nome desta configuração pode variar um pouco, mas não será difícil identificá-la.
Apresentando os vilões: Search Path & Library Path
Sim! Estas duas configurações são maliciosas e são a causa de muitos problemas de instalação de pacotes e até mesmo de compilação de projetos em geral. Na verdade a maior vantagem de ambas é também sua maior desvantagem: a versatilidade! De tão versáteis, estas propriedades se tornam perigosas aos incautos e eu vou explicar o porquê.
Bem, um path funciona listando vários diretórios (pastas) de busca. Suponha que você tenha uns 100 diretórios listados, neste caso os arquivos que o Delphi procura podem estar em quaisquer destes diretórios, inclusive em mais de um deles e é aí onde está o lado negro dessa propriedade. Se você faz referência a MinhaUnit em uma cláusula uses e existe MinhaUnit.pas (ou .dcu) em mais de um desses 100 diretórios, o Delphi vai usar aquele que achar primeiro, segundo a ordem da lista de diretórios. Veja, por exemplo, a lista de um Library Path típico:
Acima, a ordem que o Delphi realiza a busca por um arquivo é, de fato, de cima para baixo. Se nossa unit estiver em todos estes caminhos, o Delphi vai usar a versão que está no primeiro diretório listado e vai ignorar a presença de tal unit nos demais. Se a versão contida no primeiro diretório for mais antiga do que aquela que está nos outros, podem haver erros de compilação ou comportamentos errados de um programa que use esta unit.
Caso no path escolhido pelo Delphi esteja uma versão compilada (.dcu) ela será usada, tal como foi dito anteriormente, mas caso este arquivo .dcu seja uma compilação de uma versão diferente daquela que se espera, a famosa mensagem "Unit MinhaUnit was compiled with a different version of MinhaOutraUnit.TMinhaClasse" será exibida! Ignore a posição de MinhaUnit nesta mensagem. Ela poderia estar no lugar de MinhaOutraUnit, não importa, o que importa é que este tipo de erro acontece por existirem versões de arquivos .dcu diferentes daquelas que deveriam existir.
Se você estiver tendo alguns dos problemas listados aqui, o primeiro passo é procurar nos caminhos listados tanto no Library Path como no Search Path do projeto as units problemáticas e verificar se elas não se repetem, mantendo apenas uma cópia de cada uma no seu caminho correto. Procure sempre por nomedaunit.* e mantenha inicialmente apenas arquivos .pas. Isso é uma regra geral para ajudar a resolver problemas rapidamente, mas como eu explicarei posteriormente neste artigo, devemos manter no Library Path apenas arquivos .dcu, .res, .inc e .dfm, enquanto que no Search Path só devem existir arquivos .pas.
A fim de tirar você da ignorância quanto à instalação de pacotes eu fiz este artigo e a partir deste ponto eu vou explicar para que servem cada uma das configurações, de forma que você as use corretamente e possa amaldiçoar quem as usa de forma errada.
O que é o Search Path?
Todos os projetos no Delphi possuem o Search Path que nada mais é do que uma coleção de caminhos (paths) onde o Delphi busca por arquivos que são referenciados dentro do projeto em cláusulas uses ou diretivas {$R} ou {$I}. O Search Path deve ser considerado como uma lista privada de caminhos acessíveis apenas pelo projeto onde ele for configurado.
Suponha que você possui um sistema dividido em módulos executáveis. Neste caso você terá n projetos Delphi, e caso dentro deste sistema, projetos distintos utilizem uma mesma unit, esta unit pode ficar em um único local e ser referenciada por cada um dos projetos por meio do Search Path, configurado individualmente em cada um deles.
Um exemplo de uso real disso é quando se desenvolve uma aplicação DataSnap, a qual tem no mínimo dois projetos, um para o cliente (Thin Client) e outro para a camada do meio (MiddleWare). Ambos os módulos fazem parte de um único sistema (separado pelo DataSnap), logo eles podem compartilhar arquivos entre si e estes arquivos devem ser referenciadas por meio do Search Path de cada um dos projetos (Thin Client e MiddleWare).
Falando especificamente de units, tanto arquivos .dcu como arquivos .pas podem existir nos caminhos do Search Path, no entanto, como ele é uma coleção de caminhos de busca de um projeto específico, fica claro que arquivos listados no Search Path fazem parte do código-fonte do projeto onde ele for definido, logo, as units presentes nos caminhos de um Search Path devem ser arquivos .pas.
Acima podemos ver, mais ao fundo, a caixa de diálogo Project Options, que pode ser acessada através do menu Project > Options ou através da combinação de teclas Shift+Ctrl+F11. A localização da configuração do Search Path varia de acordo com a versão do Delphi. A imagem mostra a versão do Delphi XE5, mas se seu Delphi não apresentar a caixa de diálogo como na imagem, certamente será fácil de achar esta configuração.
Clicando no botão de reticências vai exibir o editor do Search Path, o qual, na imagem, mostra 3 caminhos. Ao compilar este projeto estes 3 caminhos serão vasculhados em busca de arquivos referenciados em cláusulas uses e diretivas {$R} e {$I}.
O papel do Search Path nos pacotes
O Search Path parece ser bem útil para organização de projetos, no entanto, no tocante aos pacotes especificamente, seu uso deve ser limitado apenas para indicar caminhos com arquivos .res, .dcr, .rc, .dfm e .inc. O motivo disso é que o Delphi não permite que pacotes distintos contenham units compartilhadas entre si.
Se você usar o Search Path para compartilhar units entre pacotes distintos, duas coisas vão acontecer. No caso mais brando será emitido um aviso ao compilar, e no caso mais grave seu pacote não será compilado ou não poderá ser carregado pelo Delphi.
No primeiro caso, a referência indireta por meio de Search Path vai gerar o seguinte aviso:
[dcc32 Warning] MeuPacote.dpk(88): W1033 Unit 'UMinhaUnit' implicitly imported into package 'MeuPacote'
A ajuda do Delphi é clara a respeito deste aviso, quando diz "This message will help the programmer avoid violating the rule that a unit may not reside in more than one related package", ou seja, pacotes relacionados não podem conter referências às mesmas units. Um pacote está relacionado a outro quando, por exemplo, PackageB.bpl depende de PackageA.bpl, porque PackageB.bpl contém em sua cláusula requires uma referência a PackageA.dcp. Quando dois pacotes estão relacioados desta forma apenas um dos pacotes precisa conter diretamente todas as units, por exemplo, suponha que PackageA.bpl contenha as units Unit1.pas, Unit2.pas e Unit3.pas. Suponha que PackageB.bpl também precise destas units (todas ou algumas delas, não importa). Neste caso, é suficiente que PackageB.bpl contenha na sua cláusula requires uma referência a PackageA.dcp para que ele "enxergue" e use todas as units que existem em PackageA.bpl, sem necessidade de duplicar ou compartilhar units.
O segundo problema vai acontecer se você carregar (instalar) um pacote e depois tentar compilar um outro pacote que tem units compartilhadas com o primeiro, o seguinte erro de compilação vai aparecer:
[dcc32 Error] MeuPacote.dpk(45): E2200 Package 'MeuOutroPacote' already contains unit 'MinhUnit'
Caso você consiga compilar os dois pacotes com units compartilhadas entre si e carregar um deles, ao tentar carregar o segundo, o seguinte erro será exibido:
Em suma, o Delphi vai dar um jeito de evitar que você faça esse tipo de coisa, portanto, vou deixar abaixo um aviso importante:
NÃO USE O SEARCH PATH PARA COMPARTILHAR UNITS ENTRE PACOTES. AO INVÉS DISSO, COLOQUE TODAS AS UNITS COMPARTILHÁVEIS EM UM DOS PACOTES E FAÇA COM QUE OUTROS PACOTES DEPENDAM DELE
O que é o Library Path?
O Library Path é uma configuração que afeta todos os projetos (configuração global) e sua definição é basicamente a mesma do Search Path. Ele é, pois, uma coleção de paths onde o Delphi busca por arquivos. A diferença entre o Library Path e o Search Path é apenas quanto aos tipos de units que devem ser encontradas em cada um deles e quanto a sua especificidade.
Quanto aos tipos de units, enquanto no Search Path devemos ter apenas units não compiladas (.pas), no Library Path devemos ter apenas units compiladas (.dcu). A especificidade refere-se ao quão as units estão relacionadas ao projeto. Observe a imagem a seguir:
Nesta imagem, a busca por units se dá no sentido das setas. Inicialmente se busca dentre as units do próprio projeto, em seguida se tenta encontrar as units no Search Path e por fim no Library Path. Units que pertencem diretamente ao projeto devem ser exclusivamente arquivos .pas, pois estes arquivos podem ser editados por nós e compilados posteriormente para gerar o arquivo final de nosso projeto (.exe, .dll, .bpl, etc.).
Units que estão no Search Path também devem ser arquivos .pas, e também pertencem ao projeto, pois o Search Path é uma configuração específica de cada projeto, a diferença é que as units acessadas via Search Path também podem ser usadas por outros projetos em um sistema que utiliza vários módulos (várias DLLs, vários executáveis, etc.). Faz sentido que projetos distintos façam uso de units em comum, quando estas units compartilharem código que pertença exclusivamente a estes projetos, seja por conterem regras de negócios específicas, seja por fazerem parte de algum framework customizado para alguns projetos apenas. Estas units, portanto, podem ser colocadas em diretórios que estão fora da hierarquia de diretórios dos projetos e poderão ser encontradas via Search Path.
Units que estão no Library Path devem ser exclusivamente arquios .dcu. Esta é a dica de ouro que justifica a existência de todo este artigo. Observe novamente a tela de edição do Library Path:
O primeiro path referenciado no Library Path informa ao Delphi onde encontrar todas as units compiladas (.dcu) de sistema. Units de sistema são units do próprio Delphi, que nós usamos frequentemente, tais como StdCtrls, SysUtils, StrUtils, dentre outras. Se você remover este path o Delphi vai parar de funcionar. No caminho indicado, caso você tenha curiosidade, todos os arquivos presentes são binários e lá, além de encontrarmos arquivos .dcu, também encontraremos alguns arquivos .dfm.
O Library Path, claro, também funciona apontando para caminhos com arquivos .pas, no entanto, por esta ser uma configuração global, não faz sentido que projetos completamente distintos compilem esses arquivos .pas toda vez, que é justamente o que acontece quando arquivos .pas existem no Library Path.
Isso mesmo, arquivos de código-fonte .pas encontrados via Library Path são compilados separadamente por cada projeto que fizer referência a suas units, e estas units são salvas no Unit Output Directory (veja mais adiante neste artigo) do projeto, dando a falsa impressão de que aquela unit pertence ao projeto diretamente, quando na verdade ela deveria ter sido apenas referenciada de forma binária indiretamente, para apenas ser usada na fase de linking de um projeto.
Units encontradas via Library Path não precisam ser modificadas e consequentemente não precisam ser compiladas, além disso, elas podem ser usadas por quaisquer de nossos projetos, portanto, elas não podem estar ligadas a projetos específicos, como acontece com o Search Path.
Quando o Library Path está configurado corretamente, nenhuma de suas units será incluída no Unit Output Directory de nenhum projeto e consequentemente projetos diferentes não conterão sua própria versão de um .dcu que deveria ser único, por nunca precisar sofrer alterações por ser genérico (utilizável em vários projetos diferentes).
O fato de encontrarmos apenas units compiladas no Library Path nos leva a concluir que em algum momento elas foram compiladas, portanto, units presentes no Library Path foram criadas durante a compilação de algum pacote, logo, normalmente os caminhos existentes no Library Path pertencem, de uma forma ou de outra, a pacotes.
Paths e tipos de arquivo (resumo)
Abaixo está uma tabela que resume quais arquivos podem existir em cada um dos paths que o Delphi considera. Obviamente caso seu projeto não utilize alguns dos arquivos mencionados, não significa que você precisa usá-los, mas caso faça uso deles, esta tabela será sua grande amiga daqui pra frente. Eu não vou chamar esta tabela de recomendação pessoal, porque eu tenho visto muita gente fazendo uso dos paths de forma totalmente errada, portanto, eu considero a minha abordagem como um guia definitivo para minimizar problemas. Tem horas que, para o bem maior, a gente não pode ser um "falso modesto". Use a tabela ou continue fazendo errado. A escolha é sua
Search Path X * X X X X X
Library Path X X ** X ** X **
Browsing Path X
Package Output Directory X
DCP Output Directory X
Output Directory X X
Unit Output Directory X
* Este arquivo só deve ser incluído no Search Path caso você esteja compilando um projeto normal. O Search Path não deve ser usado para referenciar units em projetos de pacotes porque o Delphi não gosta de units com o mesmo nome em pacotes (BPLs) distintos.
** Este arquivo só deve ser incluído no Library Path caso ele seja requerido durante a compilação de outros projetos. No caso dos arquivos res e inc, se eles forem utilizados apenas pelo pacote no momento da geração do BPL, eles não precisam ser incluídos no Library Path. No caso dos arquivos dfm, se eles representarem telas que são acessadas apenas em tempo de projeto, tais como telas de editores de propriedade e de componentes, eles não precisam ser incluídos no Library Path
Proposta de organização de projetos e pacotes
Organização é tudo, então resolvi terminar esse artigo com uma proposta de organização de pastas de projetos que vai facilitar sua vida, caso você decida começar a usar os paths da forma certa.
Meu Projeto\bin - Arquivo binário finalizado. Configure esta pasta como Output Directory nas propriedades do projeto
Meu Projeto\bin\dcu - Units compiladas. Configure esta pasta como Unit Output Directory, nas propriedades do projeto
Meu Projeto\dev - Arquivos de suporte relacionados ao projeto, mas que não são compiláveis
Meu Projeto\dev\doc - Arquivos de documentação a respeito do projeto
Meu Projeto\prj - Arquivo .dpr do projeto e todos os arquivos que forem criados automaticamente pelo Delphi
Meu Projeto\res - Arquivos de recurso que são utilizados pelo projeto na hora da compilação do mesmo. Podem ser arquivos .res, imagens, ícones ou qualquer outro tipo de arquivo que é carregado pelo projeto e é incluído no binário finalizado, isso inclui textos, scripts, etc. Você pode incluir esta pasta no Search Path do projeto a fim de facilitar a referência dos recursos
Meu Projeto\src - Arquivos de código-fonte .pas exclusivos deste projeto
Se seu projeto tiver mais de um módulo ele conterá mais de um arquivo de projeto (.dpr). Todos estes arquivos deverão estar em Meu Projeto\prj. Você pode, se quiser, colocar cada arquivo de projeto em uma pasta separada abaixo de prj a fim de separar cada projeto. Você pode também, neste caso, diretamente em Meu Projeto\prj, colocar o arquivo de grupo de projetos (.bdsgroup ou .groupproj), o qual vai aglutinar todos os projetos de módulos individuais de forma que você possa ter acesso simplificado a partir da IDE a todos os projetos
Se seu projeto tiver mais de um módulo ele conterá fontes de mais de um projeto. Todos estes arquivos deverão estar em Meu Projeto\src, mas você pode, se quiser, criar pastas abaixo de Meu Projeto\src a fim de separar os códigos-fonte dos módulos
Se mais de um módulo utilizar um mesmo código-fonte .pas, crie a pasta Meu Projeto\lib e dentro desta coloque tais arquivos. Em seguida, em cada projeto, inclua no Search Path a pasta Meu Projeto\lib
Meu Pacote\dcu - Units compiladas. Configure esta pasta como Unit Output Directory, nas propriedades do projeto e inclua ela no Library Path do Delphi
Meu Pacote\dev - Arquivos de suporte relacionados ao pacote, mas que não são compiláveis
Meu Pacote\dev\doc - Arquivos de documentação a respeito do pacote
Meu Pacote\prj - Nesta pasta deve haver uma subpasta para cada versão de Delphi na qual este pacote é utilizável e dentro de cada uma destas subpasta deve ser colocado o arquivo de projeto do pacote específico daquela versão de Delphi (arquivo .dpk)
Meu Pacote\res - Arquivos de recurso que são utilizados pelo pacote na hora da compilação do mesmo. Podem ser arquivos .res, .dcr, imagens, ícones ou qualquer outro tipo de arquivo que é carregado pelo projeto do pacote e é incluído no binário finalizado (.bpl), isso inclui textos, scripts, etc. Você pode incluir esta pasta no Search Path do projeto do pacote a fim de facilitar a referência dos recursos. Ela também poderá ser incluída no Library Path dependendo da interpretação de seu conteúdo. Por favor, veja as observações mais adiante
Meu Pacote\res\dfm - Arquivos de formulário exclusivamente. Por favor, veja as observações a seguir
Meu Pacote\src - Arquivos de código-fonte .pas exclusivos deste pacote
Não configure em hipótese alguma a propriedade Output Directory do projeto do pacote. Verifique-a e mantenha-a em branco
Não configure em hipótese alguma a propriedade DCP Output Directory do projeto do pacote. Verifique-a e mantenha-a em branco
Na pasta Meu Pacote\res também podem ser incluídos arquivos de recurso que serão utilizados por outros projetos que utilizem o seu pacote. Para entender melhor, devemos considerar que existem dois pontos de vista: ao compilar um pacote e ao compilar um projeto que use seu pacote. Diante destes dois pontos de vista, devemos entender que:
Quando você está desenvolvendo e compilando o seu pacote, ele está em tempo de projeto, portanto arquivos existentes na pasta Meu Pacote\res poderão ser usados para a geração do BPL final e esta pasta deverá estar listada no Search Path do projeto do pacote
No momento em que você compila um pacote e ele é carregado pelo Delphi, ele se torna algo em execução, logo, seu pacote estará em tempo de execução e não mais em tempo de projeto
Ao compilar um projeto que use seu pacote, a pasta Meu Pacote\res, deverá estar listada no Library Path APENAS se ela contiver recursos que precisarão estar disponíveis para o projeto sendo compilado. Imagens e ícones são um bom exemplo disso
A pasta Meu Pacote\res\dfm tem uma função importante, mas negligenciada. Como se sabe, arquivos .dfm por padrão sempre são salvos juntamente com seu arquivo .pas correspondente. Suponha que seu pacote esteja sendo carregado pelo Delphi e suponha que você esteja desenvolvendo algo que use units de seu pacote. Suponha também que você esteja referenciando alguma unit que contenha um .dfm associado. Neste caso o .dfm precisará estar disponível para seu projeto sendo compilado e para que ele saiba onde estão estes arquivos .dfm eles precisam estar listados no Library Path obviamente, mas, você NÃO PODERÁ incluir este path no Library Path porque ele contém units não compiladas (arquivos .pas) e isso seria ERRADO. Existem duas formas de resolver este problema. A forma clássica é copiar os arquivos .dfm que estão na pasta Meu Pacote\src para a mesma pasta onde os arquivos .dcu são gerados, ou seja, Meu Pacote\dcu. Eu não gosto dessa forma porque eu convencionei que na pasta Meu Pacote\dcu devem existir apenas units compiladas (arquivos .dcu). Para resolver esse problema conceitual, já que arquivos .dfm são arquivos de recurso e não units compiladas, eu crio a pasta Meu Pacote\res\dfm, copio todos os arquivos .dfm que estão na pasta Meu Pacote\src para lá e em seguida eu adiciono Meu Pacote\res\dfm ao Library Path. Se eu criar um novo formulário eu preciso apenas me lembrar de copiar o arquivo .dfm para a pasta correta. Uma variação dessa forma, que exige um controle maior, é copiar APENAS os arquivos .dfm que efetivamente precisarão ser vistos fora do projeto do pacote. A desvantagem dessa forma de trabalho com arquivos .dfm é que toda vez que você alterar um formulário, precisará se lembrar de copiar o arquivo .dfm para a pasta Meu Pacote\res\dfm
Esta artigo ficou maior do que eu esperava, mas foi necessário devido a grande utilidade do seu conteúdo. Se você leu tudo com cuidado será capaz de entender e notar quando um componente ou pacote está mal elaborado quanto a distribuição de seus artefatos e poderá organizá-lo da forma correta a fim de evitar problemas futuros. Se você é um desenvolvedor de componentes e leu este artigo, não existem mais desculpas para que seus componentes sejam distribuídos de forma errada. Estejam avisados! Eu torço para que as informações compartilhadas aqui atinjam o maior número de pessoas que usam o Delphi. Nós que utilizamos esta ferramenta precisamos conhecê-la a fundo a fim de podermos tirar o máximo de proveito da mesma.
1 Dependendo da configuração da propriedade Lib Suffix o XXX pode ser substituído por uma string qualquer. Normalmente o Lib Suffix indica a versão do compilador. Por exemplo, se este pacote tivesse sido compilado no Delphi 2006, o nome do BPL gerado deveria ser dsnap100.bpl, pois a versão do compilador do Delphi 2006 é 10
2 Arquivos DCP, ao contrário dos arquivos BPL não recebem o Lib Suffix, o qual é usado apenas nas saídas principais do compilador, isto é, apenas arquivos .exe, .dll e .bpl utilizam o Lib Suffix para formar o nome do arquivo