【问题标题】:How to Implement a C++ Plugin System for a Modular Application?如何为模块化应用程序实现 C++ 插件系统?
【发布时间】:2020-06-20 00:40:58
【问题描述】:
我正在尝试设计一个模块化应用程序;开发人员将插件创建为使用 loadlibrary() 或 dlopen() 链接到主应用程序的 dll。我目前对此的想法是:
1: 应用程序和插件模块都包含一个带有纯虚拟类 IPlugin 的核心标头,该类具有方法 run()。然后插件模块实现类,定义 run():
2:插件模块定义了一个函数IPlugin* GetPlugin()使用“extern c”来保证ABI的兼容性
3: 应用程序需要使用 loadlibrary() 的插件模块,使用 从 GetPlugin() 检索 IPlugin getprocaddress()
4:应用调用方法run()来运行插件
这一切都有效,但是如何创建一个可以访问我的完整应用程序代码的插件界面?如果我有一个创建窗口或初始化按钮的类,我想通过插件接口访问它们并能够操作它们并接收事件,同时仍然让主应用程序端管理内存。
1.我该怎么做呢?
2.最好的解决方案是否与 ABI 兼容?
【问题讨论】:
标签:
c++
dll
plugins
dllexport
loadlibrary
【解决方案1】:
API 要么使用 C++,要么使用 C,没有中间立场。如果您想强制插件开发人员使用与 ABI 兼容的编译器,那么您可以拥有一个处理类和虚拟方法的接口,而无需任何“extern C”喧嚣。
否则,您仅提供 C API 和 C API。当然,您可以使用函数指针结构来实现虚函数表,并且在内部您可以将其包装在 C++ 类中等等。但那是你的实现细节,并不包含在插件接口本身的设计中。
就是这样,差不多。 Windows 上没有免费的 C++ API 兼容性。人们使用至少 5 个不兼容的编译器 - MSVC 2017+、2015、2012、mingw g++ 和 clang。一些特别落后的企业机构有时会坚持使用更旧的 MSVC。因此,除非您为所有这些用户提供 shims,否则 C++ 接口基本上是一个失败的原因。在持续集成 (CI) 的今天,这并非不可想象 - 您可以轻松构建使用您的 C API 的包装器,并通过与他们首选的开发系统兼容的 C++ 接口将其公开给用户。但这并不意味着你可以直接从你的 C++ 代码中弄乱他们的 C++ 对象。仍然有一个 C 中介,您仍然需要在适配器代码中使用帮助程序。例如。您不能直接删除用户提供的对象 - 您只能通过调用适配器 DLL 中的帮助器来执行此操作 - 一个使用相同运行时且 ABI 与用户的 C++ 代码兼容的帮助器。
并且不要忘记任何给定编译器版本都有二进制不兼容的运行时库 - 例如。调试与发布以及单线程与多线程。因此,对于任何 MSVC 版本,您必须提供插件开发人员将链接的四个适配器 DLL。然后您的代码也链接到该适配器,以访问用户的插件。因此,您将首先解析插件中的二进制标头以确定它正在使用的适配器 DLL 和运行时,如果它们不匹配,则会发出错误消息(插件开发人员很可能会搞砸)。然后,如果有匹配项,则加载插件 DLL。动态链接器将引入必要的适配器 DLL。然后您就可以使用适配器 DLL 来访问插件了。
之前已经这样做了,我的建议是让每个适配器 dll 为您的主机程序提供不同的 C 符号,因为总是会有多个插件,每个插件都使用不同的适配器,这只会使事情复杂化。相反,您需要通过 Windows 上的按需加载链接到所有适配器,并且只有在解析插件 DLL 以了解它正在使用什么时才访问特定的适配器。然后,当您调用适配器时,动态链接器将为真正的适配器函数解析需求负载存根。类似的方法可以在非 Windows 平台上使用,但需要编写帮助代码来提供需求链接功能 - 因此在 Unix 上显式使用 dlopen 可能是最简单的。您仍然需要解析插件的 ELF 标头以找出它使用的 C++ 运行时和它期望的适配器库,验证组合;然后才加载它。然后你会打开适配器与插件对话。在任何情况下,您都不会直接调用插件本身的任何函数——当您需要跨越 C++ 运行时边界时,只有适配器才能安全地执行此操作。可能有更简单的方法来做这一切,但我的经验是它们只能完成 99% 的工作,所以最终这里没有便宜的解决方案——除非有人编写了一个开源项目(或产品)来完成这一切。如果您涉足其中,您将被期望了解肮脏的实现细节并处理 C++ 运行时错误。因此,如果您想避免所有这些 - 不要与需要可加载库的 C++ 用户可见 API 混淆。
您当然可以为用户做一个只有标题的 C 到 C++ 的桥接,但令人惊讶的是,有些用户不喜欢只有标题的解决方案。叹。我怎么知道?我必须摆脱只有标题的界面才能提高客户的坚持度……那天我像疯子一样哭着笑着。