Поддержка собственных расширений в приложениях


Связь с расширением производится с помощью интерфейса, поэтому приложение должно предоставлять по меньшей мере один интерфейс для использования расширения. Расширения загружаются приложением при помощи класса QPluginLoader, который содержит несколько методов. Самый часто используемый из них — это метод instance(), создающий и возвращающий указатель на объект расширения. Этот класс автоматически производит загрузку расширений, при указании имени файла расширения в его конструкторе. Выгрузку расширения, если в этом есть необходимость, можно осуществить с помощью метода unload(). Программа, показанная на рисунке, демонстрирует приложение, предоставляющее поддержку для использования расширений. Для этого ею предоставляется интерфейс для операций над текстом.


#ifndef _interfaces_h_
#define _interfaces_h_

class QString;
class QStringList;

class StringInterface {
public:
    virtual ~StringInterface() {}
    virtual QStringList operations() const = 0;
    virtual QString operation(const QString& strText, 
                              const QString& strOperation
                             ) = 0;
};

Q_DECLARE_INTERFACE(StringInterface,
                    "com.mysoft.Application.StringInterface/1.0"
                   )
#endif //_interfaces_h_

Интерфейс — это класс, который содержит только чисто виртуальные определения методов. В нашем случае, приложение предоставляет только один интерфейс — StringInterafce, из названия которого ясно, что он предназначен для операций над строками. Этот интерфейс объявляет два прототипа методов: operations() — для получения списка операций расширения, и operation() — служащий для вызова операций над строками. Виртуальный деструктор нам нужен для того, чтобы C++ не выдавал предупреждающие сообщения о том, что класс, имеющий виртуальные методы, не имеет виртуального деструктора.

Идентификация интерфейса должна быть задана при помощи макроса Q_DECLARE_INTERFACE(), в котором необходимо указать строку-идентификатор, для которой МОС должен сгенерировать метаинформацию. С ее помощью объект класса QPlugLoader проверяет версию расширения и другую информацию, заданную в этой строке. Строка идентификатора состоит из четырех компонентов, разделенных между собой точками:

  • домен создателя интерфейса;
  • имя приложения;
  • имя интерфейса;
  • номер версии.

#ifndef _PluginsWindow_h_
#define _PluginsWindow_h_

#include <QMainWindow>
#include "interfaces.h"

class QLabel;
class QMenu;

class PluginsWindow : public QMainWindow {
Q_OBJECT

private:
    QLabel* m_plbl;
    QMenu*  m_pmnuPlugins;

public:
    PluginsWindow(QWidget* pwgt = 0);

    void loadPlugins(             );
    void addToMenu  (QObject* pobj);

protected slots:
    void slotStringOperation();
};
#endif //_PluginsWindow_h_

Класс основного окна приложения PluginsWindow унаследован от класса QMainWindow. Это сэкономит нам время при работе с меню и лейаутами.


PluginsWindow::PluginsWindow(QWidget* pwgt/*=0*/) : QMainWindow(pwgt) 
{
    m_plbl        = new QLabel("this is the test text");
    m_pmnuPlugins = new QMenu("&PluginOperations");

    loadPlugins();
    setCentralWidget(m_plbl);
    menuBar()->addMenu(m_pmnuPlugins);
}

В конструкторе класса создаются виджеты надписи и меню. Виджет надписи (указатель m_plbl) вносится в рабочую область приложения, а меню (указатель m_pmnuPiugins) добавляется вызовом метода addMenu() к основной строке меню. Вызов метода loadPlugins() производит поиск и загрузку расширений.


void PluginsWindow::loadPlugins()
{
    QDir dir(QApplication::applicationDirPath());
    if (!dir.cd("plugins")) {
        QMessageBox::critical(0, "", "plugins directory does not exist");
        return;
    }

    foreach (QString strFileName, dir.entryList(QDir::Files)) {
        QPluginLoader loader(dir.absoluteFilePath(strFileName));
        addToMenu(qobject_cast<QObject*>(loader.instance()));
    }
}

Мы хотим использовать в приложении все возможные расширения. При загрузке расширений мы исходим из того, что они находятся в каталоге \plugins, в котором мы ищем все файлы расширений — для этого мы используем класс QDir. Найденные файлы передаются в конструктор класса QPluginLoader. Затем, возвращенный из объекта QPluginLoader вызовом метода instance() указатель преобразуется к типу указателя на QObject и передается в метод addToMenu(), как возможный кандидат для добавления операций расширения к пунктам меню.


void PluginsWindow::addToMenu(QObject* pobj)
{
    if (!pobj) {
        return;
    }
    
    StringInterface* pI = qobject_cast<StringInterface*>(pobj);
    if (pI) {
        QStringList lstOperations = pI->operations();
        foreach (QString str, lstOperations) {
            QAction* pact = new QAction(str, pobj);
            connect(pact, SIGNAL(triggered()), 
                    this, SLOT(slotStringOperation())
                   );
            m_pmnuPlugins->addAction(pact);
        }
    }
}

В методе addToMenu() первым делом проверяется допустимость указателя, то есть его неравенство нулю. В том случае, если указатель окажется равен нулю, будет произведен выход из метода. При помощи qobject_cast<StringInterface*> проверяется доступность поддерживаемого нашим приложением интерфейса в расширении. Представьте себе, что поддерживаемых интерфейсов может быть несколько, а при помощи qobject_cast<T> мы можем отличить один от другого. Если проверка на поддержку интерфейса прошла удачно, то мы, вызовом метода operations(), опрашиваем список всех предоставляемых расширением операций и сохраняем их в переменной lstOperations. Затем, для каждой операции создается объект действия, в который передаются название операции и указатель на объект, являющийся расширением. Созданный объект действия соединяется со слотом slotStringOperation() и добавляется в меню.


void PluginsWindow::slotStringOperation()
{
    QAction* pact = qobject_cast<QAction*>(sender());

    StringInterface* pI = qobject_cast<StringInterface*>(pact->parent());

    m_plbl->setText(pI->operation(m_plbl->text(), pact->text()));
}

Метод sender() возвращает указатель на объект выславший сигнал. Этот указатель приводится к типу указателя на QAction. Вызовом метода parent() из объекта действия мы получаем указатель на объект расширения. Воспользовавшись методом operation(), мы производим действия над текстом виджета надписи. В этот метод мы передаем в текст виджета надписи и название применяемой операции, которое соответствует названию объекта действия.

Читать далее: Создание расширения для приложения