可视化C++编译器/链接器在使用运行时 DLL 加载时执行的操作

visual What C++ compiler/linker does when using runtime DLL loading?

本文关键字:加载 DLL 运行时 执行 操作 编译器 C++ 链接 可视化      更新时间:2023-10-16

我想了解 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;
}

链接器抱怨LNK2019LNK2001错误:它无法解析符号。因此,它的行为符合预期(我没有使用.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(( 上使用函数指针解决了这个问题。

我不太了解的另一个问题是当我删除virtualoverride get_number()时:

int get_number() const;

由于_main函数中未解析的Base::get_number(void) const符号,我有一个LNK2019错误。我知道 virtual 关键字将动态解析成员函数(在运行时(。在我们的例子中,DLL尚未加载,get_number符号不可用。

  • 第二个问题:这是否意味着必须始终使用 DLL 运行时链接virtual方法?

  • 第三个问题:如何使用 Windows API 导出C++函数?这样我就可以删除extern "C" { ... }的东西。

感谢您的阅读!我希望我能读到有趣的答案!:)

有两种方法可以链接 dll 文件。

第二种方式(它的工作方式(是 C 绑定方法,您可以在其中查询 dll 以获取特定的函数名称,它会向您返回一个函子。

使用第二种方式,您将无法扩展基类,因为它们未定义(您没有任何代码可以复制粘贴,因此可以在链接时进行(。

为了有一个可以扩展其类的 dll,您需要使用动态绑定。您需要编译.dll并提供符号库(或导出库(。您可以在项目属性的 VS 工作室中使用此选项。

机制如下:

  • 编译 Dll 项目 -> 输出 : myLib.dll , myLib.lib
  • 在主项目中使用从 myLib.lib 导出的符号(主项目将 myLib.lib 作为依赖项(
  • 在运行时,由于绑定,您的程序会知道它需要 myLib.dll 才能工作,因此它将加载它(如果找到,否则会出现运行时错误(

使用导出库的另一个优点是可以导出C++函数(导出时会损坏(。

很难对

损坏的函数进行 C 绑定。

另一方面,与动态绑定相比,C 绑定不会让你的程序尖叫.dll如果找不到 myLib,你只会得到一个指向函数的空指针。