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.