Pacotes: Teoria e Prática - Parte I
Conhecendo seus benefícios e aplicações

 

Introdução

 

O constante crescimento das transações devido a demanda cada vez maior de solicitações e usuários cada vez mais exigentes, aponta para um inevitável crescimento no tamanho e complexidade dos softwares, sejam eles aplicações de automação comercial ou um ERP contemplando todos os processos em uma organização.
            Outro aspecto relevante para o aumento no tamanho de um software, é a não adoção de metodologias de desenvolvimento, o que acaba por gerar códigos redundantes, retrabalho e até mesmo “remendos” no software os quais somados, vão acrescentando vários megabytes inchando seu executável final e, com isso dificultando o processo de distribuição e atualização do mesmo.
            Como se tudo isso não bastasse, um software mal estruturado e consequentemente “inchado”  ou mesmo, um executável grande devido ao tamanho e complexidade da aplicação, esbarramos em limitações por parte do sistema operacional, onde o mal gerenciamento de memória acaba por impedir que nosso software venha se quer a carregar, problema este freqüente no Windows 98 e com menos freqüência no Windows 2000 e XP, porém, não estando estes sistemas operacionais livre deste problema.
            Dentro deste contexto, o Delphi a partir da versão 3 adicionou suporte a pacotes (packages)  que nos possibilitam modularizar aplicações Delphi e C++Builder.

 

Pacotes ou DLLs?

Pensando apenas na modularização, poderíamos vir a utilizar DLLs as quais são universalmente utilizadas por todas as linguagens de programação e permitem compartilhar código entre elas. Um exemplo clássico são as diversas empresas de impressoras fiscais.

Geralmente, elas desenvolvem e distribuem uma DLL contendo todas as funções necessárias para comunicação com seu periférico, bastando ao programador de automação comercial declarar estas funções em sua aplicação e fazer uso sem necessitar tomar conhecimento em qual linguagem a DLL foi escrita. Assim sendo, particularmente recomendo a utilização de DLLs somente quando for necessário disponibilizar funções e funcionalidades para outras linguagens de programação, visto que a criação e manipulação de DLLs exige um bom conhecimento na tipagem e gerenciamento de memória impostos pelo Windows.
            Veja algumas das vantagens em utilizar pacotes:

  • Reduzir o tamanho dos executáveis;

  • Dividir a aplicação em módulos. Modularizando a aplicação em pacotes, obteremos uma série de vantagens, dentre elas, compartilhar código entre vários projetos, gerenciar funcionalidades do sistema, melhorar a distribuição e a atualização para correção de erros;

  • Instalação de componentes facilitada. Se o objetivo for desenvolver um conjunto de componentes para instalar no Delphi, também utilizaremos para fazer a distribuição dos componentes, facilitando a instalação na máquina do desenvolvedor.

 

Observe que as duas primeiras vantagens também são fornecidas pelas DLLs. Mas, os pacotes oferecem maior integração ao ambiente de desenvolvimento do Delphi. Outra importante vantagem é a possibilidade de utilizar orientação a objetos, ou seja, podemos instanciar uma classe declarada dentro de um pacote. Assim, quando as aplicações desenvolvidas forem exclusivamente em Delphi (ou C++Builder), há grandes ganhos na utilização de pacotes.

 

Uma visão geral dos pacotes run-time

Os pacotes run-time são como DLLs, uma coleção de dados e códigos que formam uma parte da aplicação, porém não está integrado ao executável da aplicação. Uma vantagem de usar os pacotes run-time é que você pode atualizar os pacotes sem atualizar o arquivo executável. Você pode fornecer aos usuários pequenos pacotes sem enviar a aplicação inteira. O arquivo executável é menor, porque grande parte do código está guardado em pacotes externos. 

Outra vantagem dos pacotes run-time é que você pode compartilhá-los com outras aplicações, economizando espaço em disco. Um exemplo de como você pode usar isso, considere o que acontece quando você distribui vários executáveis sem os pacotes externos. Cada arquivo executável contém a VCL. Se você encontrar uma maneira de colocar a VCL em um pacote run-time, os arquivos executáveis serão muito pequenos, porque todo o código da VCL estará compartilhado em um único pacote run-time. 

Reconhecendo isto, a Borland vende o Delphi com vários pacotes run-time prontos para a VCL, o que permite que você construa suas aplicações usando-os. Para conferir, acesse o menu  Project | Options e selecione a aba Packages (veja figura abaixo). Você verá um checkbox chamado “Build with runtime packages”. Se você selecionar esta opção, poderá selecionar quais os pacotes deseja usar.

 


A aba Packages do dialog box Project Options.

Você encontrará estes pacotes no diretório \Delphi\Lib, estes são os pacotes run-time fornecidos pela Borland, cada um dos quais contém uma porção do VCL. Você pode optar por linkar todos estes pacotes run-time, ou apenas escolher alguns deles.

Se você fizer uma aplicação exemplo usando os pacotes run-time contendo a VCL, o tamanho de seu executável deve ficar próximo de 14 Kb, comparado aos 287 Kb se você não usar os pacotes run-time. É claro, você ainda deve continuar enviando os pacotes run-time com sua aplicação, o qual tem aproximadamente 2Mb. Então o que você ganhou? Bem, agora você pode efetuar alterações apenas em um pequeno executável e distribuí-lo, sem a necessidade de enviar novamente o pacote, pois este não irá mudar e já foi distribuído anteriormente.

Você pode, é claro, criar seus próprios pacotes run-time, mas primeiro vamos conhecer as extensões de arquivos usadas quando trabalhamos com pacotes, veja o quadro abaixo.


DPK

Código fonte do pacote

DCP

Uma imagem binária contendo o cabeçalho do pacote e a concatenação de todos os arquivos DCU do pacote. Um arquivo único DCP é criado para cada pacote. O nome base para o DCP é o nome base do arquivo fonte DPK. Isto é necessário para linkar os pacotes run-time. Fazendo uma analogia, um DCP está para um pacote, assim como um DCU está para uma unit.

DCU

Uma imagem binária para um arquivo único contendo um pacote. Um DCU é criado, quando necessário, para cada arquivo (unit).

BPL

O pacote run-time. Nada mais é que uma DLL Windows , porém com características especiais do Delphi. O nome base para a BPL é o nome base para o arquivo fonte DPK. Este é o arquivo que você precisa enviar junto com sua aplicação.

 

Extensões de arquivos usadas pelos pacotes.

 

Opções de configuração em pacotes.

Veremos a seguir algumas opções de ajustes disponíveis quando criamos um pacote. A figura a seguir apresenta um pacote recém criado no Delphi.


Opções do pacote.

 

Contains

Contém as units que integram o pacote

Requires

Indica os pacotes externos requeridos para execução

Options

Opções de compilação e diretórios. Semelhante a opção Project | Options em um projeto, veja imagem a seguir.

 

Opções do pacote.

 


“Options” do pacote.

 


Description

Apresenta descrição do pacote quando visualizado na aba Packages.

Usage options

Designtime only

Indica que o pacote poderá ser instalado na IDE do Delphi, ou seja, adicionado a palheta de componentes.

Runtimeonly

Indica que o pacote será utilizado apenas em run-time e seu conteúdo será utilizado para rodar outras aplicações ou pacotes.

Designtime and Runtime

Contempla as duas funcionalidades anteriores.

Build control

Rebuild as needed

Esta opção indica que o pacote será compilado automaticamente sempre que necessário. Essa opção geralmente é utilizada em pacotes de componentes que são utilizados por outros pacotes, pois sempre que um pacote necessitar que o outro seja recompilado, isso será feito automaticamente sem intervenção do programador.

Explicit Rebuild

Esta opção geralmente é utilizada em nossos pacotes de run-time os quais o programador efetuar a compilação no momento que achar necessário.

 

“Options” do pacote.

Outras opções relevantes em relação ao tratamento dos pacotes estão na aba Directories/Conditionals, confira a seguir.


Directories/Conditionals

Output directory

Indica em qual pasta será gerado o arquivo BPL.

Unit output directory

Indica em qual pasta serão gerados os arquivos DCUs referente as units contidas no pacote.

Search Path

Path úteis durante a compilação do pacote.

Debug source path

Indica onde de depurador do Delphi deverá buscar os arquivos durante uma sessão debug.

DCP output directory   

Indica em qual pasta será gerado o arquivo DCP referente a compilação do projeto.

Durante o decorrer deste nosso workshop iremos utilizar estas opções nos exemplos que serão implementados para mostrar a funcionalidade dos pacotes.

 

Tipos de pacotes: Dinâmicos e Estáticos

O Delphi nos oferece a possibilidade de trabalhar com pacotes estáticos ou dinâmicos. Ambas abordagens oferecem ótimos resultados à sua aplicação e caberá a você decidir qual abordagem se encaixará melhor às suas necessidades. Neste tópico, iremos conhecer como funcionam estas duas abordagens e dessa forma facilitar a sua escolha.
            Como dito anteriormente, um pacote nada mais é que uma biblioteca especial de link dinâmico usada por aplicações Delphi e C++Builder. Estes pacotes podem conter units com código, componentes, formulários e é claro, tudo que envolve a programação orientada a objetos oferecida pelo Delphi.

 

 

Pacotes Estáticos

O trabalho com pacotes estáticos é bem mais simples em relação aos dinâmicos e não requer alterações significativas em projetos que já estão em produção. Basicamente, quando você quebra sua aplicação em pacotes estáticos, irá separar fisicamente porções (units, formulários, rotinas) em um pacote a parte de sua aplicação, porém ao informar o nome do referido pacote nas configurações do projeto (Project | Options | Packages | Run-Time Packages) o aplicativo irá automaticamente buscar uma unit ou formulário dentro do referido pacote quando esta não estiver presente na aplicação. Por exemplo, se você possuir na cláusula uses de sua unit principal (MainForm) referência a unit unCadClientes a qual contém seu cadastro de clientes, no momento que for instanciar tal formulário e o mesmo não for encontrado na aplicação, a carga será feita a partir do pacote estático de forma totalmente transparente.
            A primeira vista, a utilização de pacotes estáticos traz muitas vantagens, das quais destaco:

  • Facilidade para adequar/quebrar aplicações já existentes e resolver de forma simples problemas como falta de recursos;

  • Facilidade na atualização das aplicações, sendo possível distribuir apenas os pacotes referentes aos módulos alterados;

  • Redução do tempo de carga da aplicação em pelo menos 50%;

  • Em ambiente de desenvolvimento, você poderá deixar a opção “Build with runtime packages” desmarcada e dessa forma, compilar, testar e depurar a aplicação como se não estivesse utilizando pacotes e, quando for gerar o build para distribuição, compilar/gerar os pacotes, marcar esta opção e gerar o EXE pequeno;

Temos  também algumas desvantagens em relação a utilização de pacotes dinâmicos, das quais destaco:

  • Todos os pacotes (BPLs) devem obrigatoriamente disponíveis em uma path acessível pelo EXE, pois do contrário, no momento da carga da aplicação uma exceção será gerada;

  • Caso comercialize sua aplicação em módulos separados, deverá distribuir todos os pacotes (BPLs) mesmo dos módulos que não estarão disponíveis ao usuário e efetuar o controle via programação permitindo ou não acesso aos mesmos;

  • O gerenciamento de memória (carregar e descarregar) é feito automaticamente pelo sistema (isso pode ser bom ou pode ser ruim, depende da sua experiência em lidar com o assunto);

 

 

Laboratório 1

Vamos agora partir para nosso primeiro projeto de exemplo e demonstrar na prática como funcionam os pacotes estáticos. Abra o Delphi, vá ao menu File | New | Application e veja na figura abaixo o layout sugerido.

 

Layout sugerido.

 

Nomeie o projeto como fmPrincipal e salve o projeto como UsaPackages e a unit como unPrincipal. Depois, dê um duplo clique no ActionList1 e adicione as opções apresentadas na figura a seguinte.


Opções do ActionList.

 

Dentro da pasta onde salvou o projeto, adicione três novas pastas sendo: RtmPackClientes, RtmPackDM e RtmPackProdutos, as quais iremos utilizar mais adiante para armazenar os pacotes. Prosseguindo, adicione um DataModule (menu File | New | Data Module), nomeie como DM e salve a unit unDM e nele adicione dois componentes TTable configurando como demonstra o quadro a seguir.


tabCustomer: TTable

DatabaseName

DBDemos

TableName

Customer

Active

True

tabParts: TTable

DatabaseName

DBDemos

TableName

Parts

Active

True

Tabelas no DM.

Adicione dois novos formulários ao nosso projeto nomeando como fmClientes e fmProdutos e salve as units como unClientes e unProdutos respectivamente. Na cláusula uses de ambos, adicione referência ao DM declarando a unDM. A figura a seguir apresenta o layout e componentes necessários para estes dois formulários.

 


fmClientes.

fmProdutos.

 

Acesse o menu Project | Options e mova o fmClientes e o fmProdutos para a lista Available Forms, como apresenta a figura abaixo.


Opções do projeto.

 

Volte ao formulário principal (fmPrincipal), dê um duplo clique no ActionList1 e no evento OnExecute da acClientes adicione o seguinte código:

procedure TfmPrincipal.acClientesExecute(Sender: TObject);
begin
  if fmClientes = nil then
    Application.CreateForm(tfmClientes, fmClientes);
  try
    fmClientes.ShowModal;
  finally
    FreeAndNil(fmClientes);
  end;
end;
OnExecute da acClientes.

 

Faça o mesmo para a acProdutos adicionando o seguinte código:

procedure TfmPrincipal.acProdutosExecute(Sender: TObject);
begin
  if fmProdutos = nil then
    Application.CreateForm(tfmProdutos, fmProdutos);
  try
    fmProdutos.ShowModal;
  finally
    FreeAndNil(fmProdutos);
  end;
end;
OnExecute da acProdutos.

Obs: Lembre-se de declarar na cláusula uses da unPrincipal as units unClientes e unProdutos.

Feito isso, compile o projeto. Até aqui, nada diferente daquilo que você está acostumado a trabalhar até hoje. Verifique na pasta onde salvou o projeto que o executável gerado deve ter aproximadamente 870KB.

 

Retirando a VLC do projeto

Vamos agora apenas retirar a parafernália embutida pela VCL em nosso executável e novamente compilar nosso projeto. Para retirar a VCL, acesse o menu Project | Options | Packages e marque a opção “Build with runtime packages” conforme sugere a figura a seguir.

 

Opções do projeto.

 

Confirme com OK e compile o projeto. Bem, sem nenhum esforço no executável teve uma redução dos aproximados 870KB para aproximadamente 30KB. Isso em muitos casos resolve aquele velho problema de falta de recursos principalmente no Windows 98 (Out Of Resources), contudo, isso irá depender muito do tamanho e complexidade de sua aplicação.

Obs: Lembre-se que para distribuir uma aplicação que esteja compilada desta forma, será necessários distribuir os pacotes referentes a VCL conforme mencionado no tópico “Uma visão geral dois pacotes run-time”.

 

Moduralizando a aplicação em pacotes estáticos.

O primeiro passo já foi realizado quando marcamos a opção “Build with runtime packages”. O próximo será criarmos três pacotes sendo um para abrigar o DM, outro para o fmClientes e finalmente outro para o fbProdutos.
            Para facilitar vamos trabalhar com o “Project Manager” do Delphi. Estando com nosso projeto aberto na IDE, acesse o menu View | Project Manager ou simplesmente tecle a combinação CTRL+ALT+F11 e terá o manipulador de projetos conforme apresenta a figura a seguir.

 

Project Manager.

 

Clique da direita em Project Group e escolha a opção “Save Project Group”, mantendo o nome padrão e salvando-o na mesma pasta do projeto. Clique em New para criarmos nosso primeiro pacote selecionando a opção “Package” em “New Items”, veja a figura a seguir.

 


Project Manager.

 

Após selecionar e confirmar esta opção teremos um novo pacote em nosso manipulador de projetos conforme apresenta a próxima figura.


Novo pacote.

Salve este pacote na pasta RtmPackDM com o nome RtmPackDM.dpk. Antes de trabalharmos neste pacote, vamos criar os outros dois restantes, sendo RtmPackClientes.dpk (na pasta RtmPackClientes) e RtmPackProdutos.dpk (na pasta RtmPackProdutos). Após adicionar e salvar os três pacotes, nosso manipulador de projetos irá conter o projeto e os três novos pacotes, confira na figura a seguir.

 


Projeto e pacotes.

 

Algumas literaturas sugerem remover do projeto (Remove file from project) as units que serão adicionadas em pacotes. Particularmente, não acho necessário, pois uma das facilidades em trabalhar com pacotes estáticos é exatamente você continuar dando manutenção ao projeto de forma transparente e somente quando for gerar o build para distribuição, compilar e linkar os pacotes ao projeto.

Obs. Configure nas opções do pacote em Directories/Conditionals os parâmetros “Output directory” e “DCP output directory” para “..\”, com isso, os DCPs e BPLs serão gerados na mesma pasta onde encontra-se o executável de nosso projeto exemplo.

Dando continuidade, vamos adicionar as units a cada pacote respectivamente. Iremos começar pelo RtmPackDM.dpk no qual iremos adicionar nosso DataModule (unDM). Clique no botão “Add” do RtmPackDM.dpk e selecione a unDM e confirme, ficando nosso pacote como apresenta a figura a seguir.

 

RtmPackDM

Repita o mesmo procedimento para RtmPackClientes.dpk e RtmPackProdutos.dpk. Após adicionar cada unit ao seu respectivo pacote, compile primeiro o RtmPackDM clicando no botão “Compile”. Aqui irá entrar uma regrinha básica quando trabalhamos com pacotes: Analisar a interdependência entre os módulos/units. Observe que tanto a unClientes, quando a unProdutos fazem referência ao DataModule (unDM), contudo, não podemos e nem devemos adicionar a unDM em todos os pacotes, primeiro porque isso seria redundante e segundo porque o próprio Delphi irá identificar que uma mesma unit está adicionada em diferentes pacotes e gerará uma exceção a este respeito. Então, o que fazer?

 

Adicionando dependência entre pacotes

Você deve estar lembrando que o grupo Requires indica os pacotes externos requeridos para execução, ou seja, informamos ao pacote atual onde ele deverá buscar as referências que não encontrar nele próprio. Outro detalhe que você deve estar lembrado é que ao compilar um pacote, além do arquivo BPL geramos também um arquivo DCP que é uma imagem binária contendo o cabeçalho do pacote e a concatenação de todos os arquivos DCU do pacote. Um arquivo único DCP é criado para cada pacote. O nome base para o DCP é o nome base do arquivo fonte DPK. Isto é necessário para linkar os pacotes run-time. Fazendo uma analogia, um DCP está para um pacote, assim como um DCU está para uma unit.
            Bem, neste momento você já deve ter identificado qual será o “pulo do gato” para que os pacotes RtmPackClientes e RtmPackProdutos acessem o DataModule. Selecione o item Requires no RtmPackClientes.dpk, clique em “Add” e vá na pasta principal de nosso projeto onde encontrará o arquivo RtmPackDM.dcp, bastando selecioná-lo e adicioná-lo. Repita o mesmo procedimento para o RtmPackProdutos.dpk. Feito isso, compile estes pacotes e com isso teremos seus respectivos DCPs e BPLs gerados na pasta do projeto.

 

Linkando os pacotes ao projeto

Efetuaremos agora o link estático dos pacotes ao nosso projeto. Para isso, está com o projeto ativo, acesse o menu Project | Options | Packages, apague todo o conteúdo da caixa de texto referente “Build with runtime packages”, clique em “Add” e selecione os três pacotes anteriormente gerados, lembrando que o link será feito através dos arquivos DCPs, confira a figura a seguir.


Pacotes adicionados ao projeto.

 

Confirme em OK e compile o projeto. Por curiosidade, vá a pasta onde o executável foi gerado e verá que temos um EXE ainda menor, apenas com 22KB! Rode a aplicação, chame os formulários e veja que tudo continua transparente, como se não estivéssemos utilizando pacotes de run-time.

Obs: Quer ter certeza de que os pacotes estão sendo utilizandos? Vá à pasta onde estâo o executável e as BPLs e renomeie a RtmPackDM.bpl para RtmPackDM.old, por exemplo e rode a aplicação. Provavelmente a aplicação não irá carregar e receberá uma exceção semelhante a apresentada na figura a seguir.

 

Erro quando a aplicação não encontra uma BPL requerida.

 

Podemos destacar isso como uma desvantagem da linkagem estática de pacotes, pois apesar dos módulos estarem fisicamente separados, existe a dependência e todos os módulos devem estar acessíveis no momento da carga da aplicação. Isso também ocorre quando trabalhamos com DLLs e efetuamos a declaração das funções da mesma em nossa unit e, se a DLL não estiver presente no momento da carga da aplicação o mesmo tipo de exceção será levantada.

 

Facilidade de manutenção no projeto

Supondo que você necessite efetuar alterações em alguma das units, testar para ver se a alteração ficou satisfatória, depurar para encontrar possíveis erros, trabalhar com pacotes neste contexto seria um inconveniente, pois, você teria que compilar o pacote, compilar o projeto e depois executar seus testes.
            Uma vantagem dos pacotes estáticos já citada anteriormente é que a qualquer momento você pode voltar a sua aplicação para um único módulo, ou seja, sem a dependência dos pacotes. Para isso, bastará acessar o menu Project | Options | Packages e desmarcar a opção “Build with runtime packages” e confirmar em OK. Agora, ao compilar sua aplicação verá que o executável voltou a ter os mesmos aproximados 870KB e, poderá alterar, rodar, depurar, enfim, fazer o que for necessário em seu projeto sem necessitar recompilar seu pacote. Quando terminar a manutenção no projeto, marque novamente a opção “Build with runtime packages”, abra somente os pacotes referentes as units alteradas, compile e bastará atualizar os pacotes alterados (BPLs) em seu cliente para que as alterações sejam efetuadas.

 

Considerações Finais

Com isso encerramos nosso primeiro laboratório demonstrando como criar uma aplicação e modularizá-la em pacotes estáticos. Na próxima edição estarei abordando a utilização de pacotes dinâmicos e como eles podem ser úteis em sua aplicação.
            Abraço à todos e até a próxima.

 

Sobre o Autor

Alessandro Ferreira, Analista de Sistemas Sênior do Citibank Capital Markets Brazil onde atualmente trabalha com Microsoft .NET e Oracle. Bacharel em Administração de Sistemas de Informação e Microsoft Certified Professional (MCAD.NET, MCSD.NET, MCTS e MCPD), trabalhou com Delphi por 10 anos sendo 7 anos dedicados ao suporte técnico aqui no The Club, auxiliando centenas de programadores pelo Brasil.

E-mail: suporte@theclub.com.br

The Club - O Maior Clube de programadores do Brasil