欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

如何为 C++ 程序编写扩展程序

最编程 2024-04-14 10:35:48
...

我简单的说明下c/c++里的插件系统是怎么运行的吧。包括.COM、Qt Plugin等各种框架的插件机制,基本都是这样的原理。

Windows/Linux均支持通过文件名运行时加载动态链接库,通过函数符号名称获得函数指针,故:

  1. 定义纯虚基类作为Interface(如果有Java基础比较好理解)。
  2. 把实现类封装为dll文件,用LoadLibrary运行时载入。
  3. 通过C API获取插件对象实例。因为C++ ABI在不同编译器、不同编译器版本之间有差异,而的C ABI是稳定的。

所以就可以这么做了——

  1. 写一个接口类,内部都是纯虚函数,用作定义对外接口。
  2. 写一个实例类,继承实现这个接口。这个类不用导出。
  3. 导出一个C函数getInstance如下。
  4. 使用插件者,通过文件名在运行时加载dll;
  5. 使用插件者,通过字符串"getInstance"获取到函数指针;
  6. 运行函数指针,得到对象实例。然后就可以通过接口调用了。
extern "C" std::shared_ptr<ISomeInterface> getInstance()
{
    return std::dynamic_pointer_cast<ISomeInterface>(std::make_shared<MyImplementClass>());
}

上述make_shared这一步,需要封装在库里,暴露一个函数接口,然后可以用上面那个模板函数进一步封装以方便使用。

注:

对纯虚方法的引用,可以直接编译通过,不需要链接方法实现。

所以使用者(程序本身)只需要include接口描述,就可以在代码里使用该接口类型的指针对象了,可以顺利编译通过,不需要链接。

然后实际运行时,就可以随意替换实现类(替换插件dll库),然后通过配置文件或其他手段,通知程序从某个dll插件加载实例即可。

使用者代码如下:

// 加载dll
HMODULE lib = LoadLibrary("xxx.dll");
// 解析函数指针
std::function<std::shared_ptr<ISomeInterface>(void)> getInstanceFunc = GetProcAddress(lib, "getInstance");
// 获得对象
this->myPlugInstance = getInstanceFunc();
// 通过接口随便操作咯
this->myPlugInstance->doSomething();

程序退出前别忘了通过FreeLibrary卸载dll库。

Linux下同理,只不过变成了.so库,同样有对应的系统API完成这些操作。

======== 更新 ========

前面的写法是手敲的,没考虑是否能编译,当伪码看就行。

评论中就遇到了问题——VC编译器给extern "C"添加了额外的约束,不能用来传递类对象。

所以我更新下实际业界中的实现,比上面的复杂一些,但是更实用。

实际实现中,使用了抽象工厂+单例两个设计模式:

  1. 软件框架统一提供一个插件工厂,用户通过约定的插件id(比如.COM的GUID,比如company.product.module.class这样的字符串标识)创建插件实例。
  2. 插件工厂提供注册接口,插件加载进内存后将各类型的构造器和id注册进去。
  3. 插件动态库制作一个static全局静态对象,构造函数里注册插件类,析构函数里取消注册,这样可以在动态库加载时自动注册,卸载时自动取消注册。
  4. 整个插件库不需要导出任何接口,因为纯虚接口无需链接,对象则是统一从框架的插件工厂获取。

代码如下,在MinGW和MSVC编译器上都可通过。

本方法需要开启RTII和C++11,实际上几乎所有C++插件框架,都依赖于RTII。

手机慎入,因为有大量模板。电脑可流畅阅读,已尽量控制行宽80字符。

// IPluginFactory.h 框架接口,所有用户代码/插件代码均链接这个框架库,类似Qt里的Qt5Core.dll
// IBase: 所有插件接口的基类,可以使用各类框架的Object类型,比如Qt的QObject
// 最好内置引用计数,如此处
struct IBase : public std::enable_shared_from_this<IBase>
{
    virtual ~IBase() = default;
};

// 插件工厂接口
struct IPluginFactory
{
    virtual ~IPluginFactory() = default;

    template<typename T>
    std::shared_ptr<T> createInstance(const std::string id)
    { return std::static_pointer_cast<T>(createInstanceWithBase(id); }

protected:
   virtual std::shared_ptr<IBase> createInstanceWithBase(const std::string& id) = 0;
};

// 整个dll,只需要导出这唯一一个符号,其他所有类都不需要导出
extern "C" IPluginFactory* getPluginFactory();



// PluginFactory.cpp
// 插件工厂实例,此处使用std::string作为类标识,便于使用
class PluginFactory : public IPluginFactory
{
public:
    PluginFactory() {}
    virtual ~MyPlugin() = default;

    bool registerClass(const std::string& id,
                       std::function<std::shared_ptr<IBase>()> constructor)
    {
        if (constructors.find(id) != constructors.end()) 
            return false;
        constructors[id] = constructor;
    }

    void unregisterClass(const std::string& id)
    { 
        auto it = constructors.find(id);
        if (it != constructors.end())
            constructors.erase(it);
    }

protected:
    virtual std::shared_ptr<IBase> createInstanceWithBase(const std::string& id)
    {
        auto it = factories.find(id);
        if(it == factories.end())
            return nullptr

上一篇: 纯函数和虚函数

下一篇: C++虚函数

推荐阅读