为什么我可以使用运行时加载的 dll 未导出的函数

问题描述

我有一个相当简单的插件系统,可以在运行时加载和重新加载 dll。没有涉及任何静态链接。 .exe 仅包含加载 dll 所需的代码。它目前通过使用 LoadLibrary/GetProcAddress/FreeLibrary 函数在 Windows 下执行此操作。 (在 VisualStudio 中)没有设置额外的库目录、项目引用或库。

然而,我正在做的是包括我稍后动态加载的 dll 项目中的一些头文件(以防万一)。现在我知道我可能应该只使用 GetProcAddress 查询的接口函数调用 dll,但这是我第一次这样做,所以我只是想弄清楚。

现在假设我得到了 CreateWindow 函数函数指针,如下所示:

void Loadplugin(PlatformPlugin* plugin)
{
  plugin->Handle = LoadLibrary(plugin->PLUGIN_FILE_TEMP);
  plugin->CreateWindow = (PLUGIN_PLATFORM_CreateWindow)GetProcAddress(plugin->Handle,"CreateWindow");
}

CreateWindow 函数返回一个指向 Window 对象的指针,该对象主要包含虚拟函数,并由特定于平台的 Window 类(如 WindowsWindow)继承:

class Window
{
public:
  virtual unsigned int GetWidth() const = 0;
  virtual unsigned int GetHeight() const = 0;
};

class WindowsWindow : public Window
{
 public:
   virtual unsigned int GetWidth() const override;
   virtual unsigned int GetHeight() const override;
};

当然也有一个带有定义的 .cpp 文件。 这里出现了“问题”:当调用 CreateWindow 时,我得到了有效的指针,当在它上面调用 GetWidth 时,我也得到了正确的结果。但是您可能已经注意到,我从未导出任何 Window 类或函数。据我了解,我不应该调用 GetWidth 但我可以。 Dependencywalker 证实了这一点,并只向我展示了 CreateWindow 作为导出的函数。 我怎么能叫它呢?包含的头文件在理论上只为编译器提供调用函数所需的信息,但由于头文件实际上不包含函数实现,链接器如何知道我的函数的地址?

解决方法

虚函数的地址存储在对象的 vtable 中。因此,您不需要导出符号来调用这些函数,只需要一个有效的对象(或一个对象的地址)和 vtable 布局的知识(由您的头文件提供)。

然后编译器可以索引到 vtable 中以找到要调用的地址并调用它。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...