Динамически загружать функцию из DLL - PullRequest
73 голосов
/ 02 января 2012

Я немного смотрю на файлы .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.

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

Ответы [ 2 ]

132 голосов
/ 02 января 2012

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

Youнужна отдельная функция 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.

27 голосов
/ 20 мая 2014

В дополнение к уже опубликованному ответу я подумал, что должен поделиться удобным трюком, который я использую для загрузки всех функций DLL в программу через указатели функций, без написания отдельного вызова GetProcAddress для каждой функции.Мне также нравится вызывать функции напрямую, как это делается в операторе.

Начните с определения общего указателя типа функции:

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, которую я использовал.

Кроме того, теоретически может быть заполнениевставляется в объединение / структуру, что может привести к сбою.Однако указатели имеют тот же размер, что и требование выравнивания в Windows.static_assert, чтобы гарантировать, что структура / объединение не имеет отступов, может быть в порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...