【发布时间】:2014-12-15 22:52:49
【问题描述】:
我想了解 DLL 机制以及当我在运行时加载 DLL 时编译器会做什么(即我不会使用生成的 .lib)。
考虑以下 C++ 代码:
DLL接口头文件
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
class MYDLL_API Base
{
public:
Base();
virtual ~Base();
virtual int get_number() const;
virtual const char* what() const = 0;
private:
int i_;
};
class MYDLL_API Child : public Base
{
public:
Child();
virtual ~Child();
virtual int get_number() const override;
virtual const char* what() const override;
private:
int j_;
};
extern "C" {
MYDLL_API Base* __cdecl initializeObject();
}
DLL 实现源文件
#include "MyDLL.hh"
Base::Base()
: i_(42)
{}
Base::~Base()
{}
int Base::get_number() const
{
return i_;
}
Child::Child()
: Base()
, j_(24)
{}
Child::~Child()
{}
int Child::get_number() const
{
return j_;
}
const char* Child::what() const
{
return "Hello!";
}
Base* initializeObject()
{
return new Child();
}
这个 DLL 的目标是拥有一个由 Base 类定义的公共接口,但它允许在运行时加载的不同 DLL 中编译的特定实现(这里暴露了 Child 类的目的是例子)。
在这个阶段,如果我天真地包含 DLL 的标头:
#include "MyDLL.hh"
int main()
{
Base* b = new Child();
std::cout << b->get_number() << std::endl;
std::cout << b->what() << std::endl;
delete b;
getchar();
return 0;
}
链接器抱怨LNK2019 和LNK2001 错误:它无法解析符号。所以,它的行为符合预期(我没有使用.lib)。
现在考虑一下我用来在运行时加载 DLL 的以下代码:
#include "MyDLL.hh"
typedef Base* (*initFuncType)();
int main()
{
HINSTANCE handle = LoadLibrary(L"MyDLL.dll");
initFuncType init = nullptr;
init = (initFuncType)(GetProcAddress(handle, "initializeObject"));
if (init)
{
Base* b = init(); //< Use init() !
std::cout << b->get_number() << std::endl;
std::cout << b->what() << std::endl;
delete b;
}
getchar();
FreeLibrary(handle);
return 0;
}
这次成功了,链接就完成了。
- 第一个问题:发生了什么?编译器和链接器发生了什么变化?在 initializeObject() 上使用函数指针解决了这个问题。
另一个我不太了解的问题是当我删除get_number() 的virtual 和override 时:
int get_number() const;
我有一个LNK2019 错误,因为_main 函数中的Base::get_number(void) const 符号未解析。我知道virtual 关键字将动态解析成员函数(在运行时)。在我们的例子中,DLL 尚未加载,get_number 符号不可用。
第二个问题:这是否意味着方法必须始终为
virtual使用 DLL run-time 链接?第三个问题:如何使用 Windows API 导出 C++ 函数?这样我就可以删除
extern "C" { ... }的东西了。
感谢您的阅读!我希望我能读到有趣的答案! :)
【问题讨论】:
-
编译器没有做任何特别/不同的事情。您的操作系统的动态链接器会尝试使用 DLL 中的符号表找到适当的符号。
-
您正在重新发明 COM,可能并不完美。只要方法是虚拟的,就会通过对象的v-table间接调用。当您将其设为非虚拟时,您必须再次使用 GetProcAddress()。
-
@TheParamagneticCroissant 我假设这是由
LoadLibrary和GetProcAddress完成的,但是当我使用init()时,类成员函数上没有链接器错误。你能解释一下原因吗? -
@PierrePagnoux 为什么会出现链接器错误?
-
@HansPassant 我尽量不要过分依赖 Windows 技术。 v-table(在分配期间创建,我忘了)有效地完成了工作。这是一种使用它的好方法吗?这样,我就不用自己绑定每个方法了?
标签: c++ visual-c++ dll dllimport dllexport