Динамическая загрузка функции из библиотеки DLL
Я немного посмотрю .dll файлы, я понимаю их использование, и я пытаюсь понять, как их использовать.
Я создал a .dll файл, содержащий функцию, которая возвращает целое число с именем funci ()
используя этот код, я (думаю) я импортировал .dll файл в проект(нет никаких жалоб):
#include <windows.h>
#include <iostream>
int main() {
HINSTANCE hGetProcIDDLL = LoadLibrary("C:Documents and SettingsUserDesktop fgfdgdgdgtest.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;
}
однако, когда я пытаюсь скомпилировать это .cpp файл, который я думаю, импортировал .dll у меня есть следующая ошибка:
C:Documents and SettingsUserDesktopfgfdgonemore.cpp||In function 'int main()':|
C:Documents and SettingsUserDesktopfgfdgonemore.cpp|16|error: 'funci' was not declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|
Я знаю a .dll отличается от файла заголовка, поэтому я знаю, что могу;t импортировать такую функцию, но это лучшее, что я мог придумать, чтобы показать, что я пробовал.
мой вопрос в том, как я могу использовать указатель "hGetProcIDDLL" для доступа к функции внутри .файл DLL.
Я надеюсь, что этот вопрос имеет смысл и я не лаешь какое-то неправильное дерево еще раз.
2 ответов:
LoadLibraryне делать то, что вы думаете. Он загружает DLL в память текущего процесса, но это не магически импортировать функции, определенные в нем! Это было бы невозможно, так как вызовы функций разрешаются компоновщиком во время компиляции whileLoadLibraryвызывается во время выполнения (напомним, что C++ - это статически типизированный язык).вам нужна отдельная функция WinAPI, чтобы получить адрес динамически загружаемых функций:
GetProcAddress.пример
#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; }кроме того, стоит экспорт ваша функция из DLL правильно. Это можно сделать так:
int __declspec(dllexport) __stdcall funci() { // ... }как отмечает Лундин, это хорошая практика освободите ручку в библиотеку если они вам больше не нужны. Это приведет к его выгрузке, если ни один другой процесс по-прежнему не содержит дескриптор той же DLL.
в дополнение к уже опубликованному ответу, я подумал, что должен поделиться удобным трюком, который я использую для загрузки всех функций DLL в программу через указатели функций, без написания отдельного вызова GetProcAddress для каждой функции. Мне также нравится вызывать функции напрямую, как это было сделано в OP.
начните с определения общего типа указателя функции:
typedef int (__stdcall* func_ptr_t)();какие типы, которые используются на самом деле не важно. Теперь создайте массив этого типа, что соответствует количеству функций в DLL:
func_ptr_t func_ptr [DLL_FUNCTIONS_N];в этом массиве мы можем хранить фактические указатели на функции, которые указывают в пространство памяти DLL.
следующая проблема заключается в том, что
GetProcAddressожидает имена функций в виде строк. Поэтому создайте аналогичный массив, состоящий из имен функций в DLL:const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = { "dll_add", "dll_subtract", "dll_do_stuff", ... };теперь мы можем легко вызвать GetProcAddress() в цикле и хранить каждую функцию внутри, что массив:
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 } }если цикл был успешным, единственная проблема, которую мы имеем сейчас, это вызов функций. Указатель на функцию typedef из более ранней версии не полезен, потому что каждая функция будет иметь свою собственную подпись. Это можно решить, создав структуру со всеми типами функций:
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;и, наконец, чтобы соединить их с массивом ранее, создайте объединение:
typedef union { functions_struct by_type; func_ptr_t func_ptr [DLL_FUNCTIONS_N]; } functions_union;теперь вы можете загрузить все функции из DLL с удобным петля, но вызовите их через
by_typeчлен Союза.но конечно, это немного обременительно, чтобы ввести что-то вроде
functions.by_type.dll_add_ptr(1, 1);всякий раз, когда вы хотите вызвать функцию.как оказалось, именно по этой причине я добавил постфикс "ptr" к именам: я хотел, чтобы они отличались от реальных имен функций. Теперь мы можем сгладить синтаксис icky struct и получить нужные имена, используя некоторые макросы:
#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)и вуаля, теперь вы можете использовать имена функций, с правильным типом и параметрами, как если бы они были статически в проект:
int result = dll_add(1, 1);
отказ от ответственности: строго говоря, преобразования между различными указателями функций не определяются стандартом C и небезопасны. Так что формально то, что я здесь делаю, - это неопределенное поведение. Однако в мире Windows указатели функций всегда имеют одинаковый размер независимо от их типа и преобразования между ними предсказуемо на любой версии Windows, которую я использовал.
кроме того, теоретически в union/struct может быть вставлено дополнение, которое приведет к сбою всего. Однако указатели имеют тот же размер, что и требование выравнивания в Windows. А
static_assertчтобы гарантировать, что структура/объединение не имеет заполнения, возможно, все еще в порядке.
Comments