Por que usar pacotes?
A resposta é simples: por causa do poder do pacote. Os pacotes de tempo de design simplificam o lançamento e a instalação de componentes personalizados; os pacotes de tempo de execução injetam novo poder na programação tradicional. Depois de compilar o código reutilizável em uma biblioteca de tempo de execução, você poderá compartilhá-lo entre vários aplicativos. Todas as aplicações podem acessar componentes padrão através de pacotes, e o próprio Delphi faz isso. Como o aplicativo não precisa copiar uma biblioteca de componentes separada no arquivo executável, isso economiza bastante recursos do sistema e espaço em disco. Além disso, os pacotes reduzem o tempo gasto na compilação porque você só precisa compilar o código específico do aplicativo.
Se os pacotes puderem ser usados dinamicamente, poderemos obter mais benefícios. Os pacotes fornecem uma nova abordagem modular para o desenvolvimento de aplicativos. Às vezes, você pode querer tornar determinados módulos componentes opcionais do aplicativo, como um sistema de contabilidade que vem com um módulo opcional de RH. Em alguns casos, você só precisa instalar o aplicativo básico, enquanto em outros casos pode ser necessário instalar módulos de RH adicionais. Esta abordagem modular pode ser facilmente implementada através da tecnologia de pacotes. No passado, isso só poderia ser conseguido carregando dinamicamente uma DLL, mas usando a tecnologia de empacotamento do Delphi, você pode "empacotar" cada tipo de módulo do aplicativo em pacotes. Em particular, os objetos de classe criados a partir de pacotes são de propriedade da aplicação e podem, portanto, interagir com objetos na aplicação.
Pacotes e aplicativos de tempo de execução
Muitos desenvolvedores só pensam nos pacotes Delphi como um local para colocar componentes, quando na verdade os pacotes podem (e devem) ser usados no design modular de aplicações.
Para demonstrar como usar pacotes para modularizar sua aplicação, vamos criar um exemplo:
1. Crie um novo programa Delphi com dois formulários: Form1 e Form2;
2. Remova o Form2 da lista de formulários criados automaticamente (PRoject | Options | Forms);
3. Coloque um botão no Form1 e insira o seguinte código no manipulador de eventos OnClick do botão:
com TForm2.Create(aplicativo) faça
começar
ShowModal;
Livre;
Fim;
4. Lembre-se de adicionar Unit2 à cláusula uses de Unit1;
5. Salve e execute o projeto.
Criamos uma aplicação simples que exibe um formulário com um botão que, ao ser clicado, cria e exibe outro formulário.
Mas o que devemos fazer se quisermos incluir o Form2 do exemplo acima em um módulo reutilizável e fazê-lo funcionar normalmente?
A resposta é: Bao!
Criar um pacote para Form2 requer o seguinte trabalho:
1. Abra o Gerenciador de Projetos (Visualizar | Gerenciador de Projetos);
2. Clique com o botão direito no Grupo de Projetos e selecione "Adicionar Novo Projeto...";
3. Selecione “Pacote” na lista de projetos “Novo”;
4. Agora você deverá conseguir ver o editor de pacotes;
5. Selecione o item “Contém” e clique no botão “Adicionar”;
6. Em seguida clique no botão "Navegar..." e selecione "Unit2.pas";
7. O pacote agora deverá conter a unidade "Unit2.pas";
8. Finalmente salve e compile o pacote.
Agora completamos o pacote. Deve haver um arquivo chamado "package1.bpl" em seu diretório Project/BPL. (BPL é a abreviatura de Borland Package Library e DCP é a abreviatura de Delphi CompiledPackage.)
Este pacote está completo. Agora precisamos ativar a opção de opções do pacote
e recompile o aplicativo original.
1. Clique duas vezes em "Project1.exe" no gerenciador de projetos para selecionar o projeto;
2. Clique com o botão direito e selecione "Opções..." (você também pode selecionar Projeto | Opções... no menu);
3. Selecione a página de opção “Pacotes”;
4. Marque a caixa de seleção "Construir com pacotes de tempo de execução";
5. Edite a caixa de edição "Pacotes de tempo de execução": "Vcl50;Package1" e clique no botão "OK";
6. Nota: Não remova Unit2 do aplicativo;
7. Salve e execute o aplicativo.
O aplicativo funcionará como antes, mas a diferença pode ser vista no tamanho do arquivo.
Project1.exe agora tem apenas 14K de tamanho, em comparação com 293K anteriormente. Se você usar o Navegador de recursos para visualizar o conteúdo dos arquivos EXE e BPL, verá que o Form2 DFM e o código agora estão salvos no pacote.
Delphi conclui a vinculação estática de pacotes durante a compilação. (É por isso que você não pode remover Unit2 do projeto EXE.)
Pense no que você pode ganhar com isso: você pode criar um módulo de acesso a dados no pacote e, ao alterar as regras de acesso a dados (como mudar de conexões BDE para conexões ADO), modificar ligeiramente e republicar o pacote. Alternativamente, você pode criar um formulário em um pacote que exiba a mensagem "Esta opção não está disponível na versão atual" e então criar um formulário totalmente funcional em outro pacote com o mesmo nome. Agora temos o produto nas versões “Pro” e “Enterprise” sem nenhum esforço.
Carregamento e descarregamento dinâmico de pacotes
Na maioria dos casos, uma DLL ou BPL vinculada estaticamente será suficiente. Mas e se não quisermos lançar o BPL? "A biblioteca de vínculo dinâmico Package1.bpl não pode ser encontrada no diretório especificado" é a única mensagem que podemos receber antes do término do aplicativo. Ou, em uma aplicação modular, podemos usar qualquer número de plugins?
Precisamos nos conectar dinamicamente ao BPL em tempo de execução.
Para DLLs, existe um método simples, que é usar a função LoadLibrary:
função LoadLibrary(lpLibFileName: Pchar): HMODULE;stdcall;
Depois de carregar a DLL, podemos usar a função GetProcAddress para chamar as funções e métodos exportados da DLL:
função GetProcAddress(hModule: HMODULE; lpProcName:LPCSTR): FARPROC;
Finalmente, usamos FreeLibrary para desinstalar a DLL:
função FreeLibrary(hLibModule: HMODULE): BOOL;stdcall;
No exemplo a seguir carregamos dinamicamente a biblioteca HtmlHelp da Microsoft:
função TForm1.ApplicationEvents1Help(Comando: Word; Dados: Inteiro; var CallHelp: Boolean):Boolean;
tipo
TFNHtmlHelpA = função (hwndCaller: HWND; pszFile: PansiChar; uCommand: UINT;dwData: Dword): HWND;
var
Módulo de Ajuda: Hmodule;
HtmlHelp: TFNHtmlHelpA;
começar
Resultado := Falso;
HelpModule := LoadLibrary('HHCTRL.OCX');
se HelpModule <> 0 então
começar
@HtmlHelp := GetProcAddress(HelpModule, 'HtmlHelpA');
se @HtmlHelp <> nulo então
Resultado: = HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0;
FreeLibrary(HelpModule);
fim;
CallHelp := Falso;
fim;
Carregando BPL dinamicamente
Podemos usar o mesmo método simples para lidar com BPL, ou devo dizer basicamente da mesma maneira simples.
Podemos carregar pacotes dinamicamente usando a função LoadPackage:
função LoadPackage (nome const: string): HMODULE;
Em seguida, use a função GetClass para criar um objeto do tipo TPersistentClass:
function GetClass(const AclassName: string):TPersistentClass;
Depois de tudo feito, use UnLoadPackage(Module:HModule);
Vamos fazer algumas pequenas alterações no código original:
1. Selecione "Project1.exe" no gerenciador de projetos;
2. Clique com o botão direito e selecione "Opções...";
3. Selecione a página de opção “Pacotes”;
4. Remova "Package1" da caixa de edição "Runtime Packages" e clique no botão OK;
5. Na barra de ferramentas do Delphi, clique no botão “Remover arquivo do projeto”;
6. Selecione “Unit2 | Form2” e clique em OK;
7. Agora no código fonte de "Unit1.pas", remova Unit2 da cláusula uses;
8. Insira o código de tempo OnClick do Button1;
9. Adicione duas variáveis do tipo HModule e TPersistentClass:
var
Módulo de pacote: HModule;
AClass: TPersistentClass;
10. Use a função LoadPackage para carregar o pacote Pacakge1:
PackageModule := LoadPackage('Package1.bpl');
11. Verifique se PackageModule é 0;
12. Use a função GetClass para criar um tipo persistente:
AClass := GetClass('TForm2');
13. Se este tipo persistente não for nulo, podemos retornar ao anterior
Crie e use objetos deste tipo da mesma maneira:
com TComponentClass(AClass).Create(Application) como TcustomForm faz
começar
ShowModal;
Livre;
fim;
14. Por fim, use o processo UnloadPackage para desinstalar o pacote:
UnloadPackage(PacoteModule);
15. Salve o projeto.
Aqui está a lista completa de manipuladores de eventos OnClick:
procedimento TForm1.Button1Click(Remetente: Tobject);
var
Módulo de pacote: HModule;
AClass: TPersistentClass;
começar
PackageModule := LoadPackage('Package1.bpl');
se PackageModule <> 0 então
começar
AClass := GetClass('TForm2');
se AClass <> nulo então
com TComponentClass(AClass).Create(Application) como TcustomForm faz
começar
ShowModal;
Livre;
fim;
UnloadPackage(PacoteModule);
fim;
fim;
Infelizmente, isso não é tudo.
O problema é que a função GetClass só consegue pesquisar tipos cadastrados. As classes de formulário e as classes de componentes que normalmente são referenciadas em um formulário são registradas automaticamente quando o formulário é carregado. Mas no nosso caso, o formulário não pode ser carregado antecipadamente. Então, onde registramos o tipo? A resposta está na bolsa. Cada unidade do pacote é inicializada quando o pacote é carregado e limpa quando o pacote é descarregado.
Agora voltando ao nosso exemplo:
1. Clique duas vezes em "Package1.bpl" no gerenciador de projetos;
2. Clique no sinal + ao lado de “Unit2” na seção “Contém”;
3. Clique duas vezes em "Unit2.pas" para ativar o editor de código-fonte da unidade;
4. Adicione a seção de inicialização no final do arquivo;
5. Utilize o procedimento RegisterClass para registrar o tipo de formulário:
RegistrarClass(TForm2);
6. Adicione uma seção de finalização;
7. Use o procedimento UnRegisterClass para cancelar o registro do tipo de formulário:
UnRegisterClass(TForm2);
8. Por fim, salve e compile o pacote.
Agora podemos executar o "Projeto1" com segurança e ele funcionará como antes, mas agora você pode carregar os pacotes como quiser.
fim
Lembre-se, quer você esteja usando pacotes de forma estática ou dinâmica, ative Projeto | Pacotes | Construir com pacotes de tempo de execução.
Antes de desinstalar um pacote, lembre-se de destruir todos os objetos de classe do pacote e cancelar o registro de todas as classes registradas. O seguinte processo pode ajudá-lo:
procedimento DoUnloadPackage(Módulo: HModule);
var
eu: inteiro;
M: TMemoryBasicInformation;
começar
para i := Application.ComponentCount - 1 até 0 fazer
começar
VirtualQuery(GetClass(Application.Components[i].ClassName), M, Sizeof(M));
se (Módulo = 0) ou (HMODULE(M.AllocationBase) = Módulo) então
Application.Components[i].Grátis;
fim;
Cancelar registroModuleClasses(Módulo);
UnloadPackage(Módulo);
fim;
Antes de carregar o pacote, a aplicação precisa saber os nomes de todas as classes cadastradas. Uma forma de melhorar esta situação é criar um mecanismo de registro que informe à aplicação os nomes de todas as classes registradas pelo pacote.
Exemplo
Vários pacotes: os pacotes não suportam referências circulares. Ou seja, uma unidade não pode fazer referência a uma unidade que já faz referência a essa unidade (hehe). Isso dificulta que certos valores no formulário de chamada sejam definidos pelo método chamado.
A solução para esse problema é criar alguns pacotes adicionais que sejam referenciados tanto pelo objeto de chamada quanto pelos objetos do pacote. Imagine como tornamos o Application o dono de todos os formulários? A variável Application é criada em Forms.pas e incluída no pacote VCL50.bpl. Você deve ter notado que seu aplicativo não só precisa compilar VCL50.pas, mas também requer VCL50 em seu pacote.
Em nosso terceiro exemplo, projetamos um aplicativo para exibir informações de clientes e, sob demanda, pedidos de clientes (dinamicamente).
Então, por onde podemos começar? como todos os aplicativos de banco de dados
O procedimento é o mesmo, precisamos nos conectar. Criamos um módulo de dados principal contendo uma conexão TDataBase. Em seguida, encapsulamos este módulo de dados em um pacote (cst_main).
Agora na aplicação, criamos um formulário de cliente e referenciamos DataModuleMain (vinculamos estaticamente VCL50 e cst_main).
Em seguida, criamos um novo pacote (cst_ordr) que contém o formulário de pedido do cliente e exige cst_main. Agora podemos carregar cst_ordr dinamicamente na aplicação. Como o módulo de dados principal já existe antes do carregamento do pacote dinâmico, cst_ordr pode usar diretamente a instância do módulo de dados principal do aplicativo.
A imagem acima é um diagrama funcional desta aplicação:
Pacotes substituíveis: Outro caso de uso de pacotes é a criação de pacotes substituíveis. A implementação desta funcionalidade não requer os recursos de carregamento dinâmico do pacote. Suponha que queiramos lançar uma versão de teste do programa por tempo limitado, como fazer isso?
Primeiro criamos um formulário "Splash", geralmente uma imagem com a palavra "Trial", e o exibimos durante a inicialização do aplicativo. Em seguida, criamos um formulário “Sobre” que fornece algumas informações sobre o aplicativo. Por fim, criamos uma função que testa se o software está desatualizado. Encapsulamos esses dois formulários e esta função em um pacote e o lançamos com a versão de teste do software.
Para a versão paga, também criamos um formulário "Splash" e um formulário "Sobre" - com os mesmos nomes de classe dos dois formulários anteriores - e uma função de teste (que não faz nada) e os adicionamos Encapsulados em um pacote com o mesmo nome.
O quê? Isso é útil, você pergunta? Bem, podemos lançar uma versão de teste do software ao público. Se um cliente adquirir o aplicativo, precisaremos apenas enviar o pacote não experimental. Isso simplifica bastante o processo de lançamento do software, pois são necessárias apenas uma instalação e uma atualização do pacote de registro.
O pacote abre outra porta para o design modular para as comunidades de desenvolvimento Delphi e C++ Builder. Com pacotes você não precisa mais passar identificadores de janela, nem funções de retorno de chamada, nem outras tecnologias de DLL. Isto também encurta o ciclo de desenvolvimento da programação modular. Tudo o que precisamos fazer é deixar os pacotes do Delphi trabalharem para nós.