• c++中如何为一个程序写扩展


    c++中如何为一个程序写扩展

    https://www.zhihu.com/question/52538590

    作者:mnzn2530
    链接:https://www.zhihu.com/question/52538590/answer/2421788573

    首先,不同的范围选择是不一样的。

    1。 如果是同一个编译器,同一个版本,你可以直接用动态库,导出函数,变量,类都是可以的。

    2。 如果不同的编译器,那么COM基本上是唯一的选择,当然COM的强大不止这一点,遵守com规范,你的插件可以给vba用,给微软的wscript用,可以直接嵌入到office里用。

    3。 如果跨平台,那么qt的plugin很不错,但是plugin的实现感觉和com还不大一样,qt的plugin只导出了两个函数,但是它也可以直接使用导出类。。非常方便。。。。

    当然,这都是在有头文件的情况下。。。如果没有头文件,你完全不知道dll里在干啥。。。那就要参考以前的外挂怎么写了。。。。也不是不行。。。。

    作者:诸葛不亮
    链接:https://www.zhihu.com/question/52538590/answer/137136003

    我简单的说明下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;
            return it->second();
        }
    
    private:
        PluginFactory(const PluginFactory&) = delete;
        PluginFactory& operator=(const PluginFactory&) = delete;
    
        // 构造各个类实例的工厂函数
        std::unordered_map<std::string, std::function<std::shared_ptr<IBase>()>> constructors;
    };
    
    extern "C" IPluginFactory* getPluginFactory()
    {
       static PluginFactory instance;
       return &instance;
    }
    
    
    
    // IMyPlugin.h 
    // 类接口
    struct IMyClassA : public IBase
    {
        virtual void func() const = 0;
    };
    
    
    
    // MyPlugin.cpp
    // 类实例
    class MyClassA : public IMyClassA
    {
    public:
        MyClassA() = default;
        virtual ~MyClassA() = default;
    
        virtual void func() const { std::cout << "hello world" << std::endl; }
    };
    
    // 插件自动注册
    struct MyPlugin
    {
        MyPlugin()
        {
            PluginFactory* factory = dynamic_cast<PluginFactory*>(getPluginFactory());
            factory->registerClass("MyPlugin.MyClassA",
                                   []{ return std::static_pointer_cast<IBase>(std::shared_ptr<MyClassA>::create()); });
        }
        ~MyPlugin()
        {
            PluginFactory* factory = dynamic_cast<PluginFactory*>(getPluginFactory());
            factory->unregisterClass("MyPlugin.MyClassA");
        }
    }
    static MyPlugin myPlugin;
    
    
    
    // main.cpp
    int main(int argc, char* argv[])
    {
        (void)argc;
        (void) argv;
    
        // 加载插件
        HANDLE hModule = ::LoadLibrary("MyPlugin.dll");
    
        // 获取实例
        IPluginFactory* factory = getPluginFactory();
        auto myClassA = factory->createInstance<IMyClassA>("MyPlugin.MyClassA");
        if (myClassA)
            myClassA->func();
    
        // 释放插件
        myClassA.reset();
        ::FreeLibrary (hModule);
    
       return 0;
    }
    

    实际实现中,各插件框架,都会有个宏用于声明插件,这个宏有一般是放置在全局域,展开后自动生成类似MyPlugin类的代码和对象声明。

    这个宏的输入参数里至少会包含插件类的ID和插件类名。

    虽然全局静态对象的初始化顺序是未知的,但这里的依赖是安全的——工厂对象是函数内的静态对象,只有在函数调用时才会完成初始化,所以可以确保,宏定义出的这个全局对象构造时,通过getPlugin()肯定能获取到已经初始化完成的插件工厂。

     

    对了,如果你约束好所有插件接口函数的输入输出类型,通过可以跨编译器平台的数据结构实现,如C++11的标准布局(Standard Layout),那么你这个插件哪怕是用MinGW编译的,也能拿给MSVC使用——.COM就是这么实现的。

     

    最后的就是一些扩展功能了,比如运行时热更新:

    1. 为IBase提供start()/stop()两个方法,并且在IBase的构造/析构中,更新框架内部持有的对象列表;
    2. 热更新插件时,根据对象列表,stop()该插件所有类实例;
    3. 就地析构(显示调用构造函数),销毁该插件所有类实例;
    4. 卸载插件,更新动态库,重新加载插件;
    5. 就地构造(placement new),在原有插件对象的指针地址上重新创建类实例,以避免用户代码里持有的插件对象指针失效。

    如果再通过命令模式/状态机等约束插件行为的话,甚至可以还原卸载前的现场。

    ================ End

  • 相关阅读:
    6月11日 python学习总结 框架理论
    6月7日 python 复习 collections
    6月6日 python复习 面向对象
    6月6日 python学习总结 jQuery (三)
    6月5日 python复习 模块
    6月5日 python学习总结 jQuery (二)
    6月4日 python学习总结 装饰器复习
    Redis源码分析(五)--- sparkline微线图
    Redis源码分析(六)--- ziplist压缩列表
    Redis源码分析(六)--- ziplist压缩列表
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/16104540.html
Copyright © 2020-2023  润新知