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