1、基于 QT Plugin 框架结构 日常总结 2009-04-24 18:56:02 阅读 168 评论 0 字号:大中小 订阅 一:qt plugin 介绍Qt Plugin 和其他类型的插件一样,是一种计算机应用程序,它和主应用程序(host application)互相交互,以提供特定的功能。应用程序支持 Plugin 有许多原因,一些主要原因包括:使得第三方开发者有能力扩展应用程序,以提供无法先期预料的特色;减小应用程序的大小;由于软件版权之间的不兼容性将源代码和应用程序分享。Qt Plugin 分动态插件和静态插件两种。二:qt plugin 创建和使用方法Qt 有两种与插件有关的
2、API。一种用来扩展 Qt 本身的功能,如自定义数据库驱动,图像格式,文本编解码,自定义分格,等等,称为 Higher-Level API。另一种用于应用程序的功能扩展,称为 Lower-Level API。前一种是建立在后一种的基础之上的。这里讨论的是后一种,即用来扩展应用程序的 Lower-level API。让应用程序支持插件扩展的步骤:1. 定义一个接口集(只有纯虚函数的类),用来与插件交流。2. 用宏 Q_DECLARE_INTERFACE()将该接口告诉 Qt 元对象系统。Q_DECLARE_INTERFACE(BrushInterface,“com.trolltech.PlugA
3、ndPaint.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,pu
4、blic ShapeInterface,public FilterInterfaceQ_OBJECTQ_INTERFACES(BrushInterface ShapeInterface FilterInterface)public:.;3. 用宏 Q_EXPORT_PLUGIN2()导出插件。Q_EXPORT_PLUGIN2 ( PluginName, ClassName )4. 用适当的.pro 文件构建插件。下面的代码声明了一个接口类:class FilterInterfacepublic:virtual FilterInterface() virtual QStringList filt
5、ers() const = 0;virtual QImage filterImage(const QString ;Q_DECLARE_INTERFACE(FilterInterface, “com.trolltech.PlugAndPaint.FilterInterface/1.0“)这里是实现该接口的插件类的定义:#include #include #include #include class ExtraFiltersPlugin : public QObject, public FilterInterfaceQ_OBJECTQ_INTERFACES(FilterInterface)pu
6、blic:QStringList filters() const;QImage filterImage(const QString ;根据插件的类型不同,pro 文件中配置上有不同。下面是 pro 文件分析:TEMPLATE = lib / 声明为 lib,动态和静态插件一样。CONFIG += plugin static / 声明为 plugin,带 static 表面为静态,否则为动态。INCLUDEPATH += /HEADERS = basictoolsplugin.hSOURCES = basictoolsplugin.cppTARGET = $qtLibraryTarget(pnp
7、_basictools) / 指明插件的名称DESTDIR = /plugandpaint/plugins加载插件的主应用程序默认在当前目录下的 plugins 文件夹中寻找可用插件,如果是动态插件,则直接放在 plugins 文件夹中便可,如果是静态,则需要在主应用程序的 main 函数的开始的地方用宏:Q_IMPORT_PLUGIN(pluginname(和pro 文件中声明的一致)声明需要加载的插件并在工程配置中指明插件的 lib 位置。三:基于 qt plugin 技术的框架结构设想1. 愿景由于我们目前系统功能多,模块多,缺乏系统的整体性。我们想借助Qt Plugin 技术,把各个独
8、立的功能模块实现为一个个插件,统一在主体框架中,并能根据不同地方的用户的不同需求,在主框架中加载不同的功能模块,以实现整个系统的功能集中,体现出系统的整体性。2. plugin 接口通过技术验证得出,目前我们采用动态插件,各个功能的插件实现定义的统一接口,具体功能放在插件界面中实现,此部分就像开发独立的应用程序,只是需要注意的是: 功能部分的主界面需要继承至插件界面基类:PluginWidget,插件接口中用具体的实现类指针给插件界面基类指针赋值,在加载插件的主框架中通过插件接口中定义的基类指针统一调用,利用 C+动态技术动态识别具体指向的实现类。 插件界面类必须实现基类的虚函数:Create
9、Actions()用于创建 Action 创建 Action 需要使用基类的方法 newAction 创建,在此函数中加入了保存创建的 Action 功能。插件接口定义如下:class QPluginInterfacepublic:/ 析构函数virtual QPluginInterface() / 插件名称virtual QString PluginName() = 0;/ 插件显示在主框架中的图标文件路径virtual QString PluginIconurl() = 0;/ 插件提供的对外操作接口集virtual QList* Actions() = 0;/ 创建插件提供的操作方法vi
10、rtual void CreateActions()=0;/ 插件的主界面virtual QWidget* Widget() = 0;protected:/ 插件的主界面基类PluginWidget *pluginWidget;插件界面基类定义如下:class PluginWidget :public QMainWindowQ_OBJECTpublic:PluginWidget(QWidget*parent=0);PluginWidget();QList* Actions();virtual void CreateActions()QAction * newAction(const QIcon
11、 QAction * newAction(const QString void AppendAction(QAction*act);protected:/ action 链表QList *m_actlist;下图是一个实现案例中各类之间的关系图:3. 插件调用插件在主框架中动态加载,目前考虑主框架基本结构是继承至QMainWindow,工具栏上显示当前加载的插件的功能键,并留有返回键可以回退到上一级。主工作区是一个 QStackWidget,保存插件的界面,并把插件序号和插件对应的界面建立映射,保存在QMap中。通过序号到 QStackWidget 中切换界面。下图是把 DBManager 做
12、成插件加载到主框架的运行界面:下图是把一个简单的绘图程序做成了插件,加载到主框架的运行界面:四:总结目前只是通过实现两个动态插件在主框架中运行,基本算是功能性的验证,离具体实施还有很多工作需要进一步的研究,比如主框架的风格,插件的管理等等。由于本人的能力有限,可能有很多认识不够的地方,请指正。QTqt plugin 插件plugin 是作为 dll 的一种特殊的应用而存在的. 也就是说,在 linux 下面,他是 so 的一种应用而已. 动态链接装入器例程 dlopen 需要在文件系统中查找共享目标文件以打开文件并创建句柄. 有 4 种方式用以指定文件的位置:1.dlopen call 中的绝
13、对文件路径 2.在 LD_LIBRARY_PATH 环境变量中指定的目录中 3.在 /etc/ld.so.cache 中指定的库列表之中,可以用命令更新 ld.so.cache:ldconfig -C ld.so.cache -n /lib -v4.先在 /usr/lib 之中,然后在 /lib 之中 今天主要是看了 qt 相关的 plugin 的东西. 并且尝试了一个 plugin 的程序,对于我们来说,plugin 是一个非常非常有用的东西,以后要多用.对于 qt 而言,plugin 是做成动态链接库的形式存在的,但是 qt 的 plugin 有一些特殊的地方. 事实上,qt 的 plug
14、in 有两种,高层次和低层次的. qt 的高层(high level)plugin 是有 qt 的meta-object system 自动管理的. 也就是说你必须把你的 plugin 放在特定的子目录下面不能修改,qt 会自动寻找这些 plugin. 当然我们可以用 QLibrary 重新设定 qt lib 的 path,但是我觉得这样是非常不礼貌的做法. 当然了,高层 plugin 也会自动搜寻系统的当前执行目录下的子目录来寻找 plugin. 高层的 plugin 包括了许多有用的东西,包括了 image,textcode,style 等等. 而对于我们更加有用的低层次的 plugin,
15、这种 plugin 可以用 QPluginLoader 来加载,我们可以利用 qApp:applicationDirPath()来得到我们当前的程序运行 path,然后利用QDir:cd(“plugins“)这样的操作进入我们的 plugins 子目录 ,然后 load 之. 对于这类的plugin 我们需要遵循下列的 stepap:1.定义一个 Interface,并且用 Q_DECLARE_INTERFACE 宏告知 qt meta-object system 有这样一个 interface;2.用 QPluginLoader 加载这个 plugin,用 qobject_castappli
16、cationDirPath();pluginsDir.cd(“plugins“);foreach (QString fileName,pluginsDir.entryList(QDir:Files) QPluginLoader loader(pluginsDir.absoluteFilePath(fileName);./ 注意这段程序中用到了 applicationDirPath 来得到 app 的地址. 要用到这种功能的话/ 要#include 才行,qApp 不需要自己定义,系统会自动定义的. / 还有就是用到了 foreach 宏来列举 dir 下面的所有文件名. 显然比较可爱我们的试验
17、程序 plugin 叫做 kitty(猫咪),而 ap 叫做 hunny,显然,我是想不到合适的名字罢了.kitty:.pro:# Automatically generated by qmake (2.00a) 四 11 月 9 10:37:28 2006#TEMPLATE = libCONFIG += pluginINCLUDEPATH += # “指定特殊的 include path“TARGET += kitty # “特别注意这里“DEPENDPATH += .INCLUDEPATH += .# InputHEADERS += kitty.hSOURCES += kitty.cpp.
18、h:#ifndef KITTY_H#define KITTY_H#include #include class Kitty : public QObject,public KittyInterfaceQ_OBJECTQ_INTERFACES(KittyInterface)public:/ for KittyInterfacevoid go();#endif.cpp:#include #include “kitty.h“void Kitty : go()printf(“Kitty : go() calledn“);printf(“Kitty : goto() n“);Q_EXPORT_PLUGI
19、N2(kitty,Kitty)hunny: / 我们把 kitty 作为 plugin 放在 plugins 目录下.pro: / 其实不需要做什么特殊的修改# Automatically generated by qmake (2.00a) 四 11 月 9 10:58:12 2006#TEMPLATE = appTARGET += DEPENDPATH += .INCLUDEPATH += .# InputHEADERS += hunny.h interfaces.hSOURCES += hunny.cpp main.cppCONFIG += debug.h: / 也不需要特殊的动作#if
20、ndef HUNNY_H#define HUNNY_H#include #include #include #include #include “interfaces.h“class Hunny : public QObjectQ_OBJECTpublic: void loadPlugins();private:QDir pluginsDir;#endif.cpp: / load kitty 而已#include “hunny.h“void Hunny : loadPlugins()printf(“Hunny : loadPluginsn“);pluginsDir = QDir(qApp-ap
21、plicationDirPath();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 = %#xn“,pl
22、ugin);if (plugin) KittyInterface *iKitty = qobject_cast(plugin);printf(“loadPlugins : load KittyInterface,iKitty = %#xn“,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:
23、 / 公用的 interfaces#ifndef INTERFACES_H#define INTERFACES_Hclass KittyInterfacepublic: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. 对于小程序,尤其
24、是一个人边想边做的这种,主程序和插件同步开发时,最好只用一份接口文件,其他插件工程中直接加入该文件,不要再拷贝到其工程目录下,这样修改接口的话较容易。 记得把接口文件所在目录包含进其他工程中。3. 有自己的界面的插件,它所实现的接口中至少要有启动和退出函数。4. 退出插件时, 调用插件提供的退出函数,然后在主程序插件列表中删除该对象。这样就等到主程序退出时才能释放 dll,理论上用 QPluginLoader 的 unload 可以彻底卸载,但我发现这个函数有些问题,会导致系统崩溃(Qt4.3.0)。5. 一般的工具插件可以用 exec 函数启动,这样把当前程序控制权彻底交到插件界面。若用 s
25、how 启动插件界面,需要指定窗口为 WindowModal,即使这样,Windows 任务栏也会显示的像两个程序一样。(这是错误的,初始化插件中的窗体时没有指定正确的父窗口是导致该问题的原因。)6. 编写 qt 的 Plugin 及注意事项2010-01-28 15:341)主程序,测试加载插件#include “interfaces.h“#include #include #include int main(int argc, char *argv)QCoreApplication a(argc, argv);qDebug() (plugin);qDebug()GetHello();lay
26、er-SetHello(“my layer is very good“);qDebug() GetHello(); pluginLoader.unload(); return a.exec();为使 qDebug 信息能够正常输出,需要在主程序工程 pro 文件中加入CONFIG += qt console2)共享的接口的头文件/* 接口,包括矢量图层、栅格数据*/#ifndef INTERFACES_H#define INTERFACES_H#include #include QT_BEGIN_NAMESPACEclass QString;class QStringList;QT_END_N
27、AMESPACEclass DataInterface: public QObjectpublic:virtual DataInterface();class VectorLayer: public DataInterfacepublic:virtual VectorLayer() virtual QString GetHello()=0;virtual void SetHello(QString Msg)=0;QT_BEGIN_NAMESPACEQ_DECLARE_INTERFACE(VectorLayer,“com.microinfospace.Goldmap.VectorLayer/1.
28、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 VectorLayerQ_OBJECTQ_IN
29、TERFACES(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_NAMESPACEQ_EXPORT_PLUGIN2(test, TestHello)QT_END_NAMESPACE这里的 Q_EXPORT_PLUGIN2 需要注意test 是插件名TestHello 是这个插件的类名下图为运行结果http:/