从DLL动态加载函数

77 c++ dll winapi

我正在查看.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;
}
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试编译我认为已导入.dll的.cpp文件时,我遇到以下错误:

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 ===|
Run Code Online (Sandbox Code Playgroud)

我知道.dll与头文件有所不同,所以我知道我可以;导入这样的函数,但这是我能想到的最好的表明我已经尝试过的.

我的问题是,如何使用"hGetProcIDDLL"指针访问.dll中的函数.

我希望这个问题有道理,我再也不会咆哮一些错误的树了.

Nik*_* B. 138

LoadLibrary不会做你认为它做的事情.它加载DLL到当前进程的内存,但它并不会神奇地导入它定义的功能!这是不可能的,因为LoadLibrary在运行时调用时,链接器会在编译时解析函数调用(请记住,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;
}
Run Code Online (Sandbox Code Playgroud)

此外,您应该正确地从DLL 导出您的函数.这可以这样做:

int __declspec(dllexport) __stdcall funci() {
   // ...
}
Run Code Online (Sandbox Code Playgroud)

正如Lundin指出的那样,如果您不再需要它,那么将句柄释放到库中是一种很好的做法.如果没有其他进程仍然拥有同一DLL的句柄,这将导致它被卸载.

  • 除此之外,答案很好,很容易理解 (7认同)
  • 请注意,`f_funci`实际上_是一个type_(而不是_has_一个类型).类型`f_funci`读作"指向返回`int`并且不带参数的函数的指针".有关C中函数指针的更多信息,请访问http://www.newty.de/fpt/index.html. (6认同)

Lun*_*din 29

除了已经发布的答案之外,我想我应该分享一个方便的技巧,用于通过函数指针将所有DLL函数加载到程序中,而无需为每个函数编写单独的GetProcAddress调用.我也想在OP中尝试直接调用这些函数.

首先定义泛型函数指针类型:

typedef int (__stdcall* func_ptr_t)();
Run Code Online (Sandbox Code Playgroud)

使用哪些类型并不重要.现在创建一个该类型的数组,它对应于DLL中的函数数量:

func_ptr_t func_ptr [DLL_FUNCTIONS_N];
Run Code Online (Sandbox Code Playgroud)

在这个数组中,我们可以存储指向DLL内存空间的实际函数指针.

下一个问题是GetProcAddress期望函数名称为字符串.因此,创建一个由DLL中的函数名称组成的类似数组:

const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = 
{
  "dll_add",
  "dll_subtract",
  "dll_do_stuff",
  ...
};
Run Code Online (Sandbox Code Playgroud)

现在我们可以在循环中轻松调用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
  }
}
Run Code Online (Sandbox Code Playgroud)

如果循环成功,我们现在唯一的问题是调用函数.来自前面的函数指针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;
Run Code Online (Sandbox Code Playgroud)

最后,要将这些连接到之前的数组,创建一个联合:

typedef union
{
  functions_struct  by_type;
  func_ptr_t        func_ptr [DLL_FUNCTIONS_N];
} functions_union;
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用方便的循环从DLL加载所有函数,但是通过by_typeunion成员调用它们.

但是,当然,输入类似的东西有点麻烦

functions.by_type.dll_add_ptr(1, 1); 无论何时你想调用一个函数.

事实证明,这就是我为名称添加"ptr"后缀的原因:我想让它们与实际的函数名称不同.我们现在可以通过使用一些宏来平滑icky结构语法并获得所需的名称:

#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)
Run Code Online (Sandbox Code Playgroud)

而且,现在,您可以使用具有正确类型和参数的函数名称,就像它们与您的项目静态链接一样:

int result = dll_add(1, 1);
Run Code Online (Sandbox Code Playgroud)

免责声明:严格来说,不同函数指针之间的转换不是由C标准定义的,也不是安全的.正式地说,我在这里做的是未定义的行为.但是,在Windows世界中,函数指针总是具有相同的大小,无论它们的类型如何,它们之间的转换在我使用的任何Windows版本上都是可预测的.

此外,理论上可能会在union/struct中插入填充,这会导致一切都失败.但是,指针恰好与Windows中的对齐要求大小相同.一static_assert,以确保结构/联合没有填充可能是为了还.