QtCreator插件开发(三)——QtCreator架构

QtCreator插件开发(三)——QtCreator架构

一、QtCreator架构简介

QtCreator的核心就是一个插件加载器,其所有功能都是通过插件实现的。
QtCreator架构如下:
QtCreator插件开发(三)——QtCreator架构
QtCreator的核心功能由Core Plugin (Core::ICore)实现。
插件管理器(ExtensionSystem::PluginManager)对插件协作提供了简单方式,允许插件为其他插件扩展提供钩子。
PluginManager负责插件的加载,管理,销毁等工作。Core插件是QtCreator最基础的插件,提供了向界面增加菜单等功能。
QtCreator的核心系统是由PluginManager和Core插件构成。PluginManager负责插件的管理工作,Core负责提供QtCreator的最小功能集合。PluginManager将Core当做普通插件进行加载。对于自定义插件,Core是一个基础功能库,使用Core可以扩展QtCreator的功能。
QtCreator的所有功能,全是由插件实现的,使用插件机制的优点是简化了顶层业务,即插件管理工作的逻辑,缺点是增加了加载插件的复杂度,由于Core插件需要被其他插件依赖,所以qtcreator在插件加载时就必须要考虑插件之间的依赖性。

创新互联是工信部颁发资质IDC服务器商,为用户提供优质的双线服务器托管服务

二、插件模块

1、插件模块

最基本的插件是一个共享库,从开发者的角度,插件是一个模块。
插件模块的实现需要满足以下功能:
A、在一个类中实现ExtensionSystem::IPlugin接口。
B、使用Q_EXPORT_PLUGIN宏导出插件类。
C、提供一个pluginspec插件描述文件,用于描述插件的元信息。
D、向其它插件暴露一个或多个对象。
E、查找其它插件暴露出来的可用的一个或多个对象。
插件都需要继承IPlugin的接口,插件是由描述文件和继承IPlugin的类库组成。
描述文件内容如下:


    Scorpio.org
    (C) 2010-2011 Scorpio.org
    Do anything you want.
    A plugin that does nothing.
    http://www.scorpio.net
    
        
    

插件描述文件描述了插件的基本信息,用于被插件管理器加载。最后一行描述了插件所依赖的其它插件,PluginManager会根据插件之间的依赖关系决定加载顺序。
IPlugin是插件的基类接口,主要接口如下:

//初始化函数,在插件被加载时会调用
bool IPlugin::initialize(const QStringList &arguments, QString *errorString)
//在所有插件的initialize函数被调用后,调用该函数,此时该插件依赖的插件已经初始化完成
void IPlugin::extensionsInitialized()
//在所有插件的extensionsInitialized函数调用完成以后进行调用   
bool IPlugin::delayedInitialize()

2、暴露对象

暴露对象是存在于插件管理器对象池中的对象。
插件暴露出的对象会加入到PluginManager的对象池。PluginManager的allObjects()函数用于获取对象池中所有QObject对象的指针列表。下面的代码演示了如何在QListWidget组件中列出对象池中所有的对象:

#include 

ExtensionSystem::PluginManager* pm
         = ExtensionSystem::PluginManager::instance();

QList objects = pm->allObjects();
QListWidget* listWidget = new QListWidget;

Q_FOREACH(QObject* obj, objects)
{
    QString objInfo = QString("%1 (%2)")
                      .arg(obj->objectName())
                      .arg(obj->metaObject()->className());
    listWidget->addItem(objInfo);
}

将DoNothing插件中doNothing函数修改如下:

#include 

void DoNothingPlugin::doNothing()
{
    ExtensionSystem::PluginManager* pm
            = ExtensionSystem::PluginManager::instance();

    QList objects = pm->allObjects();
    QListWidget* listWidget = new QListWidget();
    Q_FOREACH(QObject* obj, objects)
    {
        QString objInfo = QString(QString::fromUtf8("%1 (%2)"))
                .arg(obj->objectName())
                .arg(QString::fromUtf8(obj->metaObject()->className()));
        listWidget->addItem(objInfo);
    }
    listWidget->resize(300,600);
    listWidget->show();
}

QtCreator插件开发(三)——QtCreator架构
一个对外暴露的对象是由一个插件对外暴露的QObject(或其子类)的实例,暴露对象存在于对象池中,并且可供其它插件使用。

3、如何从插件中暴露对象

有三种方法从插件中暴露一个对象:
A、IPlugin::addAutoReleasedObject(QObject)
B、IPlugin::addObject(QObject
)
C、PluginManager::addObject(QObject)
IPlugin::addObject()和IPlugin::addAutoReleasedObject()其实都是调用的PluginManager::addObject()函数。建议使用IPlugin的函数添加对象。addAutoReleasedObject()和addObject()的唯一区别是,前者添加的对象会在插件销毁的时候自动按照注册顺序的反向顺序从对象池中移除并delete。
在任意时刻,都可以使用IPlugin::removeObject(QObject
)函数将对象从对象池中移除。

4、需要暴露的对象

插件可以暴露任何对象。通常,被其它插件使用了某些功能的对象会被暴露。QtCreator中,功能通过接口的方式定义。
下面是其中一些接口:
Core::INavigationWidgetFactory
Core::IEditor
Core::IOptionsPage
Core::IOutputPane
Core::IWizard
如果一个插件包含实现了接口的对象,那么这个对象就应该被暴露出来。例如,一个插件中的某个类实现了INavigationWidgetFactory接口,并且暴露出来,那么Core就会自动把这个类提供的组件当做导航组件显示出来。创建一个导航栏插件TableNav,通过实现 Core::INavigationWidgetFactory接口,将一个简单的QTableWidget当做导航组件。
Core::INavigationWidgetFactory接口实现如下:
NavWidgetFactory.h文件:

#ifndef NAVWIDGETFACTORY_H
#define NAVWIDGETFACTORY_H

#include 
#include 
using namespace Core;

class NavWidgetFactory : public Core::INavigationWidgetFactory
{
public:
    NavWidgetFactory();
    ~NavWidgetFactory();
    Core::NavigationView createWidget();
    QString displayName() const;
    int priority() const;
    Id id() const;
};

#endif // NAVWIDGETFACTORY_H

NavWidgetFactory.cpp文件:

#include "NavWidgetFactory.h"

#include 

NavWidgetFactory::NavWidgetFactory() { }

NavWidgetFactory::~NavWidgetFactory() { }

Core::NavigationView NavWidgetFactory::createWidget()
{
    Core::NavigationView view;
    view.widget = new QTableWidget(50, 3);
    return view;
}

QString NavWidgetFactory::displayName() const
{
    return QString::fromUtf8("TableNav");
}

int NavWidgetFactory::priority() const
{
    return 0;
}

Id NavWidgetFactory::id() const
{
    return Id::fromName("TableNav");
}

TableNav插件实现如下:
TableNavPlugin .h文件:

#ifndef TABLENAVPLUGIN_H
#define TABLENAVPLUGIN_H

#include 
#include "NavWidgetFactory.h"

#include 
#include 

class TableNavPlugin : public ExtensionSystem::IPlugin
{
public:
    TableNavPlugin();
    ~TableNavPlugin();
    void extensionsInitialized();
    bool initialize(const QStringList & arguments, QString * errorString);
    void shutdown();
};

#endif // TABLENAVPLUGIN_H

TableNavPlugin .cpp文件:

#include "TableNavPlugin.h"
#include "NavWidgetFactory.h"

#include 
#include 

TableNavPlugin::TableNavPlugin()
{
    // Do nothing
}

TableNavPlugin::~TableNavPlugin()
{
    // Do notning
}

bool TableNavPlugin::initialize(const QStringList& args, QString *errMsg)
{
    Q_UNUSED(args);
    Q_UNUSED(errMsg);
    // Provide a navigation widget factory.
    // Qt Creator’s navigation widget will automatically
    // hook to our INavigationWidgetFactory implementation, which
    // is the NavWidgetFactory class, and show the QTableWidget
    // created by it in the navigation panel.
    //暴露对象
    addAutoReleasedObject(new NavWidgetFactory);
    return true;
}

void TableNavPlugin::extensionsInitialized()
{
    // Do nothing
}

void TableNavPlugin::shutdown()
{
    // Do nothing
}

Q_EXPORT_PLUGIN(TableNavPlugin)

TableNav插件描述文件如下:


    Scorpio
    (C) 2010-2011 Scorpio.org
    MIT
    Table widget as navigation.
    http://www.scorpio.net
    
        
    

TableNav插件依赖文件如下:

QTC_PLUGIN_NAME = TableNav

QTC_PLUGIN_DEPENDS += \
    coreplugin

TableNav插件工程文件如下:

EMPLATE = lib
TARGET = TableNav
include(../../qtcreatorplugin.pri)
PROVIDER = Scorpio
include(../../plugins/coreplugin/coreplugin.pri)

HEADERS += TableNavPlugin.h \
    NavWidgetFactory.h

SOURCES += TableNavPlugin.cpp \
    NavWidgetFactory.cpp

OTHER_FILES += TableNav.pluginspec \
    TableNav_dependencies.pri

结果如下:
QtCreator插件开发(三)——QtCreator架构

5、监控暴露对象

当使用PluginManager::addObject()添加对象时,PluginManager就会发出objectAdded(QObject)信号。应用程序可以使用objectAdded(QObject)信号来弄清楚被添加的对象。
只有插件被初始化后,插件管理器才会发出objectAdded(QObject*)信号。只有被初始化后添加到插件管理器对象池的插件对象,才能收到objectAdded()信号。
通常,连接到objectAdded()信号的slot会寻找一个或多个已知接口。假设插件要找的是INavigationWidgetFactory接口,那么连接objectAdded()信号的槽函数如下:

void xxxPlugin::slotObjectAdded(QObject * obj)
{
    INavigationWidgetFactory *factory = Aggregation::query(obj);
    if(factory)
    {
        // use it here...
    }
}

6、查找对象

有时,插件需要在应用程序中查找提供了某些功能的对象。目前,已知查找对象的方法有两种:
A、PluginManager::allObjects()函数返回一个QList形式的对象池。
B、通过连接PluginManager::objectAdded()信号,可以知道被暴露的对象。
假设需要查找一个实现了INavigationWidgetFactory接口的对象,然后把它添加到一个QListWidget中显示出来。那么,可以使用PluginManager::getObjects()函数。下面是代码片段:

ExtensionSystem::PluginManager* pm =                            ExtensionSystem::PluginManager::instance();
QList objects
  = pm->getObjects();
QListWidget* listWidget = new QListWidget();
Q_FOREACH(Core::INavigationWidgetFactory* obj, objects)
{
    QString objInfo = QString("%1 (%2)")
                        .arg(obj->displayName())
                        .arg(obj->metaObject()->className());
    listWidget->addItem(objInfo);
}

三、Core插件

1、Core插件简介

QtCreator的核心系统由PluginManager和Core插件构成。PluginManager负责插件的管理工作,将Core插件当做普通插件进行加载;Core插件负责提供QtCreator的最小功能集合,为其它插件提供基础功能。
QtCreator所有功能由插件实现,优点是简化了顶层业务,即插件管理工作的逻辑,只有PlunginManager和Plugin;缺点是增加了加载插件的复杂度,因为Core基础库插件需要被其他插件依赖,所以QtCreator在插件加载时就必须要考虑插件之间的依赖性。
只包括core、Find、Locator、TextEditor四个必须插件的QtCreator界面如下:
QtCreator插件开发(三)——QtCreator架构

2、Core插件的功能接口集合

C++ 开发者通常会将只包含 public纯虚函数的类当做接口。在QtCreator中,接口则是拥有一个或多个纯虚函数的QObject子类。如果一个插件实现了IXXX接口的对象,那么这个对象就应该被暴露出来。例如,一个插件中的某个类实现了INavigationWidgetFactory接口,并且暴露出来,那么 Core 就会自动把这个类提供的组件当做导航组件显示出来。
QtCreator中,功能通过接口的方式定义。Core插件模块定义了QtCreator的常用功能接口集合,如下:
Core::IOptionsPage
Core::IWizard
Core::IEditor
Core::IEditorFactory
Core::IDocumentFactory
Core::IExternalEditor
Core::IContext
Core::ICore
Core::ICoreListener
Core::IDocument
Core::IFileWizardExtension
Core::IMode
Core::INavigationWidgetFactory
Core::IOutputPane
Core::IVersionControl
功能接口会在其它插件或Core插件实现,如git插件在GitVersionControl类对Core::IVersionControl接口进行了实现,Core插件在TextDocument类中对IDocument接口进行了实现。

3、Core插件的源码

coreplugin.h文件:

#ifndef COREPLUGIN_H
#define COREPLUGIN_H

#include 

namespace Core {
class DesignMode;
namespace Internal {

class EditMode;
class MainWindow;

class CorePlugin : public ExtensionSystem::IPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Core.json")

public:
    CorePlugin();
    ~CorePlugin();

    //必须实现接口initialize
    bool initialize(const QStringList &arguments, QString *errorMessage = 0);
    //必须实现接口extensionsInitialized
    void extensionsInitialized();
    bool delayedInitialize();
    ShutdownFlag aboutToShutdown();
    QObject *remoteCommand(const QStringList & /* options */, const QStringList &args);

public slots:
    void fileOpenRequest(const QString&);

private:
    void parseArguments(const QStringList & arguments);

    MainWindow *m_mainWindow;//主窗口
    EditMode *m_editMode;//编辑模式
    DesignMode *m_designMode;//设计器模式
};

} // namespace Internal
} // namespace Core

#endif // COREPLUGIN_H

coreplugin.cpp文件:

#include "coreplugin.h"
#include "actionmanager.h"
#include "designmode.h"
#include "editmode.h"
#include "editormanager.h"
#include "fileiconprovider.h"
#include "helpmanager.h"
#include "mainwindow.h"
#include "mimedatabase.h"
#include "modemanager.h"
#include "infobar.h"

#include 

#include 
#include 
#include 

using namespace Core;
using namespace Core::Internal;

CorePlugin::CorePlugin() :
    m_mainWindow(new MainWindow), m_editMode(0), m_designMode(0)
{
}

CorePlugin::~CorePlugin()
{
    if (m_editMode) {
        removeObject(m_editMode);
        delete m_editMode;
    }

    if (m_designMode) {
        if (m_designMode->designModeIsRequired())
            removeObject(m_designMode);
        delete m_designMode;
    }

    // delete FileIconProvider singleton
    delete FileIconProvider::instance();

    delete m_mainWindow;
}

void CorePlugin::parseArguments(const QStringList &arguments)
{
    for (int i = 0; i < arguments.size(); ++i) {
        if (arguments.at(i) == QLatin1String("-color")) {
            const QString colorcode(arguments.at(i + 1));
            m_mainWindow->setOverrideColor(QColor(colorcode));
            i++; // skip the argument
        }
        if (arguments.at(i) == QLatin1String("-presentationMode"))
            ActionManager::setPresentationModeEnabled(true);
    }
}

bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
{
    qsrand(QDateTime::currentDateTime().toTime_t());
    parseArguments(arguments);
    const bool success = m_mainWindow->init(errorMessage);
    if (success) {
        m_editMode = new EditMode;
        addObject(m_editMode);
        //切换到编辑模式
        ModeManager::activateMode(m_editMode->id());
        m_designMode = new DesignMode;
        InfoBar::initializeGloballySuppressed();
    }

    // Make sure we respect the process's umask when creating new files
    Utils::SaveFile::initializeUmask();

    return success;
}

void CorePlugin::extensionsInitialized()
{
    m_mainWindow->mimeDatabase()->syncUserModifiedMimeTypes();
    if (m_designMode->designModeIsRequired())
        addObject(m_designMode);
    m_mainWindow->extensionsInitialized();
}

bool CorePlugin::delayedInitialize()
{
    HelpManager::instance()->setupHelpManager();
    return true;
}

QObject *CorePlugin::remoteCommand(const QStringList & /* options */, const QStringList &args)
{
    IDocument *res = m_mainWindow->openFiles(
                args, ICore::OpenFilesFlags(ICore::SwitchMode | ICore::CanContainLineNumbers));
    m_mainWindow->raiseWindow();
    return res;
}

void CorePlugin::fileOpenRequest(const QString &f)
{
    remoteCommand(QStringList(), QStringList(f));
}

ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown()
{
    m_mainWindow->aboutToShutdown();
    return SynchronousShutdown;
}

Q_EXPORT_PLUGIN(CorePlugin)
Core插件对Core::IMode进行了不同实现,如EditMode、DesignMode,并在initialize函数加载了相应功能。

四、插件与核心系统的通信

1、核心系统如何加载插件

在main函数中由ExtensionSystem::PluginManager插件管理器加载。
pluginManager.loadPlugins();
void PluginManager::loadPlugins()函数调用了void PluginManagerPrivate::loadPlugins()函数。

void PluginManagerPrivate::loadPlugins()
{
    //获取待加载的插件,loadQueue根据插件批次依赖关系进行排序
    QList queue = loadQueue();
    //加载插件
    foreach (PluginSpec *spec, queue) {
        loadPlugin(spec, PluginSpec::Loaded);
    }
    //初始化插件
    foreach (PluginSpec *spec, queue) {
        loadPlugin(spec, PluginSpec::Initialized);
    }
    QListIterator it(queue);
    it.toBack();
    while (it.hasPrevious()) {
        loadPlugin(it.previous(), PluginSpec::Running);
    }
    emit q->pluginsChanged();
}

2、插件如何使用核心系统为软件扩展功能

自定义插件使用Core插件提供的功能向界面添加菜单代码如下:

bool DoNothingPlugin::initialize(const QStringList& args, QString *errMsg)
{
    Q_UNUSED(args);
    Q_UNUSED(errMsg);

    Core::ActionManager* am = Core::ICore::instance()->actionManager();

    // Create a DoNothing menu
    Core::ActionContainer* ac = am->createMenu("DoNothingPlugin.DoNothingMenu");
    ac->menu()->setTitle(QString::fromUtf8("DoNothing"));
    // Create a command for "About DoNothing".
    Core::Command* cmd = am->registerAction(
                new QAction(this),
                "DoNothingPlugin.AboutDoNothing",
                Core::Context(Core::Constants::C_GLOBAL));
    cmd->action()->setText(QString::fromUtf8("About DoNothing"));
    connect(cmd->action(), SIGNAL(triggered(bool)), this, SLOT(doNothing()));
    // Insert the "DoNothing" menu between "Window" and "Help".
    QMenu* helpMenu = am->actionContainer(Core::Constants::M_HELP)->menu();
    QMenuBar* menuBar = am->actionContainer(Core::Constants::MENU_BAR)->menuBar();
    menuBar->insertMenu(helpMenu->menuAction(), ac->menu());
    // Add the "About DoNothing" action to the DoNothing menu
    ac->addAction(cmd);

    return true;
}

DoNothing插件在initialize函数中使用Core插件的Core::ActionManager、Core::ActionContainer、Core::Command功能向主界面菜单栏添加菜单。

Git插件使用Core插件提供的功能向主界面菜单栏的git菜单提供菜单和菜单项,代码如下:

//register actions
    Core::ActionContainer *toolsContainer =
        Core::ActionManager::actionContainer(Core::Constants::M_TOOLS);

    Core::ActionContainer *gitContainer = Core::ActionManager::createMenu("Git");
    gitContainer->menu()->setTitle(tr("&Git"));
    toolsContainer->addMenu(gitContainer);
    m_menuAction = gitContainer->menu()->menuAction();

    /*  "Current File" menu */
    Core::ActionContainer *currentFileMenu = Core::ActionManager::createMenu(Core::Id("Git.CurrentFileMenu"));
    currentFileMenu->menu()->setTitle(tr("Current &File"));
    gitContainer->addMenu(currentFileMenu);

五、聚合实现

聚合由Aggregation命名空间提供,提供了一种将不同类型的QObject粘合在一起的能力,因此可以将不同类型对象相互转换。使用Aggregation命名空间中的类和函数,就可以绑定相关对象到一个单独实体(聚合)。被绑定到聚合中的对象能够从聚合转换为不同的对象类类型。

1、聚合的传统实现

如果想要一个对象提供两个接口的实现,实现代码如下:

class Interface1
{
    ....
};
Q_DECLARE_INTERFACE("Interface1", "Interface1");

class Interface2
{
    ....
};
Q_DECLARE_INTERFACE("Interface2", "Interface2");

class Bundle : public QObject,
               public Interface1,
               public Interface2
{
    Q_OBJECT
    Q_INTERFACES(Interface1 Interface2)
    ....
};

Bundle bundle;

对象bundle同时实现了Interface1和Interface2。可以使用类型转换运算符,将bundle转换成Interface1或者Interface2:

Interface1* iface1Ptr = qobject_cast(&bundle);
Interface2* iface2Ptr = qobject_cast(&bundle);

2、QtCreator实现方式

QtCreator的Aggregation库提供了一种更加简洁的方式,来定义接口,然后将其打包成一个对象。创建Aggregation::Aggregate实例,然后将对象添加进该对象。加入聚合的每一个对象都可以实现一个接口。下面的代码显示了如何创建聚合。

#include 

class Interface1 : public QObject
{
    Q_OBJECT
public:
    Interface1() { }
    ~Interface1() { }
};

class Interface2 : public QObject
{
    Q_OBJECT
public:
    Interface2() { }
    ~Interface2() { }
};

Aggregation::Aggregate bundle;
bundle.add(new Interface1);
bundle.add(new Interface2);

聚合实例bundle现在有两个接口的实现。如果需要转换成相应接口,可以使用如下代码:

Interface1* iface1Ptr = Aggregation::query(&bundle);
Interface2* iface2Ptr = Aggregation::query(&bundle);
利用聚合,可以多次添加具有相同接口的多个对象。例如:
Aggregation::Aggregate bundle;
bundle.add(new Interface1);
bundle.add(new Interface2);
bundle.add(new Interface1);
bundle.add(new Interface1);
QListgt; iface1Ptrs =      Aggregation::query_all(&bundle);

使用Aggregation的另一优点是,delete聚合中的任一对象,都可以将整个聚合delete掉。例如:

Aggregation::Aggregate* bundle = new Aggregation::Aggregate;
bundle->add(new Interface1);
bundle->add(new Interface2);

Interface1* iface1Ptr = Aggregation::query(bundle);
delete iface1Ptr;
// 同时会 delete 这个 bundle 及其中所有对象
// 等价于 delete bundle

网站名称:QtCreator插件开发(三)——QtCreator架构
文章来源:http://myzitong.com/article/gpccjg.html