quinta-feira, 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.

2 comentários:

  1. 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

    ResponderExcluir
  2. 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

    ResponderExcluir

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.