Visão geral
Criar bibliotecas é prático, você mantêm um conjunto de funcionalidades presos em arquivos externos ao seu programa e então você distribui junto com o seu executável.
Uso prático
Você pode incluir um conjunto de funções dentro de uma DLL para que vários programas juntos consumam as suas funções, com isso você garante que programas diferentes usem o mesmo método, por exemplo, uma função que testa se o login de uma pessoa, isto é, nome de usuário e senha estão corretos para prosseguir com acesso ao banco de dados. Para impedir que cada programa seu tenha um Ctrl+C/Ctrl+V da mesma função em todos os programas, você criaria uma biblioteca em pascal que fizesse esse teste e colocaria todos os programas para compilar junto com essa biblioteca e isso daria certo, mas passam-se os anos e alguém altera essa biblioteca e agora teremos os programas novos que usam o código novo, porém programas mais antigos deveriam ser recompilados com a nova biblioteca ou pode acontecer problemas. Daí a ideia de usar bibliotecas externas ao seu programa principal que normalmente manterá sua consistência.
Outro exemplo prático para usar DLLs são relatórios. Ao invés de colocar relatórios embutidos dentro do seu executável, é mais saudável deixá-los externamente em forma de DLL. Esse método permite que clientes diferentes solicitem novos relatórios que afetem apenas eles.
Como criar uma função para DLL bem básica usando strings
O pascal tem métodos de lidar com variaveis que diferem muito de outras linguagens como C/C++, então no exemplo abaixo, vamos lidar com um tipo de string chamado de pChar. O tipo pChar é um array ou buffer de caracteres ou bytes enfileirados, ele não é tão prático quanto String, mas escrever DLLs que possam ser usadas por outros programas que foram escritos em outra linguagem requer que usemos ele, então veja um exemplo bem básico de como funciona.
O exemplo abaixo é uma função genérica que não implementa, mas cria um modelo de como eu posso testar um login no banco de dados fornecendo parâmetros como login, senha e role por meio de uma TStringList e retorna novamente uma TStringList.
Escolhi TStringList porque elas são fáceis de manipular, eu posso usar TStringList.Values[‘parametro’] para descobrir um valor ou TStringList.Items.IndexOf[‘parametro’] para saber se um parâmetro foi fornecido e assim por diante.
Algo que pode muito confundi-lo é que ao olhar para o código você notará que o parâmetro de entrada é pChar e não TStringList como eu disse, por que?
Não vou usar exatamente o TStringList, mas TStringList.Text que também é uma string. Ao lidar com DLLs precisamos que ela seja criada de um jeito que outros programas possam entender, outras linguagens não usam uma String do jeito que o pascal o faz, assim precisamos usar um tipo especial de String chamada de pChar.
O tipo pChar é uma cadeia especial de caracteres que pode facilmente ser convertido para string e vice-versa. Então vamos ao código:
library DLL_Teste;
uses
Classes,
SysUtils
{ you can add units after this };
function F_TestLogin(pParamList:pChar): pChar; stdcall;
var
sLoginName, sLoginPassword, sRole, sConfigFile:String;
ListaIN:TStringList;
ListaOUT:TStringList;
function FakeLogin(ALoginName, APassword, ARole:String):String;
begin
Result:=emptyStr;
end;
begin
ListaIN:=TStringLIST.Create;
ListaOUT:=TStringLIST.Create;
ListaOUT.Values['RESULT']:='FAIL';
// garantindo que qualquer exit antes do tempo retorne FAIL
Result:=pChar(ListaOUT.Text);
try
// A estrutura de minha string é compativel com TStrings então posso
// importá-la como se fosse uma StringList
ListaIN.Text:=String(pParamList); // pChar para string
sConfigFile:=Trim(ListaIN.Values['CONFIG_FILE']);
sLoginName:=Trim(ListaIN.Values['User_Name']);
sLoginPassword:=Trim(ListaIN.Values['Password']);
sRole:=UPPERCASE(Trim(ListaIN.Values['Role']));
// apenas um exemplo de teste de autenticação
if FakeLogin(sLoginName, sLoginPassword, sRole)=emptyStr then
begin
// Prosseguindo...
ListaOUT.Values['RESULT']:='OK';
ListaOUT.Values['MSG_RESULT']:='OK';
end
else
begin
// Erro ao tentar conectar ao servidor
ListaOUT.Values['RESULT']:='FAIL';
ListaOUT.Values['MSG_RESULT']:='Autenticação falhou';
end;
except
on e:exception do
begin
ListaOUT.Values['RESULT']:='FAIL';
ListaOUT.Values['MSG_RESULT']:=e.message;
end;
end;
ListaOUT.Values['User_Name']:=sLoginName;
ListaOUT.Values['Password']:=sLoginPassword;
ListaOUT.Values['Role']:=sRole;
Result:=pChar(ListaOUT.Text);
ListaIN.Free;
ListaOUT.Free;
end;
exports
F_TestLogin;
begin
end.
Não usará outra linguagem, apenas pascal?
Se você usará apenas pascal e nada mais, seja delphi ou freepascal então não é necessário usar pChar ou qualquer outro tipo de ponteiros para tipos primitivos. Ao usar os tipos primitivos do pascal ao invés de pChar e ponteiros você economiza muito código dedicado a conversão de tipos. também poderá remover as indicações do tipo stdcall;
Como consumir DLLs
Agora, como posso consumir a DLL acima?
Você pode consumir DLLs de duas formas:
- Estática: quando seu aplicativo é carregado, todas as DLLs que foram informada sem seu programa serão carregadas conjuntamente e se falhar uma delas, o programa será interrompido. Quando usar? Você precisa que seja estática quando se tratar de um programa ou serviço indispensável ao seu programa, sem essa(s) biblioteca(s) o programa não deve nem funcionar. Por exemplo, uma DLL de acesso a impressora fiscal.
- Dinamicamente: A DLL é requerida apenas no momento do uso, você a executa e em seguida a mesma é liberada da memória. Quando usar? Quando não está certo se a funcionalidade dentro da biblioteca será usada ou não, é o caso de relatórios, um usuário poderá decidir imprimir ou não o relatório então porque carregar a biblioteca todas as vezes?
Carregar uma DLL estaticamente
Não há muito segredo ao carregar uma DLL estaticamente. Porém, visto que em Delphi ou FreePascal podemos gerar um código binário num único executável então porque deveríamos usar uma DLL dessa forma? Realmente não há muitos motivos para isso, mas vamos ao exemplo mesmo assim:
(todo)
Carregar uma DLL dinamicamente
Essa é a principal razão de usar uma DLL no Delphi ou FreePascal, uma vez que você pode carregá-la e descarregá-la após o uso. Muito versátil, por exemplo, no caso de relatórios, pode manter versões diferentes do mesmo relatório ou atualizá-los mantendo o programa principal intacto. Vamos a um exemplo de como carregar e descarregar uma DLL dinamicamente:
procedure TForm1.BtnConsumirClick(Sender: TObject);
type
TF_TestLogin = function (pParamList:pChar): pChar; stdcall;
var
F_TestLogin: TF_TestLogin; // Uma variável para acessar a sub-rotina na DLL
LibHandle: THandle; // um handle para a DLL
pResultado:pChar; // resultado da função é um pchar
ListaIN:TStringList; // lista de parametros de entrada
ListaRecebe:TStringList; // valores recebidos em forma de lista
pListaIN:Pchar; // lista de parametros de entrada em formato pChar
ErrorMode: UINT; // codigo de erro caso aconteça
ErrorMsg:String; // mensagem de erro que impeça de prosseguir
const
DLL='C:\caminho\para\DLL_Teste.dll';
begin
// variavel que indicará a ocorrência de algum erro
ErrorMsg:=emptyStr;
// variavel que conterá o retorno da função dentro da DLL
pResultado:=pChar(emptyStr);
// Minha lista que receberá o retorno
ListaRecebe:=TStringList.Create;
// Meu parametro de entrada
ListaIN:=TStringList.Create;
ListaIN.Values['User_Name']:='meulogin';
ListaIN.Values['Password'] :='minhasenha';
ListaIN.Values['Role'] :='RDB$ADMIN';
// Caminho para a DLL
if not FileExists(DLL)
then ErrorMsg:='Arquivo não existe: '+DLL;
if ErrorMsg=emptyStr then
begin
// Desliga OS error messages
ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
// Pegar o handle identificador da biblioteca a ser usada
try
LibHandle := LoadLibrary(PChar(DLL));
finally
SetErrorMode(ErrorMode);
end;
if LibHandle <= HINSTANCE_ERROR
then ErrorMsg:='Erro ao carregar: '+DLL;
if ErrorMsg=emptyStr then
begin
// Confere se o carregamento da DLL foi bem-sucedido
if Win32Check(Bool(LibHandle)) then
begin
// Atribui o endereço da chamada da subrotina à variável F_TestLogin, mas
// nesse ponto o FPC e Delphi tem jeitos diferentes:
{$IFDEF FPC}
Pointer(F_TestLogin) := GetProcAddress(LibHandle, 'F_TestLogin');
{$ELSE}
F_TestLogin := GetProcAddress(LibHandle, 'F_TestLogin');
{$ENDIF}
// Verifica se um endereço válido foi retornado
if @F_TestLogin <> nil then
begin
// pegando o resultado
pListaIN:=pChar(ListaIN.Text);
pResultado := F_TestLogin(pListaIN);
// como o resultado é pChar então tenho que converter
// para string e jogar numa lista onde fica mais facil
// processar depois
ListaRecebe.Text:=String(pResultado);
end;
end;
end;
end;
//
// tudo pronto para usufruir do retorno
//
if ErrorMsg=emptyStr then
begin
memoResultado.Text:=ListaRecebe.Text;
if SameText(ListaRecebe.Values['RESULT'],'OK') then
memoResultado.Lines.Add('Legal que tudo tenha ocorrido bem.')
else
memoResultado.Lines.Add('Que chato, parece que o login falhou.');
end
else
begin
memoResultado.Lines.Add('Não pude chamar a DLL: '+ErrorMsg);
end;
// liberando memoria
F_TestLogin := nil;
while not FreeLibrary(LibHandle) do
Sleep(5); // espera 5s para uma nova tentativa
ListaIN.Free;
ListaRecebe.Free;
end;Fatoração de código
Você pode vir a achar que carregar uma DLL dinamicamente é muito verboso, no entanto, raramente vamos usá-lo do jeito que foi mostrado, normalmente vamos fatorar, isto é, criar uma função com o código acima e apenas variando o nome da DLL, os parâmetros de entrada e retornando o resultado conseguido. Ao fatorar uma única vez como por exemplo ChamarRelatorio(‘relatorio_vendas.dll’) evita-se o esforço de copiar/colar em todas as ocorrências praticamente o mesmo código.
Conclusão
Espero que tenha achado o artigo benéfico. O exemplo fornecido funciona em Delphi e também em FreePascal.