问题描述
我有一个相当简单的插件系统,可以在运行时加载和重新加载 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 中以找到要调用的地址并调用它。