Como carregar funções de uma DLL dinamicamente sem utilizar macros

1,193 views
Skip to first unread message

Marcio Gil

unread,
Nov 28, 2009, 11:17:14 AM11/28/09
to C-cpp-brasil
Eu tenho uma DLL com dezenas de funções, e gostaria de trabalhar com
elas estaticamente (linkando o sistema com a LIB correspondente, o
que amarra o sistema a uma versão específica da DLL) ou
dinamicamente (carregando as funções da DLL durante a execução do
sistema).

Para facilitar e tornar o código mais limpo eu utilizei macros:

(no header)

#ifdef DYNDLL // DLL dinâmica
#define DLLPROC( T, N ) extern T __stdcall (* N)
#else // DLL estática
#define DLLPROC( T, N ) extern "C" __stdcall T N
#endif

DLLPROC( int, Bematech_FI_VerificaImpressoraLigada )(void);
DLLPROC( int, Bematech_FI_RetornoImpressora )(int* ACK, int* ST1,
int* ST2);
DLLPROC( int, Bematech_FI_DataHoraImpressora )(char* Data, char*
Hora);
DLLPROC( int, Bematech_FI_DataMovimento )(char* DataMovimento);
DLLPROC( int, Bematech_FI_VerificaTruncamento )(char*
FlagTruncamento);
//(...)

(no arquivo fonte)

#ifdef DYNDLL
#define DLLDEF( T, N ) T __stdcall (* N)
DLLDEF( int, Bematech_FI_VerificaImpressoraLigada )(void) = NULL;
DLLDEF( int, Bematech_FI_RetornoImpressora )(int* ACK, int* ST1,
int* ST2) = NULL;
DLLDEF( int, Bematech_FI_DataHoraImpressora )(char* Data, char*
Hora) = NULL;
DLLDEF( int, Bematech_FI_VerificaTruncamento )(char*
FlagTruncamento) = NULL;
//(...)

static HINSTANCE DLLHandle = NULL;
static bool DLLOK = false;

bool LoadDLL( void )
{
#define LOAD_PROC( HANDLER, PROC, REQ ) \
reinterpret_cast<FARPROC>(PROC) = GetProcAddress( \
static_cast<HMODULE>(HANDLER), #PROC ); \
if (REQ && PROC == NULL) erro += #PROC "\n"

std::string erro;

if (DLLOK && DLLHandle != NULL)
return true;

try
{
DLLOK = false;
DLLHandle = LoadLibrary( "BemaFI32.dll" );

if (DLLHandle == NULL)
{ erro = "BemaFI32.dll não carregada";
MessageBox( NULL, erro.c_str(), "Erro!",
MB_OK|MB_ICONERROR|MB_TOPMOST );
return false;
}

LOAD_PROC( DLLHandle, Bematech_FI_VerificaImpressoraLigada, true
);
LOAD_PROC( DLLHandle, Bematech_FI_RetornoImpressora, true );
LOAD_PROC( DLLHandle, Bematech_FI_DataHoraImpressora, true );
LOAD_PROC( DLLHandle, Bematech_FI_DataMovimento, true );
LOAD_PROC( DLLHandle, Bematech_FI_VerificaTruncamento, false );
// (...)

if (!erro.empty())
{ erro = std::string("Falha carregando BemaFI32.dll:\n\n") +
erro;
MessageBox( NULL, erro.c_str(), "Erro!",
MB_OK|MB_ICONERROR|MB_TOPMOST );
return false;
}
}
catch (...)
{ erro = "Falha carregando BemaFI32.dll";
MessageBox( NULL, erro.c_str(), "Erro!",
MB_OK|MB_ICONERROR|MB_TOPMOST );
return false;
}

if (DLLHandle == NULL)
return false;

DLLOK = true;

return true;
}
#endif

Com a macro LOAD_PROC foi possível simplificar disto:

Bematech_FI_RetornoImpressora =
(int __stdcall (*)(int*,int*,int*))GetProcAddress(
DLLHandle, "Bematech_FI_RetornoImpressora" );
if (Bematech_FI_RetornoImpressora == NULL)
erro += "Bematech_FI_RetornoImpressora\n"


para isto:

LOAD_PROC( DLLHandle, Bematech_FI_RetornoImpressora, true );


Pergunta: seria possível fazer algo parecido sem utilizar macros? Ou
no mínimo eu teria que repetir o nome de cada função duas vezes?

Também não fiquei muito satisfeito com aquele reinterpret_cast na
macro.


Ronaldo Faria Lima

unread,
Nov 28, 2009, 7:06:45 PM11/28/09
to ccppb...@googlegroups.com
Márcio,

leia este artigo do MSDN:

http://msdn.microsoft.com/en-us/library/ms684175%28VS.85%29.aspx

As funções da API Win32 que você precisa são: LoadLibrary, FreeLibrary
e GetProcAddress.

As macros que você demonstrou no seu código-fonte existem para
compatibilizar a API Win32, que é escrita em C, para o seu compilador
C++.

Uma coisa que você pode fazer é encapsular a DLL e suas funções dentro
de uma classe, ativando as funções da DLL através de métodos
específicos. Você está montando interfaces com impressoras fiscais da
Bematech? Eu reconheço esses entry points...

Abraços!

Ronaldo

2009/11/28 Marcio Gil <marci...@bol.com.br>:

P.

unread,
Nov 29, 2009, 8:32:37 AM11/29/09
to ccppbrasil
On 28 nov, 14:17, "Marcio Gil" <marciom...@bol.com.br> wrote:

> Com a macro LOAD_PROC foi possível simplificar disto:
>
> Bematech_FI_RetornoImpressora =
>     (int __stdcall (*)(int*,int*,int*))GetProcAddress(
>         DLLHandle, "Bematech_FI_RetornoImpressora" );
> if (Bematech_FI_RetornoImpressora == NULL)
>   erro += "Bematech_FI_RetornoImpressora\n"
>
> para isto:
>
>     LOAD_PROC( DLLHandle, Bematech_FI_RetornoImpressora, true );
>
> Pergunta: seria possível fazer algo parecido sem utilizar macros? Ou
> no mínimo eu teria que repetir o nome de cada função duas vezes?

Não entendo perfeitamente o que essas macros estão fazendo.
Como o usuário da interface faz para chamar alguma função?

Acho que eu resolveria o problema de forma similar, normalizando a
interface para um conjunto de ponteiro-para-função incondicionalmente.
Assim sendo, a diferença entre cada configuração é a forma como esse
conjunto é inicializado; se a linkagem é estática, a inicialização
pode tomar os endereços de cada função diretamente com o operador &;
se a linkagem é dinâmica, deve usar GetProcAddress exatamente como o
seu programa faz.

O header seria algo como:

// Interface!
typedef struct {
int (*Bematech_FI_RetornoImpressora)(int*, int*);
// etc.
} IBematech;

void IBematech_initialize ();

IBematech* IBematech_get ();

Com um source mais ou menos assim:

// Singleton instance!
struct IBematech s_bematech;

#ifndef DYNDLL
# include <BematechStuff.h>
#endif

void IBematech_initialize ()
{
#ifdef DYNDLL

s_bematech.Bematech_FI_RetornoImpressora =
(int __stdcall (*)(int*,int*,int*))GetProcAddress(
DLLHandle, "Bematech_FI_RetornoImpressora" );

#else

s_bematech.Bematech_FI_RetornoImpressora =
&Bematech_FI_RetornoImpressora;

#endif
}

IBematech* IBematech_get () { return s_bematech; }

> Também não fiquei muito satisfeito com aquele reinterpret_cast na
> macro.

A única alternative é usar um cast clássico com ().
Funções como dlopen e GetProcAddress invariavelmente retornam um void*
e não temos sistema melhor à vista.

--
P.

Thiago Adams

unread,
Nov 30, 2009, 6:22:49 AM11/30/09
to ccppbrasil
Olá Márcio,

Antes de mais nada, qual compilador você usa?
Se estiver usando o VC++ dê uma olhada no __declspec(dllexport) e
__declspec(dllimport) que talvez faça automaticamente tudo o que você
quer.

Fazendo na mão , a maneira que eu aconselho é a seguinte.
Faça um header único sem macros.

(header)
void funcao();

Depois implemente uma função que chame a função da dll no cpp.

(cpp)

namespace
{
void (*pfuncao)() = NULL;
}


void funcao()
{
//carrega a dll e os ponteiros..
CheckAndLoadIfNecessary();
pfuncao();
}

Desta forma é mais seguro e mais encapsulado do que ter o ponteiro de
função no header.
No caso da lib não muda nada.

-x-

Esta tarefa de gerar o código de chamada da dll é puramente mecanico,
e se alguém tiver interesse em fazer um programa gerador eu me
disponho a ajudar.

Ronaldo Faria Lima

unread,
Nov 30, 2009, 7:33:09 AM11/30/09
to ccppb...@googlegroups.com
Olá, Thiago!

Esse é um projetinho interessante, hein? Acho que daria para criar um
esquema bem interessante para interfaces com DLLs em C++...

2009/11/30 Thiago Adams <thiago...@gmail.com>:

Thiago Adams

unread,
Nov 30, 2009, 7:42:32 AM11/30/09
to ccppbrasil
Dá para fazer várias coisas legais.
Poderia gerar somente a chamada de funções (assim como é o caso aqui)

Poderia ser aplicado para classes e fazer uma tranformação em HANDLE.
Exemplo:

class X
{
X(){}
void f(int);
}

seria transformado em :

XHANDLE Create();
f(XHANDLE, int)

inclusive já daria para criar um wrapper do lado do codigo que usa a
dll para usar estes handles .

Marcio Gil

unread,
Nov 30, 2009, 12:17:28 PM11/30/09
to ccppb...@googlegroups.com

> -----Original Message-----
> From: Ronaldo Faria Lima
>
> Márcio,
>
> leia este artigo do MSDN:
>
> http://msdn.microsoft.com/en-us/library/ms684175%28VS.85%29.aspx
>
> As funções da API Win32 que você precisa são: LoadLibrary,
FreeLibrary
> e GetProcAddress.
>

São estas as funções que eu uso.

> As macros que você demonstrou no seu código-fonte existem para
> compatibilizar a API Win32, que é escrita em C, para o seu
compilador
> C++.
>

Não é por causa do compilador, é para manter a compatibilidade com
os sistemas em uso com linkagem estática (via LIB) enquanto eu testo
as vantagens de carregar a DLL dinamicamente. Mas acho que vou
descartar o método estático porque isto amarra o sistema a uma única
versão da DLL.

Mas na parte que carrega os endereços das funções eu criei a macro
LOAD_PROC para simplificar o código e diminuir digitação. São 139
funções, então seriam 4 linhas por função ou 556 linhas de código.
Como cada função tem parâmetros distintos, as chances de eu cometer
alguns erros de digitação são muito grandes. Imagine então se eu
precisar modificar alguma coisa?

> Uma coisa que você pode fazer é encapsular a DLL e suas funções
dentro
> de uma classe, ativando as funções da DLL através de métodos
> específicos. Você está montando interfaces com impressoras fiscais
da
> Bematech? Eu reconheço esses entry points...
>

Exato.


Marcio Gil

unread,
Nov 30, 2009, 12:18:55 PM11/30/09
to ccppb...@googlegroups.com

> -----Original Message-----
> From: P.
>
>
> On 28 nov, 14:17, "Marcio Gil" <marciom...@bol.com.br> wrote:
>
> > Com a macro LOAD_PROC foi possível simplificar disto:
> >
> > Bematech_FI_RetornoImpressora =
> >     (int __stdcall (*)(int*,int*,int*))GetProcAddress(
> >         DLLHandle, "Bematech_FI_RetornoImpressora" );
> > if (Bematech_FI_RetornoImpressora == NULL)
> >   erro += "Bematech_FI_RetornoImpressora\n"
> >
> > para isto:
> >
> >     LOAD_PROC( DLLHandle, Bematech_FI_RetornoImpressora, true );
> >
> > Pergunta: seria possível fazer algo parecido sem utilizar
macros? Ou
> > no mínimo eu teria que repetir o nome de cada função duas vezes?
>
> Não entendo perfeitamente o que essas macros estão fazendo.

A macro LOAD_PROC retira a necessidade de escrever o nome da função
quatro vezes, reduzindo as chances de eu errar a digitação e
diminuindo a poluição. Você pode fazer as substituições manualmente:

#define LOAD_PROC( HANDLER, PROC, REQ ) \
reinterpret_cast<FARPROC>(PROC) = GetProcAddress( \
static_cast<HMODULE>(HANDLER), #PROC ); \
if (REQ && PROC == NULL) erro += #PROC "\n"

LOAD_PROC( DLLHandle, Bematech_FI_RetornoImpressora, true );

vira:

reinterpret_cast<FARPROC>(Bematech_FI_RetornoImpressora) =
GetProcAddress(
static_cast<HMODULE>(DLLHandle), "Bematech_FI_RetornoImpressora"
);
if (true && Bematech_FI_RetornoImpressora == NULL) erro +=
"Bematech_FI_RetornoImpressora" "\n"


> Como o usuário da interface faz para chamar alguma função?
>

Posso colocar no início do programa:

while (!LoadDLL())
{ if (MessageBox( NULL,
"Falha carregando a bliblioteca.", "Erro!",
MB_RETRYCANCEL|MB_ICONERROR|MB_TOPMOST ) == IDCANCEL)
abort();
}

// Agora eu posso utilizar as funções com segurança:

int retorno = Bematech_FI_VerificaImpressoraLigada();

// Mas se a função não é essencial:

char result[2] = "1\0"; // Resultado padrão para truncamento
// se a função não estiver
disponível
if (Bematech_FI_VerificaTruncamento == NULL)
retorno = 0;
else
retorno = Bematech_FI_VerificaTruncamento( result );

> Acho que eu resolveria o problema de forma similar, normalizando a
> interface para um conjunto de ponteiro-para-função
incondicionalmente.

Mas aí, ou as funções para uso teriam nomes diferentes dos da DLL,
ou eu só teria acesso via singleton, como você sugere.

O cast classico faz exatamente o mesmo que o reinterpret_cast. Está
é sempre a minha última alternatica pois não trazem verificação
alguma, já os outros tipos de cast, como o static_cast, ainda
verificam se a conversão de tipo faz sentido. A função
GetProcAddress não retorna um 'void*' mas um 'int(__stdcall *)()',
então é possível fazer um static_cast do FARPROC para qualquer
ponteiro de função tipo __stdcall retornando um inteiro, mas não o
contrário.

Talvez com um template eu resolva isto...

Marcio Gil

unread,
Nov 30, 2009, 12:21:50 PM11/30/09
to ccppb...@googlegroups.com

> -----Original Message-----
> From: Thiago Adams
>
>
> Olá Márcio,
>
> Antes de mais nada, qual compilador você usa?
> Se estiver usando o VC++ dê uma olhada no __declspec(dllexport) e
> __declspec(dllimport) que talvez faça automaticamente tudo o que
você
> quer.
>

Eu ainda uso o C++ Builder, mas vi que o meu compilador traz suporte
a este tipo de declaração. Só que o manual não traz informação
alguma sobre para que servem nem como se usa. Como funciona?

> Fazendo na mão , a maneira que eu aconselho é a seguinte.
> Faça um header único sem macros.
>
> (header)
> void funcao();
>
> Depois implemente uma função que chame a função da dll no cpp.
>
> (cpp)
>
> namespace
> {
> void (*pfuncao)() = NULL;
> }
>
>
> void funcao()
> {
> //carrega a dll e os ponteiros..
> CheckAndLoadIfNecessary();
> pfuncao();
> }
>
> Desta forma é mais seguro e mais encapsulado do que ter o ponteiro
de
> função no header.
> No caso da lib não muda nada.
>

Concordo com você, ter uma função seria mais seguro do que trabalhar
diretamente com o ponteiro de função. Mas como você sugere
implementar este CheckAndLoadIfNecessary()? Imaginei assim:


static HINSTANCE DLLHandle = NULL;

template <typename TPROC>
void CheckAndLoadIfNecessary( TPROC &proc, const char *proc_name,
bool req )


{
if (DLLHandle == NULL) {
DLLHandle = LoadLibrary( "BemaFI32.dll" );
if (DLLHandle == NULL)

throw std::exception( "Falha carregando a DLL" );
}
if (proc == NULL) {
proc = static_cast<TPROC>(GetProcAddress( DLLHandle, proc_name
));
if (req && proc == NULL)
throw std::exception( "Falha carregando a função" );
}
}

int Bematech_FI_RetornoImpressora(int* ACK, int* ST1,int* ST2)
{
static int __stdcall
(*pBematech_FI_RetornoImpressora)(int*,int*,int*) = NULL;
CheckAndLoadIfNecessary( pBematech_FI_RetornoImpressora,
"Bematech_FI_RetornoImpressora", true );
return pBematech_FI_RetornoImpressora( ACK, ST1, ST2 );
}

É isto que você tinha em mente?

> -x-
>
> Esta tarefa de gerar o código de chamada da dll é puramente
mecanico,
> e se alguém tiver interesse em fazer um programa gerador eu me
> disponho a ajudar.
>

Mas isto não levaria ao mesmo resultado de utilizar o
preprocessador?


P.S.: pessoal, muito obrigado a todos pelas sugestões, elas estão
sendo muito úteis.


Emerson de Freitas Barcelos

unread,
Nov 30, 2009, 1:00:03 PM11/30/09
to ccppb...@googlegroups.com
Qual seria a vantagem de se livrar do pré-processador nessa situação?
Se até a microsoft ainda o utiliza para essa função ?

A interface da DLL muda entre versões ? (a assinatura das funções,
existência ou não de um ponto de entrada ?)

Não havendo essas restrições, creio que a solução padrão deveria funcionar:

1 - Uma variável de pré-processador (vc pode defini-la de acordo com uma
configuração/target no projeto)
/D:BEMATECH_DLL

2 . Usar um arquivo de cabeçalho uma instrução do tipo:

#ifdef BEMATCH_DLL
#define BEMATECH_API __stdcall declspec(dllimport)
#else
#define BEMATECH_API __stdcall declspec(dllexport)
#endif

e declarar as funções dessa forma

int BEMATECH_API UmaFuncaoQualquer(int valor1, int valor2);


Para o projeto cliente da biblioteca vc pode definir configurações que
utilizem ou a biblioteca estática ou a biblioteca de importação da DLL
(definindo apropriadamente a variável de pré-processador).


--------------------------------------------------
From: "Marcio Gil" <marci...@bol.com.br>
Sent: Monday, November 30, 2009 3:21 PM
To: <ccppb...@googlegroups.com>
Subject: [ccppbrasil] Re: Como carregar funções de uma DLL dinamicamente sem
utilizar macros

Ronaldo Faria Lima

unread,
Nov 30, 2009, 1:07:56 PM11/30/09
to ccppb...@googlegroups.com
Olá, Emerson.

A principal vantagem de realizar a carga dinâmica de uma DLL na mão é
o controle de erros do seu programa, além da validação das DLLs, com o
fim de evitar injeção de DLLs que nada tem a ver com o seu programa.
Se você fizer a carga através do loader default, se a DLL não estiver
disponível para carga, seu programa termina de maneira descontrolada,
inclusive abrindo uma dialog que nada tem a ver com a UI do seu
próprio sistema.

Não é incomum usuários de sistema que injetam DLLs estranhas na
aplicação visando interceptar ações do seu sistema. No caso do nosso
amigo Márcio, que está tratando com uma impressora fiscal, seria
possível um engraçadinho fraudar a impressora injetando uma DLL para
fazer um "caixa 2" - eu já trabalhei com sistemas de ponto-de-venda no
passado e isso acontecia, acredite-me.

Abraços,

Ronaldo

2009/11/30 Emerson de Freitas Barcelos <emerson...@yahoo.com.br>:

Thiago Adams

unread,
Nov 30, 2009, 1:08:13 PM11/30/09
to ccppbrasil
>
> > Olá Márcio,
>
> > Antes de mais nada, qual compilador você usa?
> > Se estiver usando o VC++ dê uma olhada no __declspec(dllexport) e
> > __declspec(dllimport) que talvez faça automaticamente tudo o que
> você
> > quer.
>
> Eu ainda uso o C++ Builder, mas vi que o meu compilador traz suporte
> a este tipo de declaração. Só que o manual não traz informação
> alguma sobre para que servem nem como se usa. Como funciona?

O __declspec(dllexport) / __declspec(dllimport) são usados na forma
de macro assim:

(header)

MINHADLL_API int funcao(void);

A macro MINHADLL_API vai ser __declspec(dllexport) no codigo da dll e
__declspec(dllimport) no codigo de quem usa a dll.
Quando o compilador ve o export ele sabe que deve exportar aquelas
funções na dll.
O visual C++ também gera uma lib estática com o codigo necessário
para carregar e chamar as funções da dll e o codigo saberá como usar
esta lib através do import.
No fim, o uso da dll fica igual a uma lib estática. Este esquema
também é usado para exportar classes. O código gerado é menos
compatível com outros compiladores então eu aconselho usar dll normal
mesmo com o esquema de handles ou interfaces COM.(Exemplos de como
fazer isso são a GDI+ e o directX)
É mais ou menos isso... Mas o load LoadLibrary pode ser feito 1 vez
só, fora da função que faz o GetProcAddress. Este checkandload pode
carregar a dll e todos os ponteiros internos de uma vez só. Voce pode
ter um "Init" na sua biblioteca para carregar tudo, ou pode deixar
"lazy" que carrega na 1 chamada. Este modo é util também quando se
quer ter uma dll que seja opcional.
Voce pode usar um esquema assim com macros também. Só, como eu disse,
acho que não é o caso de expor o ponteiro de funcao no header.


A vantagem do gerador é que se abre um mundo de opções :)

Por exemplo, eu gostaria de definir a interface de funçòes e depois
dizer:

- cria um wrapper de dll para mim usando handles
- criar um wrapper de dll usando estilo COM
- criar um wrapper de dll usando COM propriamente dito.
- criar um wrapper para C++/CLI
- etc...


Emerson de Freitas Barcelos

unread,
Nov 30, 2009, 1:46:01 PM11/30/09
to ccppb...@googlegroups.com
A primeira parte é comportamento normal do sistema na falta de um modulo
requerido pelo executável.

Mas na segunda parte... Se quiser mesmo se proteger nessas situações,
deve-se considerar que as funções GetProcAddress, LoadLibrary também são
passíveis de API hooking.

--------------------------------------------------
From: "Ronaldo Faria Lima" <ronaldo.f...@gmail.com>
Sent: Monday, November 30, 2009 4:07 PM

P.

unread,
Nov 30, 2009, 1:53:19 PM11/30/09
to ccppbrasil
On 30 nov, 15:17, "Marcio Gil" <marciom...@bol.com.br> wrote:

> Não é por causa do compilador, é para manter a compatibilidade com
> os sistemas em uso com linkagem estática (via LIB) enquanto eu testo
> as vantagens de carregar a DLL dinamicamente. Mas acho que vou
> descartar o método estático porque isto amarra o sistema a uma única
> versão da DLL.

Você não precisa usar LoadLibrary pra desacoplar o programa da
biblioteca dessa maneira.
Se você ligar seu programa à biblioteca de stubs que acompanha a
biblioteca dinâmica, seu programa carregará a versão da biblioteca que
estiver presente no sistema em tempo de carga, satisfazendo a esse
requisito de desacoplamento que você menciona.

Usando LoadLibrary, você não está realmente realizando link-edição
dinâmica; não há link-edição necessária entre seu programa e a
biblioteca dinâmica.
Essa ocorre um nível de abstração acima e se realiza no momento da
execução da chamada de função LoadLibrary.

Estou fazendo uma distinção entre três modos de associar seu programa
à biblioteca: link-edição de biblioteca estática, que incorpora o
código da biblioteca ao programa; link-edição de biblioteca dinâmica,
que incorpora um identificador da biblioteca ao programa; e carga em
tempo de execução com LoadLibrary. Você parece estar considerando a
primeira e terceira alternativas, e deixando a segunda de lado. Não
entendi se isso é proposital ou não.

--
P.

Marcio Gil

unread,
Nov 30, 2009, 2:46:59 PM11/30/09
to ccppb...@googlegroups.com

> -----Original Message-----
> From: P.
>
>
> On 30 nov, 15:17, "Marcio Gil" <marciom...@bol.com.br> wrote:
>
> > Não é por causa do compilador, é para manter a compatibilidade
com
> > os sistemas em uso com linkagem estática (via LIB) enquanto eu
testo
> > as vantagens de carregar a DLL dinamicamente. Mas acho que vou
> > descartar o método estático porque isto amarra o sistema a uma
única
> > versão da DLL.
>
> Você não precisa usar LoadLibrary pra desacoplar o programa da
> biblioteca dessa maneira.
> Se você ligar seu programa à biblioteca de stubs que acompanha a
> biblioteca dinâmica, seu programa carregará a versão da biblioteca
que
> estiver presente no sistema em tempo de carga, satisfazendo a esse
> requisito de desacoplamento que você menciona.
>

Este termo ("Stubs") eu não conhecia. Só constatei que se a versão
da DLL do cliente fosse diferente da versão da qual gerei minha LIB
o programa não funcionava. Imagino que o endereço das funções da DLL
fiquem guardadas no meu executável da forma como fiz inicialmente.

> Usando LoadLibrary, você não está realmente realizando link-edição
> dinâmica; não há link-edição necessária entre seu programa e a
> biblioteca dinâmica.
> Essa ocorre um nível de abstração acima e se realiza no momento da
> execução da chamada de função LoadLibrary.
>

Isso eu sei, em nenhum momento eu disse "link-edição dinâmica", eu
usei o termo "carregar a DLL dinamicamente".

> Estou fazendo uma distinção entre três modos de associar seu
programa
> à biblioteca: link-edição de biblioteca estática, que incorpora o
> código da biblioteca ao programa; link-edição de biblioteca
dinâmica,
> que incorpora um identificador da biblioteca ao programa; e carga
em
> tempo de execução com LoadLibrary. Você parece estar considerando
a
> primeira e terceira alternativas, e deixando a segunda de lado.
Não
> entendi se isso é proposital ou não.
>

Proposital não pode ser, já que não conheço este segundo método ;-)

Mas parece que vale a pena conhecer e descobrir suas vantagens.
Inicialmente tenho a impressão que carregar em tempo de execução com
GetProcAddress me traz mais controle, principalmente porque qualquer
perda de desempenho com chamadas e verificações é insignificante
perto do tempo de execução das rotinas da DLL em si.

Obrigado pelas informações.

P.

unread,
Nov 30, 2009, 4:04:06 PM11/30/09
to ccppbrasil
On 30 nov, 17:46, "Marcio Gil" <marciom...@bol.com.br> wrote:

> Proposital não pode ser, já que não conheço este segundo método ;-)
>
> Mas parece que vale a pena conhecer e descobrir suas vantagens.
> Inicialmente tenho a impressão que carregar em tempo de execução com
> GetProcAddress me traz mais controle, principalmente porque qualquer
> perda de desempenho com chamadas e verificações é insignificante
> perto do tempo de execução das rotinas da DLL em si.

Realmente, entrar na nóia do overhead nesse caso não parece fazer
muito sentido.
Mas se você não possui realmente um requisito que exija carregar a
biblioteca por LoadLibrary, o segundo método pode ser interessante.

No Windows, quando você produz uma biblioteca dinâmica, o linker
produz dois arquivos: um arquivo foo.dll que é a própria biblioteca
dinâmica, e um outro arquivo foo.lib que é uma biblioteca de stubs.
Apesar deste último arquivo se chamar foo.lib, quando ele é produzido
pela link-edição de uma biblioteca dinâmica, ele não contém realmente
o código da biblioteca -- o que você pode facilmente atestar pelo
tamanho do arquivo.

Se você incluir esta biblioteca de stubs na lista de bibliotecas do
seu programa -- exatamente como faria com uma biblioteca estática -- o
seu programa estará ligado dinamicamente com a biblioteca dinâmica.
Isto é: o programa possuirá indicações explícitas de dependência nesta
biblioteca e lacunas que serão preenchidos pelo carregador no momento
da execução.

Quando o programa for carregado, o carregador do sistema fará algo
muito similiar ao que LoadLibrary faz, com a diferença de ser um
processo automático, onde os símbolos e endereços são resolvidos uma
vez só. O carregador procura pelo arquivo foo.dll nos mesmos lugares
que LoadLibrary procura.

Esse esquema possui uma estrutura tal que o código-fonte não toma
qualquer conhecimento sobre a decisão de usar uma determinada
biblioteca de forma estática ou dinãmica, e as definições do linker
são teoricamente idênticas.

Na prática, as definições do linker vão variar um pouco de acordo com
a escolha entre estática e dinâmica, pela mesma razão que variam de
acordo com a escolha de uma biblioteca compilada para debug e release:
é conveniente ter cópias de várias configurações no mesmo sistema para
experimentação, e cada configuração acaba morando em um diretório
diferente.

--
P.

Marcio Gil

unread,
Nov 30, 2009, 6:26:14 PM11/30/09
to ccppb...@googlegroups.com

Imagino então que devo ter feito alguma coisa de errado na minha
primeira tentativa de ligar a DLL a minha aplicação. Como a DLL não
veio com um arquivo .LIB compatível, eu gerei um com a ferramenta
IMPLIB:

---
Borland Implib Version 3.0.22 Copyright (c) 1991, 2000 Inprise
Corporation

Syntax: IMPLIB [options] libname[.lib] [@respfile | srcname]
[srcname ...]
Options:
-a Add '_' alias for MS flavor cdecl functions
-c Case sensitive symbols
-f Force imports by name (with hints)
-w No Warnings

Respfile may contain a list of source files to process.
Wildcards are ok for .DLL and .DEF file names.
---

Imagino que devia ter utilizado a opção "-f" (Force imports by name)
para obter como resultado uma aplicação compatível com várias
versões da DLL.

Será que esta opção faz aquilo que você se referia? "link-edição de
biblioteca estática" (sem -f); "link-edição de biblioteca dinâmica"
(com -f), seria isto?


P.

unread,
Dec 3, 2009, 12:18:16 PM12/3/09
to ccppbrasil
On 30 nov, 21:26, "Marcio Gil" <marciom...@bol.com.br> wrote:

> Imagino então que devo ter feito alguma coisa de errado na minha
> primeira tentativa de ligar a DLL a minha aplicação. Como a DLL não
> veio com um arquivo .LIB compatível, eu gerei um com a ferramenta
> IMPLIB:

Ah. Sim. Eu estou usando expressões malucas pra falar sobre isso
porque os nomes das coisas variam tanto.
Realmente, essa biblioteca de stubs no Windows é frequentemente
chamada a "import library".

"import libraries" não existem no mundo ELF -- como o Linux.

> Imagino que devia ter utilizado a opção "-f" (Force imports by name)
> para obter como resultado uma aplicação compatível com várias
> versões da DLL.
>
> Será que esta opção faz aquilo que você se referia? "link-edição de
> biblioteca estática" (sem -f); "link-edição de biblioteca dinâmica"
> (com -f), seria isto?

Não tenho certeza.

Porém, pelo que estou entendendo, você está gerando uma "import
library" e ligando seu programa a ela.
Se não fossem os relatos de dificuldades, eu diria que você já está
fazendo tudo que precisa fazer.

Uma maneira de investigar essa situação é de trás pra frente.
O Visual Studio e o Platform SDK contém uma ferramenta chamada
"Dependency Walker" -- o nome do programa no sistema de arquivos deve
ser depends.exe .
Você pode "abrir" seu arquivo executável nessa ferramenta e ver ali
uma lista das bibliotecas dinâmicas de que ela depende.
Se a biblioteca em questão constar nessa lista, então realmente a
ligação já está OK.

Resta então resolver o problema das declarações C ou C++ no código-
fonte.
Sobre isso, os colegas da lista já discutiram anteriormente nesse
thread -- o uso de __declspec(dllimport).

Funções declaradas no programa cuja definição existirá em uma
biblioteca dinâmica -- encontrada apenas em tempo de carga -- devem
ser anotadas com o atributo __decl(dllimport) para que o compilador e
o ligador façam a coisa certa.
Isso é uma peculiaridade do ambiente de desenvolvimento do Windows.

O objetivo é declarar as funções que estão definidas nessa biblioteca
em algum cabeçalho e usá-las naturalmente -- exatamente como fazemos
com printf.

--
P.
Reply all
Reply to author
Forward
0 new messages