26 de maio de 2009

Chamando funções e classes C++ no Delphi - parte 3

Recebi alguns feedbacks a respeito dos posts anteriores em que mostrei como chamar funções C/C++ dentro do Delphi. Então, resolvi retomar o assunto para explicar algumas dúvidas que surgiram, principalmente referente ao uso de ponteiros.
Como variáveis do tipo ponteiro são muito comuns no C/C++, ficou faltando mostrar como lidar com uma função que tivesse parâmetros desse tipo. Veja abaixo a declaração em C de uma função existente numa biblioteca:
int GetName (unsigned char * pName);

O Delphi também lida com ponteiros, sendo o tipo PChar correspondente ao unsigned char *. Supondo que a DLL seja BIBLIO.DLL e a função esteja exportada como _GetName, o mapeamento da função em Delphi ficaria:
function GetName (pName: PChar): Integer;cdecl;
external 'BIBLIO.DLL' name '_GetName';

Normalmente, esse tipo de chamada supõe que o ponteiro passado como parâmetro já esteja alocado antes de ser usado. Então, para utilizar a função, devemos antes fazer a alocação e, depois de usar o retorno, devolver a memória alocada:
var lName: PChar;
begin
{ Aloca espaço para até 50 caracteres. A documentação da função deve indicar qual o tamanho necessário.}
GetMem (lName, 50);

if (GetName (lName) = 0) then begin
{ Usa aqui o nome contido em lNome }
end;

{ Devolve a memória alocada }
FreeMem (lName);
end;

Outra dúvida recorrente foi quanto ao uso do cdecl: o que é isso exatamente ? Esse recurso das linguagens de programação chama-se formalmente Convenção de chamada. Isto afeta as chamadas de funções e procedures de duas maneiras: como é feita a passagem de parâmetros para a função e de quem é a responsabilidade de fazer a limpeza da "pilha de chamadas" (stack) quando terminar a chamada à função. O quadro abaixo mostra as convenções mais comuns:
ConvençãoLimpezaParâmetros
cdeclRotina chamadoraPassados da direita para a esquerda usando o stack. Comum em bibliotecas desenvolvidas em C/C++
pascalA própria rotinaPassados da esquerda para a direita usando o stack. Usada pela linguagem Pascal.
stdcallA própria rotinaPassados da direita para a esquerda usando endereços múltiplos de 4 bytes no stack. Usada principalmente em funções da API Win32.
fastcallA própria rotinaOs 2 primeiros parâmetros do tipo DWORD (ou menores) são passados em registradores. Os demais são passados da direita para a esquerda usando o stack.
registerA própria rotinaPassados em registradores, quando possível. Os que não forem possíveis de passar em registradores são passados da esquerda para a direita usando o stack.
safecallA própria rotinaPassados da direita para a esquerda usando o stack. Usada normalmente em métodos de interface COM, tratando exceções que sejam levantadas pela interface.


E como é que eu sei qual das convenções deve ser usada para mapear uma determinada função ? Bem, se a documentação da função não for explícita quanto a isso, será preciso dar uns chutes, fazer algumas experiências... O normal é que funções em C/C++ usem o tipo cdecl e que funções desenvolvidas com o Delphi/Pascal usem o tipo pascal ou register. E bibliotecas que sigam o padrão da Microsoft normalmente usam o tipo stdcall.

Ainda sobrou um assunto: como carregar DLLs dinamicamente e como declarar os ponteiros para funções corretamente para usar nesse cenário. Falo disso num próximo post.

9 comentários :

Rafael disse...

Tudo bem mestre...

Estou com uma função em uma DLL feita em C++ com a variável "const unsigned char *data" e não consigo fazer ela funcionar no delphi. Outras funcções desta DLL eu já consegui.

Pode me dar uma ajuda, abraço.

Luís Gustavo Fabbro disse...

Rafael

O tipo de dado const unsigned char* do C/C++ deveria ser mapeado sem problemas para PChar no Delphi. A palavra const indica, a grosso modo, que o endereço apontado pelo parâmetro com esse tipo não poderá ser modificado.

Se usou esse mapeamento e não funcionou, acredito que o problema seja outro. Talvez relacionado com a forma de fazer a chamada (cdecl, fastcall, etc.). Se não conseguir encontrar, envie mais detalhes para o email do blog; por exemplo, a assinatura da função ou documentação dela, se tiver.

Rafael disse...

Tudo bem Mestre?

O tipo de dados que usei foi uma array de bytes e a chamada foi a stdcall e deu certo. Aconteceu outra coisa também, como essa dll é para comunicação serial, eu estava alocando a serial de forma errada.

Grande abraço e obriga pela ajuda.

Anônimo disse...

Olá!!
Tenho uma DLL que foi feita em C++ onde existe uma função que está declarada da seguinte forma:
teste(const char* e).
estou tentando chamar esse método na string, mas a dll não está retornando os dados que deveria, ela está retornando valores vazios.
Em delphi, a única maneira que consegui executá-la foi a seguinte:
function teste(TESTE:String):string; safecall; external 'teste.dll' name'getteste';
Caso tenha alguma sujestão do que pode estar ocorrendo, por favor responda.
Desde já agradeço a atenção.

Luís Gustavo Fabbro disse...

Você tem o projeto dessa DLL ? Certifique-se que ela foi compilada usando mesmo o SAFECALL como convenção de chamada. Se não foi, modifique a declaração no Delphi para refletir a convenção correta.

O ideal é deixar explícito na declaração do C++ qual é o tipo de chamada pra evitar que mudanças no projeto façam com que programas usando a DLL deixem de funcionar repentinamente.

De qq modo, em Delphi vc mapeou o tipo CONST CHAR* do C++ para STRING mas eles não são compatíveis. O correto é mesmo o PCHAR, como mostra o post.

Anônimo disse...

A dll que estou executando está no padrão UTF, acho que foi por isso que eu só consegui executá-la dessa forma, mas não consigo pegar os resultados dessa DLL.
poderia me dar alguma sugestão sobre isso, como eu poderia executá-la?
Ela foi programada em C++.

Luís Gustavo Fabbro disse...

O que exatamente vc quer dizer com "a biblioteca está no padrão UTF" ? Isso normalmente diz respeito ao conjunto de caracteres que um texto é capaz de lidar e não deveria influenciar a chamada de funções em si.

Vc não consegue fazer a chamada trocando o STRING por PCHAR no Delphi e seguindo as instruções do post para alocar a memória que será passada para a função ? Isso dá alguma mensagem de erro ?

Anônimo disse...

Desculpe a demora na resposta, mas é o seguinte, essa DLL foi programada em C++ só que para ser lida por um sistema desenvolvido em power builder 12 que utiliza os padrões UTF, portando ao tentar executar essa DLL em delphi a única maneira disso acontecer foi da seguinte maneira: teste(TESTE:String):string; safecall; external 'teste.dll' name'getteste';

Em C++ o pessoal chamou ela da seguinte forma:Function string teste(string teste) Library "teste.dll" ALIAS FOR "getteste;ansi".

alguma sugestão de como chamar essa DLL em Delphi parecido com a function em c++?

Luís Gustavo Fabbro disse...

Tem que ver como exatamente foi criada a função no C++ para determinar como declará-la e usá-la no Delphi. Se ela está mesmo declarada como teste(const char* e), não faz sentido tentar usá-la em Delphi passando uma variável do tipo string.

Se puder, envie o header C++ com a declaração da função e a DLL onde ela está compilada para o email aqui do Balaio pra eu dar uma olhada.

[]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.