Programação e afins

terça-feira, 2 de junho de 2009

Carregar DLLs construídas em C++ através do Delphi.

Introdução


Criar DLLs em Delphi e carrega-las não é uma tarefa difícil, existem muitos sites na internet que tratam disso, porém, na maioria das vezes, o que se encontra são exemplos de programação básica que não atendem certos objetivos. Vejamos um exemplo comumente encontrado:

Código da DLL em Delphi:
(clique na imagem para ampliá-la)



















Carregamento da DLL no Delphi:




Estes são exemplos de criação e carregamento de DLLs em Delphi. Essa DLL exporta a função soma, função que retorna a soma de dois números inteiros. Este exemplo, por ser de nível básico, não ajudará quem está precisando, por exemplo, trabalhar com passagem de ponteiros.

Há algum tempo atrás, eu precisei fazer uma DLL em C++ (Visual Studio 2008) que deveria receber e devolver records, ponteiros e etc., e também que fosse carregada em um programa feito em Delphi. Infelizmente não encontrei um material bom na internet. Por isso resolvi compilar esse texto. Irei mostrar como construir uma DLL em C++ que receba structs / records, utilizando técnicas de ponteiros e alocação dinâmica de memória, uso de strings etc.


Criando uma DLL em C++


Usarei o Visual Studio 2008 para criar a DLL em C++ usando MFC. É possível ter uma cópia do Visual Studio 2008 totalmente completa por um período de avaliação de 90 dias (http://msdn.microsoft.com/en-us/visualc/aa700831.aspx). Também existe a versão Express do Visual Studio 2008, e esta não expira, porém “não serve” para os exemplos a seguir, visto que usarei MFC. Vamos lá:

1º) Menu File -> New -> Project

2º) Na árvore Project types -> Visual C++, selecione MFC e depois MFC DLL

3º) digite um nome para o projeto. Ex: prj_minhadll



No Wizard que será exibido, clique diretamente em Finish.



Logo você deverá ter uma tela como esta:



Construção da DLL

abra o arquivo prj_minhadll.h (janela lateral esquerda - Solution Explorer)
crie o protótipo da função que usaremos para copiar a string, chamaremos a função de copiarString. Serão passados 3 parâmetros e o tipo de retorno será void. Está função será usada para demonstrar todas as 3 técnicas mencionadas anteriormente.

strSource do tipo char → ponteiro para variável que contém os dados a serem copiados
strDest do tipo char → ponteiro para variável que receberá a cópia dos dados
strSize do tipo int → número de caracteres que serão copiados

observação: em C++, uma função que retorne o tipo void, a grosso modo, é o mesmo que uma procedure no Delphi.. Quando uma variável é precedida por um (* asterisco) quer dizer q estamos declarando um ponteiro - *strSource é equivalente em C++ ao ^strSource do Delphi.


Agora que temos o protótipo da função definido, iremos implementar seu código. Para copiar o conteúdo de uma variável para outra, existe uma função muito usada em C++,
a memcpy. Esta função copia um número x de bytes de uma área da memória para outra e iremos usá-la.

abra o arquivo prj_minhadll.ccp (janela lateral esquerda Solution Explorer) e vamos ao código:







Temos então nossa função pronta, agora temos que avisar para a DLL que essa função será exportada, para isso abra o arquivo prj_minhadll.def. Este arquivo deve conter o nome de todas as DLLs que serão exportadas




Quando se usa o arquivo .def para exportar os nomes das DLLs, por padrão as funções serão exportadas usando chamadas do tipo stdcall, não entrarei em detalhes sobre isso pois existe uma boa documentação no MSDN (http://msdn.microsoft.com/pt-br/library/dt232c9t.aspx).

Finalmente iremos compilar a DLL, pressione Ctrl + Alt + F7 ou vá ao menu Build → Rebuild Solution. Se tudo estiver ok você terá uma mensagem de sucesso e a DLL será gerada na pasta Debug do projeto. Atenção, nas funções da DLL, usadas como exemplo, não fazemos nenhuma verificação para saber se existe memória suficiente alocada e isso é perigoso, por isso tome cuidado quando você escrever suas próprias funções, é sempre bom checar se existe memória alocada.


Aplicativo Delphi

Primeiramente irei mostrar 3 técnicas para o uso de strings, a primeira mostra como passar strings usando o tipo string, a segunda mostra como passar strings usando cadeia de caracteres (array of char), e a terceira como usar alocação de memória com GetMem.


Passando strings para DLL em C++


Existem formas diferentes de passar strings para funções em DLL, por exemplo, caso o aplicativo que irá carregar a DLL, e também a DLL fossem escritos em Delphi, poderíamos usar diretamente o tipo string e a unit Sharemem para “resolver” problemas de gerenciamento de memória. Porém estamos tratando de passagem de valores para uma DLL feita em C++. Sendo assim não podemos usar o tipo string diretamente uma vez que o C++ não implementa o tipo string como o Delphi. Por isso, iremos fazer com que a função em C++ receba ponteiros.


Criando o aplicativo em Delphi


Iremos agora criar a aplicação em Delphi para chamar os métodos da DLL.
Crie um diretório chamado delphi na mesma pasta em que está o arquivo de projeto da DLL (prj_minhadll.sln).
Abra o Delphi e crie um novo projeto com o nome que desejar. Salve o projeto na pasta criada. Coloque três TButton no formulário principal. Segue o texto do DFM do formulário principal:




Após os passo acima, iremos declarar a função no Delphi, a princípio usaremos o mesmo nome da função que foi usado no projeto em C++. É recomendável ler sobre chamadas (Calling conventions), stdcall, cdecl e outras, para entender o porque de podermos usar o mesmo nome da função, que está na DLL, diretamente dentro do aplicativo sem nenhuma conversão.


Estamos usando 'variáveis' do tipo PChar, ou seja, ponteiro para char (lembra-se que os dois primeiros parâmetros da função copiarString na DLL são :

strSource do tipo char → ponteiro para variável que contém os dados a serem copiados
strDest do tipo char → ponteiro para variável que receberá a cópia dos dados

A seguir iremos implementar o código que irá passar a variável para a DLL. Os passos que devemos seguir são praticamente esses:
  • reservar a memória necessária para a variável de origem e para variável de destino (ambas devem ter o mesmo número de bytes alocados)
  • definir o conteúdo da variável de origem
  • chamar a função passando a variável de origem e a variável de destino, e também o tamanho delas.
  • exibir o resultado da variável de destino na tela
  • liberar a memória alocada


Exemplo 1: usando strings

Note que quando fazemos @sOrigem[1] e @sDestino[1], estamos passando o endereço de memória do primeiro elemento de cada variável. Você pode estar se perguntando porque fazemos desse jeito e resposta não é complicada. O tipo string do Delphi reserva a posição zero para armazenar o tamanho da cadeia de caracteres. Então se passássemos da forma @sOrigem[0] a posição que guarda o tamanho da string seria populada com outros dados e ficaria corrompida, o que poderia gerar um erro no programa.


Exemplo 2: usando array of char




No exemplo 2, não precisamos alocar memória de forma manual, e passamos o endereço da variável diretamente @sOrigem, isso acontece porque usamos uma variável do tipo array of char, este tipo de variável é compatível com char array do C++, ou seja, sem as peculiaridades do tipo string do Delphi.


Exemplo 3: usando PChar e alocando memória com GetMem




Este exemplo difere razoavelmente dos anteriores, aqui declaramos as variáveis sOrigem e sDestino como PChar, ou seja, nada mais que ponteiros. Depois alocamos mémoria para esses ponteiros usando a função GetMem, 39 bytes para cada um(cada caracter corresponde a um byte). Em sequida usamos a função StrCopy para popular a variável sOrigem e por fim liberamos a memória através do FreeMem.
Note que neste exemplo, não precisamos do operado @ na frente do nome da varável, isto acontece porque estamos trabalhando diretamente com ponteiros (Pchar é um ponteiro para Char → Pchar = ^Char)

Ainda existem outras formas de passar strings para DLLs em C++, mas imagino que estes exemplos sejam o suficiente por hora. A seguir irei mostrar como trabalhar com structs / records em DLLs



Trabalhando com Structs / Records

Vamos supor que temos o seguinte record:

e a respectivo struct em C++:




E caso nosso aplicativo em Delphi necessitar chamar uma função na DLL que tenha que passar como parâmetro uma variável do tipo TDadosUsuario para ser populado por esta função, como fazer? Simples, recorrer a ponteiros novamente. Vamos lá:

Código da DLL:





Aplicativo Delphi:



Declaração da função:



Evento que chama a função:



Se você entendeu os exemplos das strings, então deve ter entendido este também. O que fizemos foi declarar uma variável do tipo TDadosUsuario e chamar a função populaRegistro da DLL, passando como parâmetro o endereço de memória da variável declarada, ou seja, apontando para a variável. Como na DLL temos uma struct idêntica ao record TDadosUsuario, os dados populados se “encaixam” de forma correta na memória apontada pelo endereço que passamos (@meuRegistro)

That's all folks! Em outra oportuidade eu tratarei de mostrar como passar arrays de records e arrays multi-dimensionais para a DLL em C++.


Abraços,

Paulo Leonardo Vieira Rodrigues


Esta obra está licenciada sob uma Licença Creative Commons.

Marcadores: , , , , , , ,

0 Comentários:

Postar um comentário

Assinar Postar comentários [Atom]

<< Página inicial