Skip to content
rhcad edited this page Feb 15, 2012 · 9 revisions

如何实现插件

一、定义接口

在一个H文件中定义接口,此接口从 x3::IObject 派生,包含纯虚函数,使用 X3DEFINE_IID(接口名) 定义接口ID。例如下面定义了有两个纯虚函数的接口 ISimple:

class ISimple : public x3::IObject
{
    X3DEFINE_IID(ISimple);
    virtual int add(int a, int b) const = 0;
    virtual int subtract(int a, int b) const = 0;
};

X3DEFINE_IIDx3::IObject 是在 iobject.h 中定义的,通常包含 objptr.h 更方便些,下面是接口文件的完整形式:

#ifndef X3_EXAMPLE_ISIMPLE_H
#define X3_EXAMPLE_ISIMPLE_H

#include <objptr.h>

class ISimple : public x3::IObject
{
    X3DEFINE_IID(ISimple);
    virtual int add(int a, int b) const = 0;
    virtual int subtract(int a, int b) const = 0;
};

#endif

其中 #ifndef 的“头文件卫士”形式适用于VC++和GCC等绝大多数编译器,在VC++中也可以使用 #pragma once 的简单形式代替。

既可以在每个H文件只定义一个接口,也可以在一个H文件中定义多个接口,根据复杂程度和喜好决定。接口文件名和接口命名风格根据自己需要决定。

二、定义类UID

一个组件类如果要让外部模块能创建对象实例,需要指定组件类的全局唯一标识信息,一般采用GUID串来标识组件类。下面的例子定义了一个类UID常量clsidSimple:

const char* const clsidSimple = "94071767-ba6b-4769-9eb4-2ebf469289f3";

既可以在单独的H文件中定义类UID,也可以与接口定义在同一个文件中。通常习惯:如果一个组件类支持多个接口,则将类ID定义在单独的H文件,然后每个接口文件包含此类ID文件;如果一个组件类仅支持一个接口(除IObject外),为方便起见则将类UID和接口定义在同一个文件中。

注意:类UID不可重复(复制已有接口文件时容易发生),否则将会覆盖已向管理器注册的类,导致其他模块无法创建原来类的接口对象。

三、实现接口

实现接口就是定义一个类,从接口派生并实现其接口函数。由于没有使用C++的RTTI机制,所以使用 X3BEGIN_CLASS_DECLARE 等宏来指定一个类实现了哪些接口,这样就可以从一种接口转换到另一种接口。这些宏是在 module/classmacro.h 中定义的。

#ifndef X3_EXAMPLE_SIMPLE_IMPL_H
#define X3_EXAMPLE_SIMPLE_IMPL_H

#include <plsimple/isimple.h>   // 包含接口定义

class CSimple : public ISimple  // 从接口派生
{
    X3BEGIN_CLASS_DECLARE(CSimple, clsidSimple) // 指定类ID
        X3DEFINE_INTERFACE_ENTRY(ISimple)       // 指定实现的接口
    X3END_CLASS_DECLARE()
protected:
    CSimple();
    virtual ~CSimple();

private:
    virtual int add(int a, int b) const;
    virtual int subtract(int a, int b) const;
};

#endif

上面是CSimple实现类的例子。其中构造函数和析构函数申明为保护函数,所实现的接口函数申明为私有函数,这是推荐方式,但不是必须的。这样做是为了明确表明不允许直接实例化和delete销毁(当然也不可能实例化,因为IObject函数还没有实现),也不允许直接调用实现类的接口函数。

一个实现类可以实现一个或多个接口,还可以从已有的实现类派生以便继承所有的接口实现(实现继承),或者重载已实现的接口函数。下面是实现继承的一个例子:

#ifndef X3_EXAMPLE_SIMPLE_IMPL2_H
#define X3_EXAMPLE_SIMPLE_IMPL2_H

#include "plsimple.h"           // 包含基实现类
#include <plsimple/isimple2.h>  // 包含附加接口
#include <plsimple/isimple3.h>

class CSimple2
    : public CSimple            // 从基实现类派生
    , public ISimple2           // 从附加接口派生
    , public ISimple3
{
    X3BEGIN_CLASS_DECLARE(CSimple2, clsidSimple)
        X3DEFINE_INTERFACE_ENTRY(ISimple2) // 指定实现的接口
        X3DEFINE_INTERFACE_ENTRY(ISimple3)
        X3USE_INTERFACE_ENTRY(CSimple)     // 继承已实现的所有接口
    X3END_CLASS_DECLARE()
protected:
    CSimple2() {}

private:
    virtual int add(const std::vector<int>& nums) const;
    virtual x3::AnyObject createSimple();
};

#endif

四、登记实现类

插件中的实现类一般不直接用于实例化对象,而是通过 x3::Object 智能指针模板类来创建对象的。需要在插件中登记有哪些可供实例化的类、是否是单实例类。

登记实现类的具体做法是在工程的一个CPP文件(通常为 module.cpp)中,包含 pluginimpl.hmodulemacro.h 文件,然后使用 XBEGIN_DEFINE_MODULE 等宏来申明有哪些实现类。例如:

#include <module/plugininc.h>
#include <module/pluginimpl.h>      // 实现插件的导出函数
#include <module/modulemacro.h>     // 登记实现类的宏定义

#include "plsimple.h"               // 包含实现类

XBEGIN_DEFINE_MODULE()
    XDEFINE_CLASSMAP_ENTRY(CSimple) // 登记普通实现类或单实例类
    XDEFINE_CLASSMAP_ENTRY_Singleton(YourSingletonClass)
XEND_DEFINE_MODULE_DLL()            // 插件动态库

OUTAPI bool x3InitializePlugin()    // 插件加载时执行,用于额外初始化
{
    return true;
}

OUTAPI void x3UninitializePlugin()  // 插件卸载时执行,用于释放额外数据
{
}

其中 x3InitializePlugin()x3UninitializePlugin() 函数由自己实现,用于额外的初始化和释放操作。这两个函数可以在module.cpp或其他CPP文件中实现,在同一个动态库或程序模块中只能实现一次。

如果某个插件不实现任何接口,只使用其他插件的接口,则可以在module.cpp中使用下面更简单的形式:

#include <module/plugininc.h>
#include <module/pluginimpl.h>
#include <module/modulemacro.h>
XDEFINE_EMPTY_MODULE()
Clone this wiki locally