Issuu on Google+

E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

8. Criando DLLs As bibliotecas de enlace dinâmico (DLL - Dynamic-link libraries) fornecem uma forma modular de construir aplicações de forma que a funcionalidade possa ser atualizada e reutilizada de forma mais flexível. Elas ajudam a reduzir a sobrecarga de memória quando muitas aplicações usam a mesma funcionalidade ao mesmo tempo porque embora cada aplicação tenha o seu próprio conjunto de dados, elas podem compartilhar o código. No Windows as DLL são módulos que contêm funções e dados. Uma DLL é carregada em tempo de execução pelos seus módulos invocadores (.EXE ou outra DLL). Quando uma DLL for carregada, ela é mapeada no espaço de endereços do processo que a invocou. As DLL podem definir dois tipos de funções: exportadas e internas. As funções exportadas podem ser chamadas por outros módulos. As funções internas podem somente ser chamadas dentro da DLL onde elas estão definidas. Um arquivo .DLL é uma coleção de arquivos .OBJ ou módulos que um .EXE pode enlaçar de forma estática (i.e. que a DLL deva estar presente para que o .EXE ser executado) ou dinâmica (i.e. que a DLL será carregada somente quando o .EXE precisar). Existe uma diferença entre o “enlace estático” de uma DLL e o “uso estático do enlace”. O enlace estático significa que se está fazendo o enlace com uma biblioteca que contém todos os .OBJ necessários. Isto não é uma DLL, e sim uma biblioteca estática. Tipicamente essas possuem a extensão .LIB. O uso estático do enlace com uma .DLL significa que se está fazendo um enlace que contém referências para a DLL (também usa .LIB, mas sem os .OBJ). O carregamento dinâmico da .DLL significa que se está chamando a função da WinAPI, LoadLibrary(...). Assim, as três permutações possíveis são: o enlace estático com uma .LIB, o uso estático do enlace com uma biblioteca de importação (também .LIB) que possui referências para uma .DLL, e o carregamento dinâmico de uma .DLL.

171


L

U

I

F

S

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

8.1. Tipos de Enlace Dinâmico Existem duas formas de chamar uma função em uma DLL:  No enlace dinâmico durante o carregamento do programa quando um módulo faz chamadas explícitas a funções exportadas pela DLL. Isto requer que existe um enlace do módulo com a biblioteca de importação da DLL. Uma biblioteca de importação fornece ao sistema operacional com a informação necessária para carregar a DLL e localizar as funções exportadas pela DLL quando a aplicação for carregada.  No enlace dinâmico durante o tempo de execução, um módulo usa a função LoadLibrary para carregar a DLL durante o tempo de execução. Depois que a DLL for carregada, o módulo chama a função GetProcAddress para capturar os endereços das funções exportadas da DLL O módulo chama as funções exportadas pela DLL usando os ponteiros para função retornados pela função GetProcAddress. Isto elimina a necessidade de uma biblioteca de importação.

8.2. DLL e Gerenciamento de Memória Cada processo que carrega a DLL mapeia-a dentro do seu espaço virtual de endereços. Depois que o processo carregar a DLL dentro dos seus endereços virtuais ele pode invocara as funções exportadas. O sistema mantém um contador de referência para cada DLL. Quando uma aplicação carrega a DLL o contador é incrementado de um. Quando a aplicação acaba ou quando o contador de referência chegar a zero, a DLL é descarregada do espaço virtual de endereços. Assim como as demais funções, as funções exportadas da DLL são executadas dentro do contexto da aplicação que a chamou. Desta forma, se aplicam as seguintes condições:  A tarefa do processo que chamou a DLL pode usar manipuladores abertos pela função da DLL. De forma análoga, os manipuladores abertos por qualquer tarefa do processo invocador podem também ser usados pelas funções da DLL.  As DLL usam a pilha do processo invocador e o seu espaço virtual de endereços.  As DLL alocam memória a partir do espaço virtual de endereços do processo invocador.

8.3. Vantagens do Enlace Dinâmico O

enlaçamento

dinâmico

possui

as

seguintes

vantagens

sobre

o

enlaçamento estático:  Os processos que carregam a DLL no mesmo endereço base podem usar uma única DLL de forma simultânea, compartilhando uma única cópia do código da DLL na memória física. Fazendo isto se poupa memória e reduz a fragmentação.

172


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

 Quando as funções de uma DLL mudarem, as aplicações que as usam não precisam ser recompiladas ou re-ligadas enquanto os argumentos das funções, as formas de chamada e os tipos de valores de retorno não mudarem. Em contraste, os códigos-objeto de enlace estático requerem que a aplicação seja re-ligada quando mudarem as funções.  Uma DLL pode fornecer suporte pós-venda. Por exemplo, uma DLL de driver de display pode ser modificada para suportar um display que não estava disponível quando a aplicação foi vendida.  Os programas escritos em diferentes linguagens de programação podem chamar a mesma função da DLL sempre que os programas sigam o mesmo padrão de chamada que a função utiliza. As convenções de chamada (tais como C, Pascal e outras) controlam a ordem na qual a função invocada deve colocar os argumentos na pilha, se a função invocada ou invocadora é responsável por limpar a pilha, e se os argumentos são passados através dos registradores.

Uma potencial desvantagem no uso das DLL é que a aplicação não é autocontida; ela depende da existência de um módulo separado (a DLL). O sistema termina os processos que usam enlace dinâmico em tempo de carregamento se eles requererem uma DLL que não é encontrada no início do processo e provoca uma mensagem de erro para o usuário. O sistema não termina os processos que usam enlace dinâmico em tempo de execução nesta situação, no entanto as funções exportadas pela DLL não ficam disponíveis para o programa.

8.4. Função Entrada Todas as DLL devem ter um ponto de entrada ou de início assim como as aplicações (função main). O sistema operacional chama a função de início quando os processos e tarefas carregam ou terminam a DLL. Se a DLL estiver ligada com uma biblioteca tal como uma biblioteca C em tempo de execução, ela pode fornecer a função de início e permitir que seja fornecida uma função de inicialização separada. Se for colocado o próprio ponto de início, pode ser usada a função DllEntryPoint. O nome DllEntryPoint é um lugar para colocar a função definida pelo usuário. Geralmente o programador pode especificar um ponto de início para a sua DLL usando o linker.

8.5. Quando usar Packages e DLLs Para a maioria das aplicações escritas em C++Builder, os packages fornecem grande flexibilidade e são mais fáceis de criar que DLLs. No entanto,

173


L

U

I

F

S

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

existem várias situações onde as DLLs são mais convenientes para os seus projetos:    

Quando Quando Quando Quando

o módulo será chamado a partir de aplicações não-C++Builder. se quer estender a funcionalidade de um servidor web. se cria módulos de código para ser usado por desenvolvedores terceiros. o projeto é um contêiner OLE.

8.6. Criando DLLs no C++Builder A criação de DLLs em C++Builder é igual à criação em C++. Proceder da seguinte maneira: 1

Escolher a opção de menu File + New para mostrar a janela de

diálogo New Items.

2

Escolher o ícone DLL e pressionar OK.

3

Escolher o tipo de fonte (C ou C++) para o módulo principal. Se você

desejar que o ponto inicial seja no estilo do MSVC++ (DllMain()) selecionar a opção correspondente. Caso contrário será usado DllEntryPoint() como ponto inicial. Selecione Use VCL para criar DLL contendo componentes VCL. Esta opção é válida somente para módulos com fonte C++. Se desejar que a DLL seja multitarefa assinalar a opção Multi-threaded. 4

Pressionar o botão OK.

174


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

As funções exportadas no código devem ser identificadas usando o modificador __declspec (dllexport) tanto para o Borland C++ ou Microsoft Visual C++. Por exemplo, o código que segue é legal tanto no C++ Builder e outros compiladores Windows C++. // MinhaDLL.cpp double dblValue(double); double halfValue(double); extern "C" __declspec(dllexport) double changeValue(double, bool); double dblValue(double value){ return 2.0 * value; }; double halfValue(double value){ return value / 2.0; } double changeValue(double value, bool whichOp){ return whichOp ? dblValue(value) : halfValue(value); }

No código acima, a função changeValue é exportada e então fica disponível para outras aplicações. As funções dblValue e halfValue são internas e não podem ser chamadas de fora da DLL.

8.6.1. Exemplo de DLL Depois de executar os 4 passos da seção anterior, utilize a opção de menu Save Project As... Crie uma pasta nova. Altere então os nomes padrão Unit1.cpp para UMinhaDLL.cpp e Project1.bpr para MinhaDLL.bpr.

A continuação, apague as linhas de comentário no UMinhaDLL.cpp para que o código apareça da seguinte forma: #include <windows.h> #pragma hdrstop //--------------------------------------------------------------------------#pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)

175


L

U

I

S

F

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

{ return 1; } //---------------------------------------------------------------------------

Acrescente o programa exemplo da seção anterior para aparecer como segue: //--------------------------------------------------------------------------#include <vcl.h> #include <windows.h> #pragma hdrstop double dblValue(double); double halfValue(double); extern "C" __declspec(dllexport) double changeValue(double, bool); //--------------------------------------------------------------------------#pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { return 1; } //--------------------------------------------------------------------------double dblValue(double value){ return 2.0 * value; }; //--------------------------------------------------------------------------double halfValue(double value){ return value / 2.0; } //--------------------------------------------------------------------------double changeValue(double value, bool whichOp){ return whichOp ? dblValue(value) : halfValue(value); } //---------------------------------------------------------------------------

O último passo é o de montar a DLL. Para fazer isto usar a opção de menu Project + Build MinhaDLL. Se não houver erros serão criados os arquivos mostrados na figura a seguir.

O arquivo MinhaDLL.DLL implementa a biblioteca que pode ser ligada de forma dinâmica ou estática. Os arquivos MinhaDLL.OBJ e MinhaDLL.LIB são

176


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

usados para efetuar ligações estáticas (se for o caso). Qualquer módulo poderá agora usar a função double changeValue(double, bool) exportada pela DLL.

8.7. Usando DLLs no C++Builder Uma DLL Windows pode ser usada em uma aplicação C++Builder como em qualquer aplicação C++.

8.7.1. Carregamento Estático Para carregar estaticamente uma DLL quando a aplicação C++Builder for carregada deve se ligar o arquivo de importação da biblioteca DLL em tempo de ligação. Para carregar uma DLL de forma estática quando for carregada a aplicação C++ Builder, deve se ligar o arquivo da biblioteca de importação para a tal DLL na aplicação C++ Builder em tempo de ligação. Para adicionar uma biblioteca de importação em uma aplicação C++ Builder, deve se abrir o arquivo make file (.BPR) da aplicação e adicionar o nome da biblioteca de importação na lista de bibliotecas designadas pela variável ALLIB. Se necessário, deve se adicionar o caminho. Para fazer isto abra o projeto da aplicação que irá chamar as funções da DLL. Visualizar então o Gerenciador de Projetos.

No Gerenciador de Projetos selecionar o arquivo .EXE e clicando com o botão direito do mouse, escolher a opção Add... do menu de contexto.

177


L

U

I

S

F

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

A partir daí abrirá uma janela Add to project. Alterar o tipo de arquivo para Library file (*.lib) como mostra a figura a seguir e então selecione o arquivo .LIB da biblioteca de funções a ser importada pressionando o botão Open.

Após isto a biblioteca será adicionada ao projeto da aplicação.

178


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

As funções exportadas da DLL ficarão então disponíveis para o uso pela aplicação. O protótipo das funções da DLL na aplicação deve usar o modificador de acesso __declspec (dllimport): __declspec(dllimport) tipo_de_retorno nome_da_função_importada(parâmetros);

8.7.2. Carregamento Dinâmico Para carregar dinamicamente uma DLL durante a execução da aplicação C++ Builder, pode se usar as funções da Windows API chamada LoadLibrary() para carregar a DLL, então usar a função GetProcAddress() para obter ponteiros para as funções individuais e finalmente a função FreeLibrary(). O programa exemplo a seguir mostra o carregamento dinâmico da DLL da seção anterior. //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop #include "UMain.h" HINSTANCE hinstLib; // manipulador de biblioteca static double(__stdcall *changeValue)(double,bool); // ponteiro para função //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { double x; // Pega um manipulador para o módulo DLL. hinstLib = LoadLibrary("DLL"); // Se o manipulador for válido, tenta pegar o endereço da função. if (hinstLib != NULL) { changeValue = (double (__stdcall *)(double,bool))GetProcAddress(hinstLib, "changeValue");

179


L

U

I

S

F

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

// Se o endereço da função é válido, chama a função da DLL. if (changeValue != NULL){ x = changeValue(4.9,0); } else{ // Se não foi possível chamar a função da DLL, emite uma mensagem. ShowMessage("Não consegui achar a função"); } } // Se não foi possível chamar a DLL, emite uma mensagem. else{ ShowMessage("Não consegui achar a DLL"); return; } // A seguir a DLL pára de existir no espaço de endereçamento da memória para este programa. FreeLibrary(hinstLib); } //---------------------------------------------------------------------------

Uma forma alternativa de carregar uma DLL durante a execução de uma aplicação C++Builder é incluir a biblioteca de importação, da mesma forma que o carregamento estático, e então setar a opção delay load linker na opção de menu Project + Options na aba Advanced Linker.

8.8. Criando DLLs contendo componentes VCL Uma das forças das DLLs é que a DLL criada com uma ferramenta de desenvolvimento pode freqüentemente ser usada por uma aplicação

escrita

usando uma ferramenta diferente. Quando as DLLs contiverem componentes VCL (tais como quadros) que podem ser utilizadas por outras aplicações, devem se fornecidas rotinas exportadas que usam convenções padronizadas de chamada evitando as chamadas especificas do C++ e que não requeiram que a aplicação que por ventura possa chamar a DLL de ter que suportar a biblioteca VCL para poder funcionar. Para criar componentes VCL que possam ser exportados devem ser usados os packages de runtime. Por exemplo, supor que se deseja criar uma DLL para mostrar a seguinte janela de diálogo:

180


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

O primeiro passo é iniciar um novo projeto com a opção de menu File + New... Então, escolher a opção DLL Wizard e o botão OK.

O projeto criado deverá aparecer no Gerenciador de projetos da seguinte forma:

181


L

U

I

S

F

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

A partir daí usar a opção de menu File + Save Project As... para salvar o projeto da DLL em uma nova pasta.

A seguir selecionar a opção de menu File + New Form. O Gerenciador de Programas ficará da seguinte forma:

Como passo seguinte, selecionar o arquivo DLLMain.cpp e usando o botão direto do mouse no menu de contexto escolher Remove From Project.

182


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

A continuação usar a opção de menu File + Save All e salve o arquivo default Unit1.cpp como DLLMain.cpp.

O Gerenciador de Programas ficará agora da seguinte forma:

A seguir, alterar o Form1 para ter a seguinte forma:

Renomeiar a propriedade Name do Label1 para LabelText, Button1 para YesButton e Button2 para NoButton. Criar um gerenciador para o evento OnClick para os dois botões. Depois disso, alterar os arquivos DLLMain.cpp e DLLMain.h para ficar como segue. Arquivo DLLMAIN.H // DLLMAIN.H //---------------------------------------------------------------------

183


L

U

I

S

F

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

#ifndef dllMainH #define dllMainH //--------------------------------------------------------------------#include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> //--------------------------------------------------------------------class TYesNoDialog : public TForm { __published: // IDE-managed Components TLabel *LabelText; TButton *YesButton; TButton *NoButton; void __fastcall YesButtonClick(TObject *Sender); void __fastcall NoButtonClick(TObject *Sender); private: // User declarations bool returnValue; public: // User declarations virtual __fastcall TYesNoDialog(TComponent *Owner); bool __fastcall GetReturnValue(); }; // exported interface function extern "C" __declspec(dllexport) bool InvokeYesNoDialog(); //--------------------------------------------------------------------extern TYesNoDialog *YesNoDialog; //--------------------------------------------------------------------#endif

Arquivo DLLMAIN.CPP // DLLMAIN.CPP //--------------------------------------------------------------------#include <vcl\vcl.h> #pragma hdrstop #include "dllMain.h" //--------------------------------------------------------------------#pragma resource "*.dfm" TYesNoDialog *YesNoDialog; //--------------------------------------------------------------------__fastcall TYesNoDialog::TYesNoDialog(TComponent *Owner) : TForm(Owner) { returnValue = false; } //--------------------------------------------------------------------void __fastcall TYesNoDialog::YesButtonClick(TObject *Sender) { returnValue = true; Close(); } //--------------------------------------------------------------------void __fastcall TYesNoDialog::NoButtonClick(TObject *Sender) { returnValue = false; Close(); } //--------------------------------------------------------------------bool __fastcall TYesNoDialog::GetReturnValue() { return returnValue; } //--------------------------------------------------------------------// função de interface de exportação padrão C++ com uso da VCL

184


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

bool InvokeYesNoDialog(){ bool returnValue; TYesNoDialog *YesNoDialog = new TYesNoDialog(NULL); YesNoDialog->ShowModal(); returnValue = YesNoDialog->GetReturnValue(); delete YesNoDialog; return returnValue; } //---------------------------------------------------------------------

Para criar a DLL usar a opção de menu Project + Build DLLVCL e o sistema irá gerar os arquivos DLLVCL.DLL, DLLVLC.LIB e DLL.OBJ. O código deste exemplo mostra uma janela de diálogo e armazena o valor true no membro privado de dados returnValue se o botão Sim for pressionado. Caso contrário returnValue será false. A função public GetReturnValue() retorna o valor de returnValue quando chamada. Para invocar a janela de diálogo e determinar qual botão foi pressionado, a aplicação invocadora chama a função exportada InvokeYesNoDialog().

Esta

função é declarada em DLLMAIN.H como uma função de exportação usando a ligação C (evitando o padrão C++) e outras convenções que possam limitar o uso da DLL por programas feitos em outras linguagens. A função é definida em DLLMAIN.CPP. Pelo uso de funções padrões C como interface na DLL, qualquer aplicação invocadora, criada ou não com C++ Builder, pode usar a DLL. A funcionalidade da VCL requerida para suportar a janela de diálogo é ligada junto à DLL em si, e a aplicação invocadora não precisará saber nada a respeito disso. Notar que quando criar uma DLL que usa a VCL, os componentes requeridos são ligados dentro da DLL resultando em uma quantidade razoável de sobrecarga no tamanho da mesma. O impacto desta sobrecarga no tamanho total da aplicação pode ser minimizado pela combinação de vários componentes em uma única DLL que somente precisa uma única cópia dos componentes de suporte da VCL.

185


L

U

I

S

F

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

8.9. Criando uma Aplicação que chama funções da DLL contendo componentes VCL Para poder usar a DLL da seção anterior, criar uma nova aplicação usando a opção de menu File + New Application. Como de praxe, continue a salvar a nova aplicação em um novo diretório. Salvar a nova aplicação com novos nomes para o arquivo .CPP e .PRJ no mesmo diretório onde estão os arquivos da DLL. Para este exemplo,

salvar

Unit1.cpp

como

UChamaDLL.cpp

e

Project1.bpr

como

ChamaDLLVCL.bpr. A seguir, adicionar a biblioteca de exportação da DLL, usando o botão direito do mouse no Gerenciador de Projeto, chamada DLLVCL.LIB. Assim o gerenciador de programas deverá parecer como:

Alterar o Form1 para parecer da seguinte forma:

Crie um gerenciador de eventos para o OnClick do botão e acrescente a declaração da função de exportação InvokeYesNoDialog() que vem da DLL. O código de UChamaDLLVCL.cpp deverá aparecer como segue: //--------------------------------------------------------------------------#include <vcl.h> #pragma hdrstop

186


E

N

G

E

N

H

A

R

I

A

D

E

P

R

O

C

E

S

S

A

M

E

N

T

O

D

I

G

I

T

A

L

I I

#include "UChamaDLLVCL.h" //--------------------------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; extern "C" __declspec(dllimport) bool InvokeYesNoDialog(void); //--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender) { bool bOption = InvokeYesNoDialog(); if (bOption) Label1->Caption = "Opção escolhida na DLL: SIM"; else Label1->Caption = "Opção escolhida na DLL: NÃO"; } //---------------------------------------------------------------------------

Executando o programa tem-se:

Pressionando o botão Chama Diálogo da DLL, invocando a função exportada:

Pressionando o botão Sim, por exemplo:

187


L

U

I

S

F

E

R

N

A

N

D

O

E

S

P

I

N

O

S

A

C

O

C

I

A

N

A DLL retorna valor true, a janela da DLL ĂŠ fechada e o aplicativo altera o conteĂşdo de Label1->Text.

188


08-DLL-C++BuilderV10