qt plugin

基于QT Plugin框架结构

日常总结 2009-04-24 18:56:02 阅读168 评论0   字号:大中小 订阅

一:qt plugin 介绍

      Qt Plugin和其他类型的插件一样,是一种计算机应用程序,它和主应用程序(host application)互相交互,以提供特定的功能。应用程序支持Plugin有许多原因,一些主要原因包括:使得第三方开发者有能力扩展应用程序,以提供无法先期预料的特色;减小应用程序的大小;由于软件版权之间的不兼容性将源代码和应用程序分享。Qt Plugin 分动态插件和静态插件两种。

二:qt plugin 创建和使用方法

Qt有两种与插件有关的API。一种用来扩展Qt本身的功能,如自定义数据库驱动,图像格式,文本编解码,自定义分格,等等,称为Higher-Level API。另一种用于应用程序的功能扩展,称为Lower-Level API。前一种是建立在后一种的基础之上的。这里讨论的是后一种,即用来扩展应用程序的Lower-level API

让应用程序支持插件扩展的步骤:

  1. 定义一个接口集(只有纯虚函数的类),用来与插件交流。

  2. 用宏Q_DECLARE_INTERFACE()将该接口告诉Qt元对象系统。

 Q_DECLARE_INTERFACE(BrushInterface,"com.trolltech.PlugAndPaint.BrushInterface/1.0")

  3. 应用程序中用QPluginLoader来装载插件。

  4. 用宏qobject_cast()来确定一个插件是否实现了接口。

 QObject *obj = new QTimer; 

 QTimer *timer = qobject_cast(obj);

写一个插件的步骤:

  1. 声明插件类,该类从QObject和该插件希望实现的接口继承而来。

  2. 用宏Q_INTERFACES()将该接口告诉Qt元对象系统。

   class BasicToolsPlugin : public QObject,

                            public BrushInterface,

                            public ShapeInterface,

                            public FilterInterface

   {

       Q_OBJECT

       Q_INTERFACES(BrushInterface ShapeInterface FilterInterface)

   public:

       ...

   };

  3. 用宏Q_EXPORT_PLUGIN2()导出插件。

  Q_EXPORT_PLUGIN2 ( PluginName, ClassName )

  4. 用适当的.pro文件构建插件。

下面的代码声明了一个接口类:

 

class FilterInterface

 {

 public:

        virtual ~FilterInterface() {}

        virtual QStringList filters() const = 0;

        virtual QImage filterImage(const QString &filter, const QImage &image, QWidget* parent)=0;

  };

Q_DECLARE_INTERFACE(FilterInterface, "com.trolltech.PlugAndPaint.FilterInterface/1.0")

这里是实现该接口的插件类的定义:

 

 #include

 #include

 #include

 #include

 class ExtraFiltersPlugin : public QObject, public FilterInterface

 {

 Q_OBJECT

 Q_INTERFACES(FilterInterface)

 public:

     QStringList filters() const;

     QImage filterImage(const QString &filter, const QImage &image,

     QWidget *parent);

 };

根据插件的类型不同,pro文件中配置上有不同。下面是pro文件分析:

TEMPLATE      = lib                                  // 声明为lib,动态和静态插件一样。

CONFIG       += plugin static                // 声明为plugin,带static表面为静态,否则为动态。

INCLUDEPATH  += ../..

HEADERS       = basictoolsplugin.h

SOURCES       = basictoolsplugin.cpp

TARGET        = $$qtLibraryTarget(pnp_basictools)           // 指明插件的名称

DESTDIR       = ../../plugandpaint/plugins

加载插件的主应用程序默认在当前目录下的plugins文件夹中寻找可用插件,如果是动态插件,则直接放在plugins文件夹中便可,如果是静态,则需要在主应用程序的main函数的开始的地方用宏:Q_IMPORT_PLUGIN(pluginname(pro文件中声明的一致))声明需要加载的插件并在工程配置中指明插件的lib位置。

三:基于qt plugin 技术的框架结构设想

1.      愿景

由于我们目前系统功能多,模块多,缺乏系统的整体性。我们想借助Qt Plugin技术,把各个独立的功能模块实现为一个个插件,统一在主体框架中,并能根据不同地方的用户的不同需求,在主框架中加载不同的功能模块,以实现整个系统的功能集中,体现出系统的整体性。

2.      plugin 接口

通过技术验证得出,目前我们采用动态插件,各个功能的插件实现定义的统一接口,具体功能放在插件界面中实现,此部分就像开发独立的应用程序,只是需要注意的是:

功能部分的主界面需要继承至插件界面基类:PluginWidget,插件接口中用具体的实现类指针给插件界面基类指针赋值,在加载插件的主框架中通过插件接口中定义的基类指针统一调用,利用C++动态技术动态识别具体指向的实现类。

插件界面类必须实现基类的虚函数:CreateActions()用于创建Action

创建Action需要使用基类的方法newAction创建,在此函数中加入了保存创建的Action功能。

插件接口定义如下:

class QPluginInterface

{

public:

    // 析构函数

    virtual ~QPluginInterface() {}

   

    // 插件名称

    virtual    QString    PluginName() = 0;

   

    // 插件显示在主框架中的图标文件路径

    virtual    QString PluginIconurl() = 0;

   

    // 插件提供的对外操作接口集

    virtual QList* Actions() = 0;

 

    // 创建插件提供的操作方法

    virtual    void CreateActions()=0;

 

    // 插件的主界面

    virtual QWidget* Widget() = 0;

protected:

    // 插件的主界面基类

    PluginWidget *pluginWidget;

};

插件界面基类定义如下:

class PluginWidget :public QMainWindow

{

    Q_OBJECT

public:

    PluginWidget(QWidget*parent=0);

    ~PluginWidget();

    QList*  Actions();

    virtual void      CreateActions(){}

    QAction *  newAction(const QIcon &icon,const QString &text,QObject*parent);

    QAction *         newAction(const QString &text,QObject*parent);

    void       AppendAction(QAction*act);

protected:

    // action链表

    QList *m_actlist;

};

下图是一个实现案例中各类之间的关系图:

3.      插件调用

插件在主框架中动态加载,目前考虑主框架基本结构是继承至QMainWindow,工具栏上显示当前加载的插件的功能键,并留有返回键可以回退到上一级。主工作区是一个QStackWidget,保存插件的界面,并把插件序号和插件对应的界面建立映射,保存在QMap中。通过序号到QStackWidget中切换界面。

下图是把DBManager做成插件加载到主框架的运行界面:

下图是把一个简单的绘图程序做成了插件,加载到主框架的运行界面:

四:总结

        目前只是通过实现两个动态插件在主框架中运行,基本算是功能性的验证,离具体实施还有很多工作需要进一步的研究,比如主框架的风格,插件的管理等等。由于本人的能力有限,可能有很多认识不够的地方,请指正。

 

 

 

 

  [QT]qt plugin插件

 

plugin是作为dll的一种特殊的应用而存在的. 也就

是说,linux下面,他是so的一种应用而已. 动态链接装入器例程 dlopen 需要在文件系统

中查找共享目标文件以打开文件并创建句柄. 4 种方式用以指定文件的位置:

  1.dlopen call 中的绝对文件路径

  2. LD_LIBRARY_PATH 环境变量中指定的目录中

  3. /etc/ld.so.cache 中指定的库列表之中,可以用命令更新ld.so.cache:

    ldconfig -C ld.so.cache -n ../lib -v

  4.先在 /usr/lib 之中,然后在 /lib 之中

 

今天主要是看了qt相关的plugin的东西. 并且尝试了一个plugin的程序,对于我们来说,

plugin是一个非常非常有用的东西,以后要多用.

对于qt而言,plugin是做成动态链接库的形式存在的,但是qtplugin有一些特殊的地方.

实上,qtplugin有两种,高层次和低层次的. qt的高层(high level)plugin是有qt

meta-object system自动管理的. 也就是说你必须把你的plugin放在特定的子目录下面不能

修改,qt会自动寻找这些plugin. 当然我们可以用QLibrary重新设定qt libpath,但是我觉

得这样是非常不礼貌的做法. 当然了,高层plugin也会自动搜寻系统的当前执行目录下的子

目录来寻找plugin. 高层的plugin包括了许多有用的东西,包括了image,textcode,style

.

而对于我们更加有用的低层次的plugin,这种plugin可以用QPluginLoader来加载,我们可以

利用qApp::applicationDirPath()来得到我们当前的程序运行path,然后利用

QDir::cd("plugins")这样的操作进入我们的plugins子目录,然后load. 对于这类的

plugin我们需要遵循下列的step

  ap:

    1.定义一个Interface,并且用Q_DECLARE_INTERFACE宏告知qt meta-object system有这

   样一个interface;

 2.QPluginLoader加载这个plugin,qobject_cast<>测试这个interface是否可用;

  plugin:

    1.定义一个plugin的类,必须从interface继承,并在.hQ_INTERFACE告诉meta-object

   system我们的类支持这样的interface;

 2.实现这个类并用Q_EXPORT_PLUGIN2(pluginName,class)导出这个plugin,建立合适的

   .pro文件来编译这个plugin

要注意的是默认的plugin是编译在debug模式下的,如果aprelease模式的话不能load

debug模式的plugin. 我们可以在.pro中加入"config += release"这样的语句来改变

build模式.

结构上,我们的interfaces.h(包含Q_DECLARE_INTERFACE)可以为applugin公共include

有问题. 我们要注意的有:

  1.interfaces.h(包含Q_DECLARE_INTERFACE);

  2.Q_INTERFACE(interface1 interface2 interface3)放在plugin.h;

  3.Q_EXPORT_PLUGIN2(pluginName,class)放在plugin.cpp;

  4.plugin.pro文件中一定要定义TARGET的内容,并且这个内容必须和Q_EXPORT_PLUGIN2

    pluginName一致;

  5.ap除了写代码load plugin之外不需要特殊的的宏;

这样我们就可以把我们的代码写成plugin,如果你觉得子目录也要做成可以定义的话当然

也可以,做成QSetting就好了.

下面是我们的试验程序,注意这个程序中用了下面语句来列举plugins目录下的所有

plugin.so:

 pluginsDir = QDir(qApp->applicationDirPath());

 pluginsDir.cd("plugins");

 foreach (QString fileName,pluginsDir.entryList(QDir::Files))

 {

        QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));

  ...

 }

 // 注意这段程序中用到了applicationDirPath来得到app的地址. 要用到这种功能的话

 // #include 才行,qApp不需要自己定义,系统会自动定义的.

 // 还有就是用到了foreach宏来列举dir下面的所有文件名. 显然比较可爱

我们的试验程序plugin叫做kitty(猫咪),ap叫做hunny,显然,我是想不到合适的名字罢了.

  kitty:

    .pro:

  ######################################################################

  # Automatically generated by qmake (2.00a) 11 9 10:37:28 2006

  ######################################################################

 

  TEMPLATE = lib

  CONFIG  += plugin

  INCLUDEPATH += ..  # "指定特殊的include path"

  TARGET  += kitty # "特别注意这里"

  DEPENDPATH += .

  INCLUDEPATH += .

 

  # Input

  HEADERS += kitty.h

  SOURCES += kitty.cpp

 .h:

  #ifndef KITTY_H

  #define KITTY_H

 

  #include

  #include

 

  class Kitty : public QObject,

       public KittyInterface

  {

 

  Q_OBJECT

  Q_INTERFACES(KittyInterface)

 

  public:

   // for KittyInterface

   void go();

  };

  #endif

 .cpp:

  #include

  #include "kitty.h"

 

  void

  Kitty :: go()

  {

   printf("Kitty :: go() called/n");

   printf("Kitty :: goto() /n");

  }

 

  Q_EXPORT_PLUGIN2(kitty,Kitty)

  hunny: // 我们把kitty作为plugin放在plugins目录下

    .pro: // 其实不需要做什么特殊的修改

  ######################################################################

  # Automatically generated by qmake (2.00a) 11 9 10:58:12 2006

  ######################################################################

 

  TEMPLATE = app

  TARGET +=

  DEPENDPATH += .

  INCLUDEPATH += .

 

  # Input

  HEADERS += hunny.h interfaces.h

  SOURCES += hunny.cpp main.cpp

 

  CONFIG  += debug

 .h:  // 也不需要特殊的动作

  #ifndef HUNNY_H

  #define HUNNY_H

 

  #include

  #include

  #include

  #include

  #include "interfaces.h"

 

  class Hunny : public QObject

  {

  Q_OBJECT

 

 

  public:

   void loadPlugins();

  

  private:

   QDir pluginsDir;

 

  };

  #endif

 .cpp: // load kitty而已

  #include "hunny.h"

 

  void

  Hunny :: loadPlugins()

  {

   printf("Hunny :: loadPlugins/n");

   pluginsDir = QDir(qApp->applicationDirPath());

   pluginsDir.cd("plugins");

  

   foreach (QString fileName,pluginsDir.entryList(QDir::Files))

   {

    QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));

    printf("loadPlugins : /

      fileName(%s)/n",fileName.toAscii().data());

    QObject *plugin = loader.instance();

    printf("loadPlugins : plugin = %#x/n",plugin);

    if (plugin)

    {

     KittyInterface *iKitty =

         qobject_cast(plugin);

     printf("loadPlugins : load KittyInterface,/

        iKitty = %#x/n",iKitty);

     if (iKitty)

     {

      iKitty->go();

     }

    }

   }

  }

  main.cpp:

  #include

  #include "hunny.h"

 

  int main(int argc,char *argv[])

  {

   QApplication app(argc,argv);

   Hunny h;

   h.loadPlugins();

   return app.exec();

  }

  interfaces.h:  // 公用的interfaces

  #ifndef INTERFACES_H

  #define INTERFACES_H

 

  class KittyInterface

  {

  public:

   virtual void go() = 0;

  

  };

 

  Q_DECLARE_INTERFACE(KittyInterface,

       "com.S1.Linxb.hunny.KittyInterface/1.0")

  // 这里的标识字符其实没有什么关系的

  #endif

 

 

 

 

 

 

 

 

Qt Plugin时要注意的几个细节

yanboo 发表于 2007-11-14 11:00:47

-

1.       接口文件中要包含QtPlugin头文件
2.
对于小程序,尤其是一个人边想边做的这种,主程序和插件同步开发时,最好只用一份接口文件,其他插件工程中直接加入该文件,不要再拷贝到其工程目录下,这样修改接口的话较容易。 记得把接口文件所在目录包含进其他工程中。
3.
有自己的界面的插件,它所实现的接口中至少要有启动和退出函数。
4.
退出插件时, 调用插件提供的退出函数,然后在主程序插件列表中删除该对象。这样就等到主程序退出时才能释放dll,理论上用QPluginLoaderunload可以彻底卸载,但我发现这个函数有些问题,会导致系统崩溃(Qt4.3.0)
5.
一般的工具插件可以用exec函数启动,这样把当前程序控制权彻底交到插件界面。若用show启动插件界面,需要指定窗口为WindowModal,即使这样,Windows任务栏也会显示的像两个程序一样。(这是错误的,初始化插件中的窗体时没有指定正确的父窗口是导致该问题的原因。)
6.

 

 

编写qtPlugin及注意事项

2010-01-28 15:34

1)主程序,测试加载插件
#include "interfaces.h"
#include
#include
#include

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "start";
QPluginLoader pluginLoader("testd.dll");
QObject *plugin = pluginLoader.instance();
qDebug() << QObject::tr("Load statue:%1").arg(pluginLoader.isLoaded()?QObject::tr("Load OK"):pluginLoader.errorString());
if (plugin)
{                
VectorLayer * layer = qobject_cast(plugin);
qDebug()<< "start to access layer";
if (layer)
{    
qDebug()<< "access layer";       
qDebug() <GetHello();
layer->SetHello("my layer is very good");
qDebug() <GetHello();
}
}
pluginLoader.unload();
return a.exec();
}

为使qDebug信息能够正常输出,需要在主程序工程pro文件中加入
CONFIG += qt console

2
)共享的接口的头文件
/*
接口,包括矢量图层、栅格数据
*/

#ifndef INTERFACES_H
#define INTERFACES_H

#include
#include

QT_BEGIN_NAMESPACE
class QString;
class QStringList;
QT_END_NAMESPACE

class DataInterface: public QObject
{
public:
virtual ~DataInterface(){};
};

class VectorLayer: public DataInterface
{
public:
virtual ~VectorLayer() {}
virtual QString GetHello()=0;
virtual void SetHello(QString Msg)=0;
};

QT_BEGIN_NAMESPACE

Q_DECLARE_INTERFACE(VectorLayer,"com.microinfospace.Goldmap.VectorLayer/1.0")

QT_END_NAMESPACE

#endif

这里的Q_DECLARE_INTERFACE需要特别留意,这段字符串分成三部分
VectorLayer                                                       接口名称
com.microinfospace.Goldmap.VectorLayer         描述,一版是公司网址啥的
0.1                                                                     版本号


3
)一个test插件的实现,主要功能,输出字符串,更改字符串内容
// test.h
#ifndef TEST_H
#define TEST_H
#include
#include
#include "interfaces.h"

class TestHello: public VectorLayer
{
Q_OBJECT
Q_INTERFACES(VectorLayer)
private:
QString CurMsg;
public:
QString GetHello();
void SetHello(QString Msg);
};

#endif


//test.cpp
#include
//-------------------------
#include
#include "test.h"

QString TestHello::GetHello()
{
return CurMsg;
}

void TestHello::SetHello(QString Msg)
{
CurMsg=Msg;
}

QT_BEGIN_NAMESPACE

Q_EXPORT_PLUGIN2(test, TestHello)

QT_END_NAMESPACE

这里的Q_EXPORT_PLUGIN2需要注意
test            
是插件名
TestHello  
是这个插件的类名

下图为运行结果

 

 

 

 

http://topic.csdn.net/u/20100708/09/9ecfd9a5-3991-4287-b588-d0c77be931b2.html

你可能感兴趣的:(qt plugin)