加载DLL函数的更好方法是什么?

时间:2013-08-03 03:55:51

标签: c++ c windows dll

我觉得有一种更好的方法,而不仅仅是在typedef中拥有数百个签名,然后通过GetProcAddress加载指针。据我所知,在加载DLL函数时,它是最容易的 - 但是很脏。

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

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

6 个答案:

答案 0 :(得分:3)

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

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

公共标题

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

<强> DLL

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() */

答案 1 :(得分:2)

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

不太确定你的意思是“一些幻想的循环”;在此上下文中使用的typedefs 与标题中的声明具有相似的用途 - 它们为编译器和人类读者提供有关正在调用的函数的签名的信息。不管怎样,你必须提供这个。

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

唯一的解决方法是以不同的方式设计DLL接口,这样就没有那么多的函数可以处理。但是,在大多数情况下,我不会说避免函数的typedef /声明是做这样的事情的充分动机。例如。你可以完全暴露一个函数,例如:(例如):

void PerformAction (LPCSTR action, DWORD parameter);

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

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

答案 2 :(得分:2)

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

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

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

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

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

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

总结一下,不要因为它们似乎提供更高的性能或健壮性而使用LoadLibrary()GetProcAddress()。尽可能链接.lib个文件。

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

答案 3 :(得分:1)

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

但是,如果因任何原因无法加载DLL,这将导致runtime exception,而不是像正常链接的DLL那样导致应用程序无法加载。

如果您的应用程序始终加载并运行此DLL,那么您应该正常使用DLL(使用.lib文件)。在这种情况下,在这种情况下,通过延迟加载DLL,你什么也得不到。

答案 4 :(得分:0)

创建一个类作为dll的包装器,其中类的公共成员函数只是指向get proc地址获取的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

的所有学分

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

答案 5 :(得分:-1)

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

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