Estou dando uma olhada nos arquivos .dll, entendo seu uso e estou tentando entender como usá-los.
Eu criei um arquivo .dll que contém uma função que retorna um inteiro chamado funci ()
usando este código, eu (acho) importei o arquivo .dll para o projeto (não há reclamações):
#include <windows.h>
#include <iostream>
int main() {
HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop \\fgfdg\\dgdg\\test.dll");
if (hGetProcIDDLL == NULL) {
std::cout << "cannot locate the .dll file" << std::endl;
} else {
std::cout << "it has been called" << std::endl;
return -1;
}
int a = funci();
return a;
}
# funci function
int funci() {
return 40;
}
No entanto, quando tento compilar este arquivo .cpp que acho que importou o .dll, recebo o seguinte erro:
C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp||In function 'int main()':|
C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp|16|error: 'funci' was not declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|
Eu sei que um .dll é diferente de um arquivo de cabeçalho, então sei que não posso importar uma função como essa, mas é o melhor que pude sugerir para mostrar que tentei.
Minha pergunta é: como posso usar o hGetProcIDDLL
ponteiro para acessar a função dentro do .dll.
Espero que essa pergunta faça sentido e eu não esteja latindo em uma árvore errada de novo.
Respostas:
LoadLibrary
não faz o que você pensa que faz. Ele carrega a DLL na memória do processo atual, mas não importa magicamente as funções definidas nele! Isso não seria possível, já que as chamadas de função são resolvidas pelo vinculador em tempo de compilação enquantoLoadLibrary
são chamadas em tempo de execução (lembre-se de que C ++ é uma linguagem de tipo estático ).Você precisa de uma função WinAPI separado para obter o endereço de funções carregadas dinamicamente:
GetProcAddress
.Exemplo
#include <windows.h> #include <iostream> /* Define a function pointer for our imported * function. * This reads as "introduce the new type f_funci as the type: * pointer to a function returning an int and * taking no arguments. * * Make sure to use matching calling convention (__cdecl, __stdcall, ...) * with the exported function. __stdcall is the convention used by the WinAPI */ typedef int (__stdcall *f_funci)(); int main() { HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop\\test.dll"); if (!hGetProcIDDLL) { std::cout << "could not load the dynamic library" << std::endl; return EXIT_FAILURE; } // resolve function address here f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "funci"); if (!funci) { std::cout << "could not locate the function" << std::endl; return EXIT_FAILURE; } std::cout << "funci() returned " << funci() << std::endl; return EXIT_SUCCESS; }
Além disso, você deve exportar sua função da DLL corretamente. Isso pode ser feito assim:
int __declspec(dllexport) __stdcall funci() { // ... }
Como observa Lundin, é uma boa prática liberar o identificador para a biblioteca se você não precisar mais dele. Isso fará com que ele seja descarregado se nenhum outro processo ainda tiver um identificador para a mesma DLL.
fonte
f_funci
na verdade, é um tipo (em vez de ter um tipo). O tipo éf_funci
lido como "ponteiro para uma função que retorna umint
e não recebe argumentos". Mais informações sobre ponteiros de função em C podem ser encontradas em newty.de/fpt/index.html .*
falta um, o que pode ter causado o erro)Além da resposta já postada, pensei que deveria compartilhar um truque útil que uso para carregar todas as funções DLL no programa por meio de ponteiros de função, sem escrever uma chamada GetProcAddress separada para cada função. Também gosto de chamar as funções diretamente como tentado no OP.
Comece definindo um tipo de ponteiro de função genérico:
typedef int (__stdcall* func_ptr_t)();
Os tipos usados não são realmente importantes. Agora crie uma matriz desse tipo, que corresponde à quantidade de funções que você tem na DLL:
func_ptr_t func_ptr [DLL_FUNCTIONS_N];
Nessa matriz, podemos armazenar os ponteiros de função reais que apontam para o espaço de memória DLL.
O próximo problema é que
GetProcAddress
espera os nomes das funções como strings. Portanto, crie uma matriz semelhante consistindo nos nomes das funções na DLL:const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = { "dll_add", "dll_subtract", "dll_do_stuff", ... };
Agora podemos facilmente chamar GetProcAddress () em um loop e armazenar cada função dentro dessa matriz:
for(int i=0; i<DLL_FUNCTIONS_N; i++) { func_ptr[i] = GetProcAddress(hinst_mydll, DLL_FUNCTION_NAMES[i]); if(func_ptr[i] == NULL) { // error handling, most likely you have to terminate the program here } }
Se o loop foi bem sucedido, o único problema que temos agora é chamar as funções. O ponteiro de função typedef anterior não é útil, porque cada função terá sua própria assinatura. Isso pode ser resolvido criando uma estrutura com todos os tipos de função:
typedef struct { int (__stdcall* dll_add_ptr)(int, int); int (__stdcall* dll_subtract_ptr)(int, int); void (__stdcall* dll_do_stuff_ptr)(something); ... } functions_struct;
E, finalmente, para conectá-los ao array anterior, crie uma união:
typedef union { functions_struct by_type; func_ptr_t func_ptr [DLL_FUNCTIONS_N]; } functions_union;
Agora você pode carregar todas as funções da DLL com o loop conveniente, mas chamá-las por meio do
by_type
membro do sindicato.Mas, claro, é um pouco pesado digitar algo como
functions.by_type.dll_add_ptr(1, 1);
sempre que você quiser chamar uma função.Acontece que esta é a razão pela qual adicionei o postfix "ptr" aos nomes: eu queria mantê-los diferentes dos nomes reais das funções. Agora podemos suavizar a sintaxe da estrutura icky e obter os nomes desejados, usando algumas macros:
#define dll_add (functions.by_type.dll_add_ptr) #define dll_subtract (functions.by_type.dll_subtract_ptr) #define dll_do_stuff (functions.by_type.dll_do_stuff_ptr)
E voilà, agora você pode usar os nomes das funções, com o tipo e os parâmetros corretos, como se estivessem estaticamente vinculados ao seu projeto:
int result = dll_add(1, 1);
Isenção de responsabilidade: falando estritamente, as conversões entre ponteiros de função diferentes não são definidas pelo padrão C e não são seguras. Então, formalmente, o que estou fazendo aqui é um comportamento indefinido. No entanto, no mundo do Windows, os ponteiros de função são sempre do mesmo tamanho, independentemente do tipo, e as conversões entre eles são previsíveis em qualquer versão do Windows que usei.
Além disso, em teoria pode haver preenchimento inserido na união / estrutura, o que faria com que tudo falhasse. No entanto, os ponteiros têm o mesmo tamanho que o requisito de alinhamento do Windows. Um
static_assert
para garantir que a estrutura / união não tem preenchimento pode estar em ordem ainda.fonte
#define
s?auto dll_add = ...
, mas em C ++ 03 não há nenhuma construção que eu pudesse pensar que simplificaria a tarefa (também não vejo nenhum problema particular com os#define
aqui)func_ptr_t
. Em vez disso, você pode usarFARPROC
, que é o tipo de retorno deGetProcAddress
. Isso pode permitir que você compile com um nível de aviso mais alto sem adicionar um elenco àGetProcAddress
chamada.auto
para uma função por vez, o que anula a ideia de fazer isso de uma vez por todas em um loop. mas o que há de errado com uma matriz std :: functionEste não é exatamente um tópico quente, mas tenho uma classe de fábrica que permite que uma dll crie uma instância e a retorne como uma DLL. É o que vim procurar mas não consegui encontrar exatamente.
É chamado como,
onde IHTTP_Server é a interface virtual pura para uma classe criada em outra DLL ou na mesma.
DEFINE_INTERFACE é usado para fornecer uma interface a um id de classe. Coloque dentro da interface;
Uma classe de interface parece,
class IMyInterface { DEFINE_INTERFACE(IMyInterface); public: virtual ~IMyInterface() {}; virtual void MyMethod1() = 0; ... };
O arquivo de cabeçalho é assim
#if !defined(SN_FACTORY_H_INCLUDED) #define SN_FACTORY_H_INCLUDED #pragma once
As bibliotecas estão listadas nesta definição de macro. Uma linha por biblioteca / executável. Seria legal se pudéssemos chamar outro executável.
#define SN_APPLY_LIBRARIES(L, A) \ L(A, sn, "sn.dll") \ L(A, http_server_lib, "http_server_lib.dll") \ L(A, http_server, "")
Então, para cada dll / exe você define uma macro e lista suas implementações. Def significa que é a implementação padrão da interface. Se não for o padrão, você dá um nome para a interface usada para identificá-lo. Ou seja, especial, e o nome será IHTTP_Server_special_entry.
#define SN_APPLY_ENTRYPOINTS_sn(M) \ M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, def) \ M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, special) #define SN_APPLY_ENTRYPOINTS_http_server_lib(M) \ M(IHTTP_Server, HTTP::server::server, http_server_lib, def) #define SN_APPLY_ENTRYPOINTS_http_server(M)
Com todas as bibliotecas configuradas, o arquivo de cabeçalho usa as definições de macro para definir o necessário.
#define APPLY_ENTRY(A, N, L) \ SN_APPLY_ENTRYPOINTS_##N(A) #define DEFINE_INTERFACE(I) \ public: \ static const long Id = SN::I##_def_entry; \ private: namespace SN { #define DEFINE_LIBRARY_ENUM(A, N, L) \ N##_library,
Isso cria um enum para as bibliotecas.
enum LibraryValues { SN_APPLY_LIBRARIES(DEFINE_LIBRARY_ENUM, "") LastLibrary }; #define DEFINE_ENTRY_ENUM(I, C, L, D) \ I##_##D##_entry,
Isso cria um enum para implementações de interface.
enum EntryValues { SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_ENUM) LastEntry }; long CallEntryPoint(long id, long interfaceId);
Isso define a classe de fábrica. Não muito aqui.
template <class I> class SN_Factory { public: SN_Factory() { } static I *CreateObject(long id = I::Id ) { return (I *)CallEntryPoint(id, I::Id); } }; } #endif //SN_FACTORY_H_INCLUDED
Então o CPP é,
#include "sn_factory.h" #include <windows.h>
Crie o ponto de entrada externo. Você pode verificar se ele existe usando o depends.exe.
extern "C" { __declspec(dllexport) long entrypoint(long id) { #define CREATE_OBJECT(I, C, L, D) \ case SN::I##_##D##_entry: return (int) new C(); switch (id) { SN_APPLY_CURRENT_LIBRARY(APPLY_ENTRY, CREATE_OBJECT) case -1: default: return 0; } } }
As macros configuram todos os dados necessários.
namespace SN { bool loaded = false; char * libraryPathArray[SN::LastLibrary]; #define DEFINE_LIBRARY_PATH(A, N, L) \ libraryPathArray[N##_library] = L; static void LoadLibraryPaths() { SN_APPLY_LIBRARIES(DEFINE_LIBRARY_PATH, "") } typedef long(*f_entrypoint)(long id); f_entrypoint libraryFunctionArray[LastLibrary - 1]; void InitlibraryFunctionArray() { for (long j = 0; j < LastLibrary; j++) { libraryFunctionArray[j] = 0; } #define DEFAULT_LIBRARY_ENTRY(A, N, L) \ libraryFunctionArray[N##_library] = &entrypoint; SN_APPLY_CURRENT_LIBRARY(DEFAULT_LIBRARY_ENTRY, "") } enum SN::LibraryValues libraryForEntryPointArray[SN::LastEntry]; #define DEFINE_ENTRY_POINT_LIBRARY(I, C, L, D) \ libraryForEntryPointArray[I##_##D##_entry] = L##_library; void LoadLibraryForEntryPointArray() { SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_POINT_LIBRARY) } enum SN::EntryValues defaultEntryArray[SN::LastEntry]; #define DEFINE_ENTRY_DEFAULT(I, C, L, D) \ defaultEntryArray[I##_##D##_entry] = I##_def_entry; void LoadDefaultEntries() { SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_DEFAULT) } void Initialize() { if (!loaded) { loaded = true; LoadLibraryPaths(); InitlibraryFunctionArray(); LoadLibraryForEntryPointArray(); LoadDefaultEntries(); } } long CallEntryPoint(long id, long interfaceId) { Initialize(); // assert(defaultEntryArray[id] == interfaceId, "Request to create an object for the wrong interface.") enum SN::LibraryValues l = libraryForEntryPointArray[id]; f_entrypoint f = libraryFunctionArray[l]; if (!f) { HINSTANCE hGetProcIDDLL = LoadLibraryA(libraryPathArray[l]); if (!hGetProcIDDLL) { return NULL; } // resolve function address here f = (f_entrypoint)GetProcAddress(hGetProcIDDLL, "entrypoint"); if (!f) { return NULL; } libraryFunctionArray[l] = f; } return f(id); } }
Cada biblioteca inclui este "cpp" com um stub cpp para cada biblioteca / executável. Qualquer coisa de cabeçalho compilada específica.
#include "sn_pch.h"
Configure esta biblioteca.
#define SN_APPLY_CURRENT_LIBRARY(L, A) \ L(A, sn, "sn.dll")
Uma inclusão para o cpp principal. Acho que esse cpp pode ser um .h. Mas existem diferentes maneiras de fazer isso. Essa abordagem funcionou para mim.
#include "../inc/sn_factory.cpp"
fonte