4

我觉得有一种比在 typedef 中有数百个签名然后通过 GetProcAddress 加载指针更好的方法。据我所知,在加载 DLL 函数时,它是最简单的——但也很脏。

有没有更简洁的方式来加载 DLL 函数?具体来说,大量的Winapi和Tool Help库函数?我知道你可以只“包含一个 .lib”,但我觉得这会导致不必要的膨胀;我也无法访问源代码(尽管 Jason C 提到可以从 .dll 转到 .lib)。

我正在寻找为此编写一个库。我觉得主要的障碍是处理具有不同签名的函数。或者这正是为什么每个人都使用 typedefs 而不是“一些奇特的循环”来加载他们的 DLL 函数的原因?

4

6 回答 6

3

您的函数必须以某种方式声明,因此您需要函数签名。但是,如果您觉得您正在复制函数签名,您可以使用decltype(&func_name)来消除该重复(给定func_name具有相同目标签名的函数)。您可以将其包装在宏中以使其更可口。

另一种常见的方法是将所有函数指针推入一个大结构(或一堆特定于任务的结构),然后加载指向该结构的指针(例如,通过加载一个返回指向该结构的指针的函数)。如下所示:

通用标头

struct func_table {
    void (*f1)(int);
    int (*f2)(void);
};

动态链接库

static struct func_table table = {
    &f1_Implementation,
    &f2_Implementation,
};

DLLEXPORT struct func_table *getFuncTable(void) {
    return &table;
}

使用 DLL 的程序

static struct func_table *funcs;

void loadFuncTable() {
    struct func_table (*getFuncTable)(void) = GetProcAddress(..., "getFuncTable");
    funcs = getFuncTable();
}

/* call funcs->f1(n) and funcs->f2() */
于 2013-08-03T04:19:51.777 回答
2

Visual C++ 有一个延迟加载 DLL的选项。您在 DLL 的 lib 文件中链接的代码,包括其标头,并正常调用函数。Visual C++ 链接器和运行时负责在第一次调用函数时调用 LoadLibrary() 和 GetProcAddress()。

但是,如果由于任何原因无法加载 DLL,这将导致运行时异常,而不是像正常链接的 DLL 那样导致您的应用程序无法加载。

如果您的应用程序始终加载并需要此 DLL 来运行,那么您应该正常使用 DLL(使用 .lib 文件)。在这种情况下,延迟加载 DLL 将一无所获。

于 2013-08-03T04:14:34.467 回答
2

有几种不同的方式来加载 Windows 库,但每种方式都有不同的用途。

手动使用LoadLibrary()andGetProcAddress()旨在允许关于几种不同动态链接情况的运行时决策。这些调用只是简单的系统调用,不会影响生成的 PE 文件的任何方面,并且返回的地址GetProcAddress()在代码生成方面没有任何特殊之处。这意味着调用者负责在非常语法级别正确使用这些符号(例如,使用正确的调用约定以及函数调用的参数的正确数量、大小和对齐方式(如果有))。

链接与 a.lib关联的文件.dll是不同的。客户端代码使用外部标识符引用运行时内容,就好像它们是静态链接的符号一样。在构建过程中,链接器将使用.lib文件中的符号解析这些标识符。虽然原则上,这些符号可以指向任何东西(就像任何其他符号一样),但自动生成的.lib文件将简单地提供符号,这些符号充当将在加载时由 PE 加载器填充的内存内容的微小代理。

这些代理的实现方式取决于预期的内存访问类型。例如,引用函数的符号.dll将被实现为单个间接jmp指令,这样call来自客户端代码的原始指令将命中内容中的jmp指令,.lib并将被转发到由客户端解析的地址加载时的 PE 加载程序。在此地址驻留动态加载的函数的代码。

除了更多细节之外,所有这些都用于指示 PE 加载器执行与客户端代码本身相同的工作:调用LoadLibrary()以将 PE 映射到内存并挖掘导出表GetProcAddress()以查找和计算正确的地址到每个需要的符号。PE 加载器在加载时根据客户端可执行文件的导入表中的信息自动执行此操作。

换句话说,加载时动态链接比运行时动态链接更慢更胖(如果有的话),但它旨在为普通开发人员提供更简单的编程接口,特别是因为总是需要许多库并且随时可用。在这些情况下,尝试通过手动加载提供任何额外的逻辑是没有用的。

总而言之,不要费心使用它们LoadLibrary()GetProcAddress()只是因为它们似乎提供了更多的性能或鲁棒性。.lib尽可能链接文件。

进一步讨论这个话题,甚至可以创建根本不包含任何导入表的 PE 映像(并且仍然可以访问系统调用和其他导出的例程)。恶意软件使用这种方法通过剥离任何加载时间信息来隐藏可疑的 API 使用(例如 Winsock 调用)。

于 2013-08-03T06:35:37.847 回答
2

链接到 .lib 文件的“膨胀”(通过“膨胀”我假设您的意思是可执行文件上有几个额外的 kB,你知道......)如果你正在使用它不是“不必要的”为了方便避免处理数百个 GetProcAddress 调用。:)

不太清楚你所说的“一些奇特的循环”是什么意思;在此上下文中使用的 typedef与标头中的声明具有类似的目的——它们为编译器和人类读者提供有关被调用函数签名的信息。一种或另一种方式,你必须提供这个。

有一些工具可以从 .dll 生成 .lib;但是您仍然必须有一个标头来声明您正在调用的函数,以便编译器知道您在做什么。本质上,为 .dll 生成的 .lib 只是加载 DLL 并为您获取函数地址的“存根”。不同的API,但本质上是一样的。

解决这个问题的唯一方法是设计不同的 DLL 接口,这样就不需要处理那么多函数。然而,在大多数情况下,我不会说避免函数的 typedefs/declarations 足以激励做这样的事情。例如,您可以只公开一个函数,例如(例如):

void PerformAction (LPCSTR action, DWORD parameter);

并让您的 PerformAction 实现根据“动作”做一些不同的事情。在某些与您的帖子无关的情况下,这当然是合理的,但对于您所描述的“问题”,这并不是真正合适的“解决方法”。

你几乎只需要处理它。使用声明创建标头并生成存根 .lib,或使用 typedef 创建标头并使用 LoadLibrary/GetProcAddress。前者将为您节省一些打字时间。后者使您可以处理不存在 DLL 的情况,或加载在设计时未知的 DLL(例如,另一个答案中提到的插件)。

于 2013-08-03T04:07:03.000 回答
0

创建一个类作为 dll 的包装器,其中类的公共成员函数只不过是指向通过 get proc address 获得的 dll 中的函数的指针,这将是从 dll 加载函数的一种好方法。

最好使用 c++ 的 RAII 能力来释放通过释放类的析构函数中的库来加载的库。

这个:

typedef int(WINAPI *ShellAboutProc)(HWND, LPCSTR, LPCSTR, HICON);

int main() {
  HMODULE hModule = LoadLibrary(TEXT("Shell32.dll"));

  ShellAboutProc shellAbout =
      (ShellAboutProc)GetProcAddress(hModule, "ShellAboutA");

  shellAbout(NULL, "hello", "world", NULL);

  FreeLibrary(hModule);
}

可以这样实现:

class ShellApi {
  DllHelper _dll{"Shell32.dll"};

public:
  decltype(ShellAboutA) *shellAbout = _dll["ShellAboutA"];
};

int main() {
  ShellApi shellApi;
  shellApi.shellAbout(NULL, "hello", "world", NULL);
}

DllHelper 类在哪里:

class ProcPtr {
public:
  explicit ProcPtr(FARPROC ptr) : _ptr(ptr) {}

  template <typename T, typename = std::enable_if_t<std::is_function_v<T>>>
  operator T *() const {
    return reinterpret_cast<T *>(_ptr);
  }

private:
  FARPROC _ptr;
};

class DllHelper {
public:
  explicit DllHelper(LPCTSTR filename) : _module(LoadLibrary(filename)) {}

  ~DllHelper() { FreeLibrary(_module); }

  ProcPtr operator[](LPCSTR proc_name) const {
    return ProcPtr(GetProcAddress(_module, proc_name));
  }

  static HMODULE _parent_module;

private:
  HMODULE _module;
};

Benoit的所有学分

我找不到比这更优雅的方式了。

于 2018-03-09T09:46:30.970 回答
-1

应该提供一个函数来手动加载一个给定文件名的dll。在 Windows API 中,它是 LoadLibrary()。除非您正在开发“插件”样式的应用程序或者您需要手动控制加载和卸载,否则通常不会使用它。

您应该考虑如何使用您的代码来确定静态库或动态加载的 dll 是否是满足您要求的最佳解决方案。

于 2013-08-03T04:01:06.340 回答