28 de maio de 2009

Chamando funções de uma biblioteca externa em Delphi

Mostrei numa série de posts como mapear as funções de uma DLL para poder chamá-las em um programa Delphi usando a sintaxe:
function GetName (pName: PChar): Integer;cdecl;
external 'BIBLIO.DLL' name '_GetName';

Essa abordagem, no entanto, tem um inconveniente: a DLL é carregada imediatamente, junto com o programa. Se a DLL não puder ser carregada - ela não foi encontrada ou uma de suas dependências não foi encontrada, por exemplo - então o programa em si também não é carregado e o Windows exibirá uma mensagem de erro.

Vou mostrar aqui um conjunto de funções da API do Windows que permite carregar a biblioteca dinamicamente. Nesse cenário, se a biblioteca não puder ser carregada por qualquer motivo, o programa em Delphi continuará em execução e poderá tratar o erro de uma forma mais elegante, mostrando mensagem de erro apropriada. Se for o caso, o programa poderá também desabilitar seus recursos que dependam da biblioteca problemática.

Para começar, será preciso definir um tipo de dado no Pascal que aceite receber um ponteiro para a função da biblioteca. Usando a função declarada no começo do post como exemplo, o tipo a ser definido será:
type TPtrGetNome = function(pNome: Pchar): Integer;cdecl;

Veja que o tipo declarado é exatamente igual à assinatura da função, sem o nome. Se quiser chamar outras funções, será necessário criar um tipo para cada assinatura diferente.

A função da API do Windows necessária para obter o endereço (ponteiro) da função que queremos chamar aceita como parâmetro um handle para a biblioteca. Por isso, teremos que obter esse handle antes, através da chamada de outras duas APIs do Windows, LoadLibrary para a carga e FreeLibrary para descarregar a biblioteca da memória, devolvendo ao Windows os recursos utilizados.
var lHandle: THandle;
begin
lHandle := LoadLibrary ('biblio.dll');

if lHandle <> 0 then begin
{ chamar a função aqui }
end
else
ShowMessage (GetLastErrorMsg);

FreeLibrary (lHandle);
end;

A API LoadLibrary retorna zero se não foi possível carregar a DLL; a API GetLastError recupera o código do erro ocorrido; qualquer outro número indica um handle válido mapeado para a DLL carregada. A função GetLastErrorMsg é minha e apenas formata o código de erro retornado por GetLastError.

Agora podemos obter o ponteiro para a função GetName na biblioteca carregada e, então, executá-la como se ela estivesse codificada em nosso próprio programa. Para obter o ponteiro da função, devemos chamar a API GetProcAddress informando o handle da biblioteca e o nome da função desejada. O nome da função segue as regras que eu descrevi no post sobre name mangling; se não tiver certeza do nome correto, use as dicas sobre como descobrir os nomes das funções em uma DLL.

var lHandle: THandle;
DoGetName: TPtrGetNome;
begin
lHandle := LoadLibrary ('biblio.dll');

if lHandle <> 0 then begin
DoGetName := GetProcAddress (lHandle, '_GetName');
if @DoGetName <> nil then
DoGetName (_Name);
end
else
ShowMessage (GetLastErrorMsg);

FreeLibrary (lHandle);
end;

Caso a função solicitada não exista na biblioteca, o ponteiro nil é retornado e deve ser tratado. Não mostrei nesse trecho mas a variável _Name é do tipo PChar e precisa ser alocada antes de se efetuar a chamada - aqui, fiz isso em outro momento.

6 comentários :

Sandra - Info-Macross disse...

Olá Luís!
Encontrei seu blog no google procurando sobre dll dinâmica no delphi, estou com um problema e não consigo encontrar a solução, será que teria um tempinho e poderia me ajudar?
Tenho que chamar uma dll dinâmica, porém quando executo ela, além de não funcionar, ela dá erro de memória logo após do end final da rotina, não faço a menor idéia do que fazer! É uma dll para o pinpad da gertec, chamando ela estaticamente funciona ok, mesmo eu não tendo o pin instalado na máquina, mostra o erro da dll que diz que não tem pinpad, mas na hora de chamar dinamicamente nem esse erro dá... Se você puder me ajudar a ver onde estou errando, eu agradeço muito!!
Desculpe colocar o código aqui, mas não quero te incomodar por e-mail...

type
Tfun = Function(uiDeviceNumber: UINT):integer; far;
THandle = Integer;

var
Form1: TForm1;
ahandle: THandle;
Fun: Tfun;

implementation
{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var teste: Tfun;
begin
aHandle := LoadLibrary('pin32');
if aHandle <> 0 then
begin
@teste := GetProcAddress(aHandle, 'iOpenPin');

if not Assigned(teste) then
ShowMessage('Função não encontrada!')
else
teste(1);

end;
FreeLibrary(aHandle);
end;

Agradeço muito a atenção..
Sandra

Luís Gustavo Fabbro disse...

Sandra

A ideia do que você mostra no seu código está correta, isto é, a sequência dos passos é esta mesma. O que eu não consigo ver é se a definição do tipo TFun mostrada corresponde ao que você tem de fato na DLL.
Pelos sintomas, parece que está faltando informar na declaração qual é o tipo de chamada que deve ser feito (veja aqui os tipos possíveis).
Não conheço a DLL que você quer usar mas a definição que eu encontrei neste link diz que a chamada é stdcall. Então, tente fazer a declaração de TFun como segue:
Tfun = Function(uiDeviceNumber: INT):integer;stdcall;
Se não funcionar, mande seu código e a DLL e sua documentação no endereço do Balaio para eu dar uma olhada.

[]

Gustavo

Anônimo disse...

DoGetName := GetProcAddress (lHandle, '_GetName');

Nesta linha o '_GetName' representa o que? Ele é o nome do método da DLL ?

Luís Gustavo Fabbro disse...

Tiago

O '_GetName' representa o nome de uma função publicada na DLL. Você pode descobrir quais funções estão publicadas (e com quais nomes) usando o programa indicado neste outro post.

[]s

Junior disse...

Olá Luís, parabéns pelo post, estou com um problema e talvez vc possa me ajudar, se possível é claro.

Criei uma DLL para update e criei um programa para chamá-la dinamicamente e tudo funciona perfeitamente. Estou criando um Windows Services e gostaria de chamar a DLL, porém ela não funciona como deveria. Tem alguma dica para usá-la com um Serviço? Tem algo que devo mudar?
Desde já agradeço a atenção. juniormvieira@gmail.com

Luís Gustavo Fabbro disse...

Junior

Não há, em essência, diferença em usar a DLL num programa desktop ou num serviço. No geral, problemas aparecem quando você tenta usar recursos da DLL em ambiente multithread quando a biblioteca em si não está preparada para isso.

Em geral, um serviço também não admite a exibição de mensagens ou outro tipo de interação com o usuário.

Que tipo de problema ocorre com sua biblioteca qdo usado no serviço?

[]s

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.