Modularização de Aplicativos – Single Package
Quem desenvolve aplicativos modularizados, pode já ter se deparado com uma desvantagem comum quando se trata de distribuir pacotes de runtime: como controlar quais packages distribuir junto com o aplicativo/módulos e suas versões e facilitar suas atualizações?
Normalmente quando compilamos um aplicativo com a opção “Build with runtime packages” marcada, no mínimo teremos que redistribuir os pacotes rtlxx.bpl e vclxx.bpl (onde o “xx” equivale à versão do Delphi, pelo menos até o Delphi 7), mais os pacotes referenciados pelas units da vcl e/ou de terceiros.
Agora imagine um projeto grande, onde temos vários componentes de terceiros empregados. Quantos arquivos extras se tornariam dependências para o aplicativo?
Procurando uma maneira simples para minimizar o número de arquivos a serem distribuídos em conjunto com um aplicativo que use runtime packages, me deparo com o excelente tutorial de Keith Johnson, ao qual não posso acrescentar muita coisa, por já ser muito completo e didático. Contento-me a simplificá-lo e torná-lo útil para a realidade de muitos aqui.
Direto ao ponto
Figura 1 – Aplicativo com pacotes dinâmicos de forma convencional.
Figura 2 – Aplicativo com pacotes dinâmicos carregando de um único pacote.
Abra o Delphi, crie um novo aplicativo e salve o projeto. Adicione os componentes necessários de forma natural, como se fosse um projeto comum e salve.
Agora você pode fechar seu projeto e criar um novo package, ou criá-lo no mesmo grupo de projetos. Para simplificar, vou usar a segunda opção. Crie uma Unit vazia dentro deste novo package.
Você deverá percorrer TODOS os forms do seu projeto, vasculhando as cláusulas Uses para que todas as units referenciadas sejam adicionadas àquela unit do nosso pacote. Nosso objetivo será fazer com que o pacote importe todas essas units implicitamente para dentro dele, para que o executável tenha apenas ele como dependência. Para isso vá até Project -> Options e escolha a opção “Directories/Conditionals”, altere o “Output directory” e o “DCP directory” para uma pasta onde estará seu executável (o “DCP directory” é necessário na hora da compilação do executável, pois ele vai procurar pelo arquivo .dcp do pacote, mas em tempo de execução, ele só precisará do arquivo .bpl), ou onde ele tenha acesso pelo path do Windows.
É preferível deixar junto ao executável como numa pasta “bin” do seu projeto para facilitar a localização e distribuição, evitando um “bpl hell”. Clique em Ok e vá até aquela unit que você criou dentro do pacote. Adicione uma cláusula Uses a ela e coloque todas as units referenciadas pelo seu aplicativo.
No diretório de saída que você acabou de configurar, coloque uma cópia da SysInit.dcu, que se encontra em $(BDS)\lib (ex: C:\Arquivos de programas\CodeGear\RAD Studio\5.0\lib). Esta unit faz-se necessária, pois se você tentar compilar seu projeto sem ela, vai disparar uma exceção, informando que não foi possível encontrar SysInit.dcu (lembre-se: o search path que você configurou sobrepõe o default do Delphi, daí ele não achar). Mesmo assim, esta unit não deve ser adicionada na sessão uses da unit do pacote, deverá apenas permanecer lá na pasta de destino. Vá até a sessão “requires” do seu pacote lá no Project manager e elimine TODAS as referências a outros packages, deixando-a vazia.
Agora compile seu pacote. Irá aparecer uma mensagem, dizendo que você deve incluir os packages da lista para manter compatibilidade com outros pacotes já instalados.
É agora que vem o segredo: Escolha “Cancel” para que as units referenciadas sejam importadas para dentro de nosso pacote, e não as referências aos seus respectivos packages. Outro diálogo aparecerá com a mensagem: “If these changes are not applied, errors may occur when the package is loaded. Cancel changes?”.
Escolha “Yes” agora. Toda vez que esse pacote for recompilado você deverá seguir estes passos.
Se você verificar o resultado, poderá comprovar um package de até vários megabytes na pasta de destino (em um projeto que temos por aqui, está em torno de 13MB). Então, acabou? Ainda não, só mais uns ajustes no executável.
Considerando que você seguiu meu conselho e criou uma pasta só para os binários (“bin”, onde estarão .exe, .dcp, .bpl), selecione o projeto do executável agora, vá até Project -> Options e escolha a opção “Directories/Conditionals”, lá em “Search path”, coloque o caminho da pasta Bin do seu projeto e faça o mesmo para o “Output directory”.
Ainda nesta tela, escolha a opção “Packages”, marque a opção “Build with runtime pakages” e no Edit, limpe tudo, para adicionar apenas o nome do seu pacote.
Clique em Ok e dê um build no seu aplicativo. Com isso, você já poderá notar uma redução de até 90% no tamanho do executável.
Como já dito antes, para a distribuição do nosso aplicativo teste, apenas os 2 arquivos acima marcados serão necessários.
Conclusão
Quais as vantagens que podemos tirar deste método? Teremos todas as vantagens de se utilizar pacotes dinâmicos em um aplicativo, como velocidade na compilação (lembre-se que na maioria das vezes, você compilará apenas o código associado diretamente ao seu aplicativo, pois o código dos componentes já está embutido no pacote), executáveis e dlls muito menores (muito mais simples e rápido para atualizar seu aplicativo junto aos clientes) e maior facilidade para implementação de plugins, seja com dlls ou bpls. Se você colocar regras de negócio em um pacote ou fizer uso de rotinas muito sigilosas, poderá distribuir apenas os bpls do pacote livremente entre os desenvolvedores do projeto sem medo, pois estará fazendo uso do encapsulamento de código a seu favor.
E as desvantagens? Se você considerar todos os benefícios desta abordagem, o fato de ter que atualizar um pacote de 13MB (se compactar pode cair para 4MB ou 5MB) toda vez que mudar algum componente não pesará tão contra assim.
Este exemplo criou um único pacote com todas as units utilizadas pelo aplicativo, mas se você preferir poderá criar 2, 3 ou quantos pacotes precisar, separando-os por categoria, como pacotes de componentes visuais, acesso a dados, regras de negócio ou outra categoria que for necessária para seu projeto.
Este é o meu primeiro post, e espero que seja útil para quem precisar aplicar tais conceitos.
Se você é novo por aqui, não deixe de assinar o feed RSS ou notificações por email. Não perca novos artigos!
Em 05 de novembro de 2008 às 12:54
Wanderson, excelente artigo, muito boa a dica.
O artigo ficou muito bom, limpo, enxuto e conciso, na medida exata do que é necessário. Tu levas jeito para esse lance de escrever hein!!!
Parabéns cara.
Em 06 de novembro de 2008 às 00:41
Obrigado Acid, pode ter certeza de que vou trazer mais artigos assim (se o Leonel aprová-los…
)
Em 05 de novembro de 2008 às 14:39
Parabéns Wanderson.
Só acrescentaria mais detalhes sobre como selecionar os pacotes que ficarão externos no Runtime Packages pois você cita sobre a criação de mais de um pacote externo e pode ter gente não entendendo como adiciona-lo para ser externo.
Em 06 de novembro de 2008 às 00:26
Excelente observação. Inclusive pode ser até o caso de eu escrever um segundo artigo sobre o tema, abrangendo essa parte!
Em 05 de novembro de 2008 às 16:46
Parabéns pelo artigo. BPL é muito bom mas dá trabalho em usar.
Excelente didática no contéudo.
Escreva mais
Abraços
Luis Wagner
Em 06 de novembro de 2008 às 01:17
Exatamente Luis, lidar com BPLs é um pouco mais trabalhoso, mas pessoalmente eu gostei muito pela flexibilidade para se produzir qualquer aplicativo de médio a grande porte. E pode ter certeza, escreverei mais.
Em 06 de novembro de 2008 às 10:04
Parabéns pelo artigo.
Vou estar testando e utilizando em minhas aplicações..
Em 13 de novembro de 2008 às 12:27
Muito legal a dica. O toque do final é o que achei mais interessante: trabalhar com mais pacotes, utilizando essa mesma idéia e de uma maneira mais enxuta, comparada à forma tradicional de uso de packages.
Parabéns.
Em 25 de janeiro de 2009 às 14:44
Excelente o artigo!
Apesar de já usar esta técnica a alguns anos, desde o Delphi 7, para o pessoal que está iniciando, compensa (e muito) iniciar qualquer projeto já com as .dcu compiladas e um único .bpl.
Parabéns pela iniciativa em postar o tutorial!
Em 04 de março de 2009 às 00:49
Boa Noite, já faz algum tempo que estou procurando algum tutorial simples e claro que me ajude a modularizar um sistema que estou desenvolvendo (seja em bpl’s ou dll’s). Como modularização é o tema desse tutorial, gostaria de saber se vcs conhecem algum tutorial que possa me ajudar. Vale ressaltar que o aplicativo já está com um tamanho considerável. Estou usando Firebird + dbexpress !
Agradeço antecipadamente.
Em 06 de março de 2009 às 11:14
Ola amigo, muito bom mesmo seu artigo. A empresa em que trabalho possui um software baseado em Runtime Packages. Estou reformulando a estrutura de pacotes e componentes proprios para utilizar o “pacotao” porem, trabalhando com componentes eu nao consigo instala-los. O processo eh o seguinte, tenho um pacotao com todas as uses necessarias. Faço referencia a este pacotao atraves do “requires” no pacote do componente que desejo instalar. Ele compila sem problemas, mas nao consigo instala-lo. Ocorre o seguinte erro: Registration procedure JBstandardReg.Register in package “C:\…..” raised exception class EComponentError .
Por acaso o amigo teve tal problema ou sabe como resolver?
Grato!
Em 19 de abril de 2009 às 22:08
Engraçado… não acho DcpOutput no delphi 2009
Em 20 de abril de 2009 às 09:33
André, desculpe mas este artigo cobre apenas até o Delphi 2007, pois é a versão que utilizei. Vou procurar algo relativo a isso, talvez renda uma atualização!
Em 24 de abril de 2009 às 11:25
Wanderson, tenho uma dúvida referente a package que acho que seria interessante um artigo, a duvida seria assim:
criar um package com um DataModule, onde iria todas as conecções de banco de dados do sistema, e usá-los em todos os cadastros, sendo cada cadastro um package.
você pode me ajudar nisso?
Em 24 de abril de 2009 às 12:20
Flavio, para tal, precisaria de maiores detalhes sobre como e o quê você pretende fazer. Entre em contato comigo pelo email wanderson.fs@gmail.com.
Em 07 de julho de 2009 às 16:27
Parabens pelo artigo Wanderson, muito bom mesmo. Agora tenho uma dúvida: no que se refere a desempenho da aplicação muda alguma coisa ?
No meu projeto ‘convencional’ eu tenho vários forms e creio eu que apenas quando eu crio o form, aquela biblioteca que está no uses dele é carregada em memória, nesse caso ai seria tudo carregado na inicialização da aplicação ?
A proposta de modularização é ótima, faz tempo que procuro algo assim, preciso só ter certeza desses detalhes para adotar esse novo modelo.
Em 07 de julho de 2009 às 16:09
Ótimo artigo, estamos precisando de gente que escreva artigos práticos e úteis como esse.
Abs.
Em 29 de julho de 2009 às 13:39
Olá Pessoal,
Eu fiz exatamente como manda no artigo, mas meu executavel não diminui de tamanho!! Arfh… alguem pode me ajudar, o que poderia fazer?
Em 12 de setembro de 2009 às 18:47
Boa Tarde…
Estou tento um erro no delphi 7 “out of memory”, já atualizei a rlink32.dll e nada e o projeto é grande, poderia me informar como faço usando o delphi 7 para criar modularização, inclusive utilizo varias componentes de terceiros. Aguardo resp.
Em 27 de setembro de 2009 às 21:28
Comentando …
Estou reescrevendo meu projeto, seguindo sua dica, de modularização.
No entanto, algo estranho ocorre. Me são gerados os seguintes arquivos
+BIN
p_MDI.exe 14kb
Funcoes.bpl 3.655kb
Estados.bpl 3.656kb
+DCPs
Funcoes.dcp 8.227kb
Estados.dcp 8.227kb
+DCUs
todos .dcu dos .pas que envolvem meu projeto.
Como era de se esperar , se eu mover apenas o pequenino executavel para uma outra pasta,
e tentar executar, me é pedido a BPL de funções, que é onde tenho uma unit com os USES
que usarei;
O estranho é que depois de copiar o Funcoes.bpl para a pasta do executavel, o sistema roda
e abre a tela de Cadastro de Estados, que está no pacote (bpl/dcp) Estados.bpl.
Ou seja, não exige a BPL de Estados para abrir a tela de Estados.
Será que fiz algo errado ?
Aqui vai um screenshot do meu projeto, abrindo a tela de estados executando com apenas a
BPL de Funcoes no mesmo dir do executavel :
http://picasaweb.google.com.br/duardbr/ProjetoEmDelphi#5386250953316710178
Gostaria que desse erro ao chamar estados, visto que não coloquei a BPL de estados no
diretorio do executavel.
Gostaria de separar cada um dos meus cadastros em uma BPL, em alguns casos uma BPL
poderia conter mais de uma tela e tal. E que só fosse conseguida a abertura dessas
telas, caso a BPL com a tela estivesse disponibilizada.
No entanto, não está funcionando assim.
Quem puder ajudar, obrigado.
Em 28 de setembro de 2009 às 10:29
duard, na configuração do build do seu executável, você deve verificar se está colocando Estados.bpl no runtime packages (na seção build with runtime packages, junto com a Funcoes.bpl).
Em 28 de setembro de 2009 às 10:01
Wanderson,
Tentei colocar, no entanto ocorre um erro ao dar build no sistema, que se não me engano é o tal BPL HELL onde o delphi diz que uma BPL já chama um USES e tal.
Por mais que eu limpe as coisas, fica nesse tal BPL Hell.
Aproveitando, poderia disponibilizar por exempli, a unit do pacotão com tudo que é uses ? Facilitaria e muito.
Em 28 de setembro de 2009 às 11:07
Aqui outro screenshot para tentar ilustrar melhor meu problema quando adiciono Funcoes;Estados nos Build Run Time Packages :
http://picasaweb.google.com.br/duardbr/ProjetoEmDelphi#5386503337940453058
ps. u_funcoes_gerais, é apenas uma unit onde adicionei tudo que é unit que vi pela frente, sendo estas units as do Delphi, tipo Windows, Classes, Messages … etc.
Em 28 de setembro de 2009 às 11:34
Aqui outro screnshot exibindo minha unit com tudo que é USES que consegui colocar e o erro que dá. São várias linhas de erro.
http://picasaweb.google.com.br/duardbr/ProjetoEmDelphi#5386505768062036466
Em 17 de fevereiro de 2010 às 22:55
Wanderson,
Ótimo tutorial quero lhe dar os parabéns.
Ótima alternativa de modularização, porém só vi uma unica desvantagem:
O consumo de memoria fica consideravelmente maior se tiver várias aplicações
abertas que estão utilizando o “pacotao” do que se a vcl estivesse compilada direto
na aplicação.
Gostaria de saber se vc sabe uma solução que eu consiga colocar vários pacotes
dentro de um só mas consumir a mesma memória ou próximo do que consumiria
uma aplicação normal, sem esta técnica.
Aguardo resposta,
Obrigado.