zoukankan      html  css  js  c++  java
  • 转:自己动手写插件框架(2)

    转自:http://www.devbean.net/2012/03/building-your-own-plugin-framework-2/

    插件编程接口

    所谓插件,其实就是基于接口的设计。基于插件的系统最基本的一点就是,要有一个中心系统,用于加载未知的插件,并且能够使用预先定义好的接口和协议与这些插件进行交互。

    最基本的方式是定义一个接口,提供一系列插件(动态的或者是静态)需要暴露出的函数。这种实现从技术上说是可行的,但实际并不那么简单地操作。原因在于,一个插件需要支持两类接口,但是却只能暴露出一个接口的函数集。这意味着,两类接口必须混合在一起。

    第一个接口(协议)是通用插件接口。该接口允许中心系统初始化插件,能够将插件提供的用于创建、销毁对象的函数注册给中心系统。这个通用插件接口不是特定领域相关的,因此能够作为一个可复用库。第二个接口则是插件对象提供的功能接口。这个接口是与特定领域相关的,必须被仔细地设计,并且由插件实际实现。中心系统应当利用这个接口与插件对象进行交互。

    下面我们给出一个通用插件接口的头文件。这里,我们不会深究细节,仅仅为了有个相对直观地认识。

     1 #ifndef PF_PLUGIN_H
     2 #define PF_PLUGIN_H
     3  
     4 #include <apr-1/apr_general.h>
     5  
     6 #ifdef __cplusplus
     7 extern "C" {
     8 #endif
     9  
    10 typedef enum PF_ProgrammingLanguage
    11 {
    12     PF_ProgrammingLanguage_C,
    13     PF_ProgrammingLanguage_CPP,
    14 }   PF_ProgrammingLanguage;
    15  
    16 struct PF_PlatformServices_;
    17  
    18 typedef struct PF_ObjectParams
    19 {
    20     const apr_byte_t * objectType;
    21     const struct PF_PlatformServices_ * platformServices;
    22 } PF_ObjectParams;
    23  
    24 typedef struct PF_PluginAPI_Version
    25 {
    26     apr_int32_t major;
    27     apr_int32_t minor;
    28 } PF_PluginAPI_Version;
    29  
    30 typedef void * (*PF_CreateFunc)(PF_ObjectParams *);
    31  
    32 typedef apr_int32_t (*PF_DestroyFunc)(void *);
    33  
    34 typedef struct PF_RegisterParams
    35 {
    36     PF_PluginAPI_Version version;
    37     PF_CreateFunc createFunc;
    38     PF_DestroyFunc destroyFunc;
    39     PF_ProgrammingLanguage programmingLanguage;
    40 } PF_RegisterParams;
    41  
    42 typedef apr_int32_t (*PF_RegisterFunc)(const apr_byte_t * nodeType,
    43                                        const PF_RegisterParams * params);
    44  
    45 typedef apr_int32_t (*PF_InvokeServiceFunc)(const apr_byte_t * serviceName,
    46                                             void * serviceParams);
    47  
    48 typedef struct PF_PlatformServices
    49 {
    50     PF_PluginAPI_Version version;
    51     PF_RegisterFunc registerObject;
    52     PF_InvokeServiceFunc invokeService;
    53 } PF_PlatformServices;
    54  
    55 typedef apr_int32_t (*PF_ExitFunc)();
    56  
    57 typedef PF_ExitFunc (*PF_InitFunc)(const PF_PlatformServices *);
    58  
    59 #ifndef PLUGIN_API
    60   #ifdef WIN32
    61     #define PLUGIN_API __declspec(dllimport)
    62   #else
    63     #define PLUGIN_API
    64   #endif
    65 #endif
    66  
    67 extern
    68 #ifdef  __cplusplus
    69 "C"
    70 #endif
    71 PLUGIN_API PF_ExitFunc PF_initPlugin(const PF_PlatformServices * params);
    72  
    73 #ifdef  __cplusplus
    74 }
    75 #endif
    76  
    77 #endif /* PF_PLUGIN_H */

    你需要认识到的第一件事是,这是一个 C 头文件。这就允许我们的插件框架能够被纯 C 系统编译和使用,并且能够编写纯 C 插件。但是,这么做并不会限定必须使用 C,实际上,它已经被设计为更常用 C++ 来实现。

    PF_ProgrammingLanguage枚举允许插件告诉插件管理器,它本身是由 C 还是 C++ 实现的。

    PF_ObjectParams是一个抽象结构,在创建插件对象时被传入。

    PF_PluginAPI_Version用于指明版本信息。这有助于插件管理器只加载兼容版本的插件。

    函数指针PF_CreateFuncPF_DestroyFunc必须由插件实现,用于插件管理器创建和销毁插件对象。

    PF_RegisterParams结构包含了插件必须提供给插件管理器的所有信息,以便插件管理器初始化插件(版本,创建、销毁函数以及开发语言)。

    PF_RegisterFunc函数指针(由插件管理器实现)允许每个插件将其支持的对象类型以PF_RegisterParams结构的形式注册给插件管理器。注意,这种实现允许插件注册不同版本的对象,以及注册多个对象类型。

    PF_InvokeService函数指针是一个通用函数,允许插件调用主系统提供的各种服务,例如日志、事件处理或者错误报告等。该函数要求有一个服务名称以及一个指向参数结构的不透明的指针。插件应当知道可用的服务以及如何调用它们(或者实现一种服务发现机制)。

    PF_PlatformServices结构用于表示平台提供的所有服务(版本、已注册对象和调用函数)。该结构会在插件初始化的时候传给每一个插件。

    PF_ExitFunc是插件退出函数的指针,由插件实现。

    PF_InitFunc是插件初始化的函数指针。

    PF_initPlugin是动态插件(也就是通过动态链接库或者共享库部署的插件)初始化函数的实际声明。它由动态插件暴露出,所以插件管理器可以在加载插件时进行调用。它有一个指向PF_PlatformServices结构的指针,所以在插件初始化时,这些服务都是可以调用的(这正是注册对象的理想时机),函数返回退出函数的指针。

    而对于静态插件(由静态链接库实现,并且直接与主应用程序链接的插件)应该实现init函数,但是不能命名为PF_initPlugin。原因是,如果有多个静态插件,它们不能有相同的名字的函数。

    静态插件的初始化过程有所不同。它们必须由主程序显式地进行初始化,也就是通过PF_InitFunc调用其初始化函数。这实际是不好的设计,因为如果要新增或者删除静态插件,主应用的代码都必须修改,并且那些不同名字的init函数都必须能够找到。

    有一个叫做“自动注册”的技术试图解决这个问题。自动注册由一个静态库的全局对象实现。该对象会在main()函数执行之前构造完成。这个全局对象能够请求插件管理器初始化静态插件(通过传递插件init()函数的指针来完成)。不幸的是,在某些版本的 Visual C++ 中,这种技术并不支持。

    编写插件

    如何编写插件?我们的插件框架提供了最通用的功能,在目前的条件下很难添加能够与主应用交互的插件。所以,你必须再次插件框架的基础之上构建自己的应用程序对象。这意味着,你的应用程序(加载插件的)连同插件本身,都必须遵守同一个交互模型。通常这代表,应用程序需要插件提供特定类型的对象,用于暴露某些特定的 API。插件框架提供所有必须的公共基础代码,用于插件的注册、枚举以及加载。

    下面的例子是 C++ 接口定义的IActor。这个接口有两个操作:getInitialInfo()play()。注意,这个接口并不足以应付所有情况,因为getInitialInfo()函数需要一个指向ActorInfo结构的指针,而play()则需要另外一个接口ITurn的指针。这是经常遇见的情况,你必须这么设计,并且指定一个特定的对象模型。

    1 struct IActor
    2 {
    3     virtual ~IActor() {}
    4     virtual void getInitialInfo(ActorInfo * info) = 0;
    5     virtual void play( ITurn * turnInfo) = 0;
    6 };

    每个插件都可以注册IActor接口的多个实现。当应用程序决定实例化一个由插件注册的对象时,它就调用由插件实现的PF_CreateFunc函数。插件就会做出响应,创建对象并返回给应用程序。函数返回值是void *,因为对象的创建操作是通用插件框架的一部分,因此并不知道任何关于特定的IActor接口的信息。应用程序负责将void *转换成IActor *,然后像其他对象一样通过接口调用其函数。当应用程序使用完IActor对象时,会调用注册的PF_DestroyFunc函数,插件就销毁该对象。至于为什么需要有虚析构函数,我们会在以后的讨论中介绍。

  • 相关阅读:
    python常用模块(3)
    python中的re模块
    python中的常用模块
    python中的模块及路径
    python中的文件操作(2)
    【weixin】微信支付简介
    【其他】博客园样式修改
    【weixin】微信企业号和公众号区别和关系是什么?
    【其他】./ 和../ 以及/区别
    【sdudy】ASCII,Unicode和UTF-8终于找到一个能完全搞清楚的文章了
  • 原文地址:https://www.cnblogs.com/kira2will/p/3940571.html
Copyright © 2011-2022 走看看