首页 插件 自己动手写插件框架(6)

自己动手写插件框架(6)

0 1.7K

插件系统组件

本章节主要描述通用插件框架的主组件及其作用。

DynamicLibrary

DynamicLibrary组件是一个简单的跨平台 C++ 类。它使用 dlopen/dlclose/dlsym 系统调用(UNIX 以及 OS X)和 LoadLibrary/FreeLibrary/GetProcAddress API 调用(Windows)。

下面是DynamicLibrary的头文件:

#ifndef DYNAMIC_LIBRARY_H
#define DYNAMIC_LIBRARY_H

#include <string>

class DynamicLibrary
{
public:
    static DynamicLibrary * load(const std::string & path,
                                 std::string &errorString);
    ~DynamicLibrary();

    void * getSymbol(const std::string & name);
private:
    DynamicLibrary();
    DynamicLibrary(void * handle);
    DynamicLibrary(const DynamicLibrary &);
private:
    void * handle_;
};
#endif

每个动态库都是一个DynamicLibrary类的实例。加载动态库会调用静态的load()函数。如果一切正常,该函数返回一个DynamicLibrary指针,否则返回 NULL。输出参数errorString包含错误信息(如果有的话)。由于各个平台上面对于动态加载的机制都有不同,所以会将用于表示这个已加载完毕的库的平台相关的句柄保存下来,因此可以在以后获取符号已经卸载。

getSymbol()函数用于从已加载库中获取符号以及用于卸载库所需的析构函数(delete 指针)。

有许多不同方法用于加载动态库。为简单起见,DynamicLibrary在每一平台上只选择一种方法。当然,我们也可以进行扩展,但是这么一来,不同平台需要提供不同接口,我们的代码就不会很简单了。

PluginManager

PluginManager是插件框架的核心。与插件相关的所有事情都要通过PluginManager解决。下面我们给出PluginManager的头文件:

#ifndef PLUGIN_MANAGER_H
#define PLUGIN_MANAGER_H

#include <vector>
#include <map>
#include <apr-1/apr.h>
#include <boost/shared_ptr.hpp>
#include "plugin_framework/plugin.h"

class DynamicLibrary;

struct IObjectAdapter;

class PluginManager
{
    typedef
        std::map<std::string, boost::shared_ptr<DynamicLibrary> > DynamicLibraryMap;
    typedef std::vector<PF_ExitFunc> ExitFuncVec;
    typedef std::vector<PF_RegisterParams> RegistrationVec;
public:
    typedef std::map<std::string, PF_RegisterParams> RegistrationMap;

    static PluginManager & getInstance();
    static apr_int32_t initializePlugin(PF_InitFunc initFunc);

    apr_int32_t loadAll(const std::string & pluginDirectory,
                        PF_InvokeServiceFunc func = NULL);
    apr_int32_t loadByPath(const std::string & path);
    void * createObject(const std::string & objectType, IObjectAdapter & adapter);
    apr_int32_t shutdown();
    static apr_int32_t registerObject(const apr_byte_t * nodeType,
                                      const PF_RegisterParams * params);
    const RegistrationMap & getRegistrationMap();
private:
    ~PluginManager();
    PluginManager();
    PluginManager(const PluginManager &);

    DynamicLibrary * loadLibrary(const std::string & path,
                                 std::string & errorString);
private:
    bool                inInitializePlugin_;
    PF_PlatformServices platformServices_;
    DynamicLibraryMap   dynamicLibraryMap_;
    ExitFuncVec         exitFuncVec_;
    RegistrationMap     tempExactMatchMap_;   // 注册完全匹配的对象类型
    RegistrationVec     tempWildCardVec_;     // 通配符 ('*') 对象类型
    RegistrationMap     exactMatchMap_;       // 注册完全匹配的对象类型
    RegistrationVec     wildCardVec_;         // 通配符 ('*') 对象类型
};
#endif

应用程序通过调用PluginManager::loadAll()加载插件,其参数为插件目录。PluginManager加载所有动态插件并且进行初始化。它会在dynamicLibraryMap_保存所有动态插件库,在exitFuncVec_保存所有退出函数指针(动态插件的和静态插件的),在exactMatchMap_保存所有注册的对象类型。通配符则在 wildCardVec_注册。现在,PluginManager已经准备好创建插件对象了。如果有静态插件,那么它们也会被注册(可能由应用程序注册,也可能是自动注册)。

在插件初始化过程中,PluginManager在临时数据结构中保存所有注册信息,注册成功之后会将这些信息整合进exactMatchMap_wildCardVec_,注册失败则丢弃。这种类似事务的行为保证了所有存储的信息都来自已经在内存中的并且成功初始化的插件。

当应用程序需要创建新的插件对象时(不论是动态的还是静态的),它会调用PluginManager::createObject()函数,其参数是对象类型和适配器。PluginManager使用注册的PF_CreateFunc函数创建对象,并且如果是 C 对象的话(根据注册结构中的PF_ProgrammingLanguage判断),需要将其从 C 适配到 C++。

此时,PluginManager已经完成工作。之后,应用程序同插件对象直接交互并最终销毁它。PluginManager则开心地将这些交互完全忽略。在插件卸载之前,销毁插件对象是由应用程序负责(至少应用程序要保证在插件卸载之后不能再调用其函数)。

PluginManager也需要负责插件卸载。当应用程序销毁所有创建的插件对象之后,需要调用PluginManager::shutdown()函数。shutdown()函数调用所有插件(静态的和动态的)的退出函数,卸载所有动态插件,并且清空内部数据结构。通过 loadAll()函数也可以“重启”PluginManager。应用程序也可以通过调用PluginManager::initializePlugin()函数“重启”所有静态插件。那些自动注册的静态插件直接就可以投入使用。

如果应用程序忘记调用shutdown()PluginManager应该在其析构函数中调用。

ObjectAdapter

ObjectAdapter的工作是,在创建对象的过程中,将 C 插件对象适配成 C++ 插件对象。IObjectAdapter接口很直接。它定义了一个单独的函数(除了虚析构函数)—— adapt。adapt()函数接受一个 void 指针,这个指针应该指向一个 C 对象和一个能够销毁这个 C 对象的PF_DestroyFunc函数指针。其返回值是包装了这个 C 对象的 C++ 对象。应用程序需要负责提供合适的包装类。插件框架不能做这个工作,因为这项工作需要获知应用程序对象模型。但是,插件框架可以提供一个ObjectAdapter模板,其作用就是将一个 C 对象转换成其包装类的static_cast运算符。

适配的结果可以传递给任何需要 C++ 对象的接口。这将会是我们下一章的核心问题,所以现在先不要关心这个。这里的主要问题是,ObjectAdapter模板提供了IObjectAdapter接口的实现,用于将 C 插件对象与 C++ 包装类之间的互换。

下面我们给出ActorFactory的代码。这是一个ObjectAdapter模板的特定子类。它的作用是将实现了C_Actor接口的 C 对象转换成实现了 IActor接口的 C++ 包装类ActorAdapterActorFactory同时提供一个静态的createActor()函数。该函数调用PluginManagercreateObject()函数,将其自身作为适配器,把返回的 void 指针转换成IActor指针。这允许应用程序仅提供一个 actor 类型,方便地调用createActor()静态函数,无需关心适配器及其转换操作。

#ifndef ACTOR_FACTORY_H
#define ACTOR_FACTORY_H

#include "plugin_framework/PluginManager.h"
#include "plugin_framework/ObjectAdapter.h"
#include "object_model/ActorAdapter.h"

struct ActorFactory : public ObjectAdapter<ActorAdapter, C_Actor>
{
    static ActorFactory & getInstance()
    {
        static ActorFactory instance;
        return instance;
    }

    static IActor * createActor(const std::string & objectType)
    {
        void * actor = PluginManager::getInstance().createObject(objectType,
                                                                 getInstance());
        return (IActor *)actor;
    }
};
#endif // ACTOR_FACTORY_H

PluginRegistrar

PluginRegistrar允许静态插件通过PluginManager自动注册其对象,无需应用程序显式初始化。其实现是提供一个全局的PluginRegistrar实例,传递给初始化函数(一个符合PF_InitFunc的签名)。PluginRegistrar调用PluginManager::initializePlugin()函数,初始化静态插件,就像初始化已加载的动态插件一样。

#ifndef PLUGIN_REGISTRAR_H
#define PLUGIN_REGISTRAR_H

#include "plugin_framework/PluginManager.h"

struct PluginRegistrar
{
    PluginRegistrar(PF_InitFunc initFunc)
    {
        PluginManager::initializePlugin(initFunc);
    }
};
#endif // PLUGIN_REGISTRAR_H

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2