【问题标题】:Creating and using dll's: __declspec(dllimport) vs. GetProcAddress创建和使用 dll:__declspec(dllimport) 与 GetProcAddress
【发布时间】:2010-10-09 16:57:55
【问题描述】:
假设我们有一个包含 2 个项目的解决方案:MakeDll(一个 dll 应用程序),它创建一个 dll 和 UseDll(一个 exe 应用程序),它使用 dll。现在我知道基本上有两种方法,一种是愉快的,另一种是不愉快的。令人愉快的方法是 UseDll 静态链接到 MakeDll.lib,并且只需 dll 导入函数和类并使用它们。不愉快的方法是使用 LoadLibrary 和 GetProcAddress,我什至无法想象如何处理重载函数或类成员,换句话说,除了外部“C”函数之外的任何其他东西。
我的问题如下(都是关于第一个选项)
- MakeDll.lib 究竟是什么
包含?
- MakeDll.dll 何时加载到我的应用程序中,何时卸载?我能控制吗?
- 如果我更改 MakeDll.dll,我是否可以在不重新构建 UseDll.exe 的情况下使用新版本(前提是它在接口方面是旧版本的超集)?一种特殊情况是导出多态类并添加新的虚函数。
提前致谢。
附:我正在使用 MS Visual Studio 2008
【问题讨论】:
标签:
c++
visual-studio
dll
【解决方案1】:
它基本上包含 DLL 中的函数列表,包括名称和序数(尽管几乎没有人再使用序数了)。链接器使用它在 UseDLL.exe 中创建一个导入表——即一个引用(本质上):“这个文件依赖于 MakeDll.dll 中的函数 xxx”。当加载器加载该可执行文件时,它会查看导入表,并(递归地)加载它列出的所有 DLL,并且(至少在概念上)使用GetProcAddress 来查找函数,因此它可以将它们的地址放入可执行文件中他们是需要的。
它通常在加载可执行文件的过程中加载。您可以使用 /delayload 开关延迟其加载,直到调用该 DLL 中的函数。
一般来说,是的。在添加虚函数的特定情况下,它将取决于类的 vtable 布局保持不变,而不是添加新函数。除非您采取措施来确保或验证自己,否则依赖它是一个非常糟糕的主意。
【解决方案2】:
MakeDll.lib 包含导出函数及其 RVA 到 MakeDll.dll 的螺柱列表
MakeDll.dll 根据为相关 dll 定义的加载类型加载到应用程序中。 (例如DELAYLOAD)。 Raymond Chen 有一个interesting article。
只要 UseDll.exe 中使用的所有 RVA 偏移量未更改,您就可以使用 MakeDll.dll 的新更新版本。如果您更改多态类的vtable 布局,例如在先前定义的vtable 中间添加一个新函数,您将需要重新编译UseDll.exe。除此之外,您可以将更新后的 dll 与之前编译的 UseDll.exe 一起使用。
【解决方案3】:
不愉快的方法是使用 LoadLibrary 和 GetProcAddress,我什至无法想象如何处理重载函数或类成员,换句话说,除了外部“C”函数之外的任何其他东西。
是的,这令人不快,但并不像听起来那么糟糕。如果您选择使用此选项,则需要执行以下操作:
// Common.h: interface common to both sides.
// Note: 'extern "C"' disables name mangling on methods.
extern "C" class ISomething
{
// Public virtual methods...
// Object MUST delete itself to ensure memory allocator
// coherence. If linking to different libraries on either
// sides and don't do this, you'll get a hard crash or worse.
// Note: 'const' allows you to make constants and delete
// without a nasty 'const_cast'.
virtual void destroy () const = 0;
};
// MakeDLL.c: interface implementation.
class Something : public ISomething
{
// Overrides + oher stuff...
virtual void destroy () const { delete this; }
};
extern "C" ISomething * create () { return new Something(); }
我已经成功地在两端使用不同的 C++ 编译器部署了此类设置(即 G++ 和 MSVC 在所有 4 种可能的组合中互换)。
您可以随意更改Something 的实现。但是,您可以不在不重新编译双方的情况下更改界面!仔细想想,这很不直观:双方都依赖对方对ISomething 的定义。您可以添加这种额外灵活性的方法是使用编号接口(如 DirectX 所做的那样)或使用一组接口并测试功能(如 COM 所做的那样)。前者的设置非常直观,但需要纪律,而第二个井......将重新发明轮子!