1. 程式人生 > >QtCreator插件開發(三)——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在插件加載時就必須要考慮插件之間的依賴性。

二、插件模塊

1、插件模塊

最基本的插件是一個共享庫,從開發者的角度,插件是一個模塊。
插件模塊的實現需要滿足以下功能:
A、在一個類中實現ExtensionSystem::IPlugin接口。
B、使用Q_EXPORT_PLUGIN宏導出插件類。
C、提供一個pluginspec插件描述文件,用於描述插件的元信息。
D、向其它插件暴露一個或多個對象。
E、查找其它插件暴露出來的可用的一個或多個對象。

插件都需要繼承IPlugin的接口,插件是由描述文件和繼承IPlugin的類庫組成。
描述文件內容如下:

<plugin name="DoNothing" version="1.0.0" compatVersion="2.8.1">
    <vendor>Scorpio.org</vendor>
    <copyright>(C) 2010-2011 Scorpio.org</copyright>
    <license>Do anything you want.</license>
    <description>A plugin that does nothing.</description>
    <url>http://www.scorpio.net</url>
    <dependencyList>
        <dependency name="Core" version="2.8.1"/>
    </dependencyList>
</plugin>

插件描述文件描述了插件的基本信息,用於被插件管理器加載。最後一行描述了插件所依賴的其它插件,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.h>

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

QList<QObject*> 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 <extensionsystem/pluginmanager.h>

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

    QList<QObject*> 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();
}

技術分享圖片
一個對外暴露的對象是由一個插件對外暴露的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 <coreplugin/inavigationwidgetfactory.h>
#include <coreplugin/id.h>
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 <QtGui>

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 <extensionsystem/iplugin.h>
#include "NavWidgetFactory.h"

#include <QtPlugin>
#include <QtGui>

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 <QtPlugin>
#include <QtGui>

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插件描述文件如下:

<plugin name="TableNav" version="0.0.1" compatVersion="2.8.1">
    <vendor>Scorpio</vendor>
    <copyright>(C) 2010-2011 Scorpio.org</copyright>
    <license>MIT</license>
    <description>Table widget as navigation.</description>
    <url>http://www.scorpio.net</url>
    <dependencyList>
        <dependency name="Core" version="2.8.1"/>
    </dependencyList>
</plugin>

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

結果如下:
技術分享圖片

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<QObject*>形式的對象池。
B、通過連接PluginManager::objectAdded()信號,可以知道被暴露的對象。
假設需要查找一個實現了INavigationWidgetFactory接口的對象,然後把它添加到一個QListWidget中顯示出來。那麽,可以使用PluginManager::getObjects<T>()函數。下面是代碼片段:

ExtensionSystem::PluginManager* pm =                            ExtensionSystem::PluginManager::instance();
QList<Core::INavigationWidgetFactory*> objects
  = pm->getObjects<Core::INavigationWidgetFactory>();
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界面如下:
技術分享圖片

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 <extensionsystem/iplugin.h>

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 <utils/savefile.h>

#include <QtPlugin>
#include <QDebug>
#include <QDateTime>

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<PluginSpec *> queue = loadQueue();
    //加載插件
    foreach (PluginSpec *spec, queue) {
        loadPlugin(spec, PluginSpec::Loaded);
    }
    //初始化插件
    foreach (PluginSpec *spec, queue) {
        loadPlugin(spec, PluginSpec::Initialized);
    }
    QListIterator<PluginSpec *> 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<Interface1*>(&bundle);
Interface2* iface2Ptr = qobject_cast<Interface2*>(&bundle);

2、QtCreator實現方式

QtCreator的Aggregation庫提供了一種更加簡潔的方式,來定義接口,然後將其打包成一個對象。創建Aggregation::Aggregate實例,然後將對象添加進該對象。加入聚合的每一個對象都可以實現一個接口。下面的代碼顯示了如何創建聚合。

#include <aggregation/aggregate.h>

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<Interface1>(&bundle);
Interface2* iface2Ptr = Aggregation::query<Interface2>(&bundle);
利用聚合,可以多次添加具有相同接口的多個對象。例如:
Aggregation::Aggregate bundle;
bundle.add(new Interface1);
bundle.add(new Interface2);
bundle.add(new Interface1);
bundle.add(new Interface1);
QList<Interface1*>gt; iface1Ptrs =      Aggregation::query_all<Interface1>(&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架構