2012-01-02 7 views
54

Я немного поглядел на DLL-файлы, я понимаю их использование, и я пытаюсь понять, как их использовать.Динамически загружать функцию из DLL

Я создал DLL-файл, который содержит функцию, которая возвращает целое с именем funci()

с помощью этого кода, я (думаю) я импортировал файл .dll в проект (нет никаких жалоб):

#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; 
} 

Однако, когда я пытаюсь скомпилировать этот .cpp файл, который я думаю, импортировал .dll у меня есть следующее сообщение об ошибке:

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 ===| 

Я знаю .dll отличается от файл заголовка, поэтому я знаю, что могу, t импортировать такую ​​функцию, но это лучшее, что я мог бы придумать, чтобы показать, что я пробовал.

Мой вопрос в том, как я могу использовать указатель «hGetProcIDDLL» для доступа к функции в DLL.

Надеюсь, этот вопрос имеет смысл, и я снова не лаю какое-то неправильное дерево.

+0

поиск статических/динамических ссылок. –

+0

Спасибо, я посмотрю на это –

+0

Я вставляю свой код, но когда я вталкиваю его сюда, формат беспорядочен, поэтому я в конечном итоге отступаю все на 4 строки. –

ответ

96

LoadLibrary не делает, что вы думаете. Он загружает DLL в память текущего процесса, но не магические функции импорта, определенные в нем! Это было бы невозможно, так как вызовы функций решаются компоновщиком во время компиляции, а LoadLibrary вызывается во время выполнения (помните, что C++ - это язык statically typed).

Для получения адреса динамически загружаемых функций вам потребуется отдельная функция 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; 
} 

Кроме того, вы должны export вашу функцию из DLL правильно. Это можно сделать так:

int __declspec(dllexport) __stdcall funci() { 
    // ... 
} 

Как Лундин ноты, это хорошая практика, чтобы free the handle to the library, если вы не нуждаетесь в них она больше. Это приведет к его разгрузке, если никакой другой процесс по-прежнему не содержит дескриптор одной и той же библиотеки DLL.

+0

Может показаться, что это глупый вопрос, но что такое/должен быть тип пользователя f_funci? –

+6

Кроме того, ответ отличный и понятный –

+3

Обратите внимание, что 'f_funci' фактически _ является типом type_ (а не _has_ a). Тип 'f_funci' читается как« указатель на функцию, возвращающую 'int' и не принимающую аргументов». Более подробную информацию о указателях функций в C можно найти по адресу http://www.newty.de/fpt/index.html. –

16

В дополнение к уже опубликованному ответу, я подумал, что должен использовать удобный трюк, который я использую для загрузки всех функций 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» к именам: я хотел, чтобы они отличались от фактических имен функций. Теперь мы можем сгладить неприглядный синтаксис структуры и получить нужные имена, используя некоторые макросы:

#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. A static_assert, чтобы гарантировать, что структура/объединение не имеет отступов, может быть еще в порядке.

+0

Этот подход в стиле C будет работать. Но не следовало бы использовать конструкцию C++, чтобы избежать '# define'? – harper

+0

@harper Ну в C++ 11 вы можете использовать 'auto dll_add = ...', но в C++ 03 нет никакой конструкции, о которой я мог бы думать, что упростит задачу (я также не вижу никакой конкретной проблемы с' # define 'здесь) –

+0

Так как это все зависит от WinAPI, вы не нужно вводить typedef свой собственный 'func_ptr_t'. Вместо этого вы можете использовать 'FARPROC', который является возвращаемым типом' GetProcAddress'. Это может позволить вам скомпилировать с более высоким уровнем предупреждения без добавления приведения в вызов «GetProcAddress». –

Смежные вопросы