Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Programming / C++Programming / Qt / Qt-doc.ru / Динамические библиотеки и плагины

.pdf
Скачиваний:
62
Добавлен:
12.02.2016
Размер:
314.8 Кб
Скачать

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

программа занимает меньше места в оперативной памяти и на диске. Когда динамическая библиотека подключается к программе, то в исполняемом файле присутствует лишь ссылка на нее;

разные программы могут использовать одну и ту же библиотеку;

после обновления динамической библиотеки не обязательно перекомпилировать использующие ее программы, что позволяет обновлять динамические библиотеки, не затрагивая связанные с ними программы. То есть если в библиотеке будет обнаружена ошибка, то достаточно будет просто заменить файл.

Следующий пример демонстрирует создание динамической библиотеки, содержащей только одну функцию.

TEMPLATE = lib

DESTDIR = ..

QT

-= gui

 

 

SOURCES = dynlib.cpp

HEADERS = dynlib.h

TARGET = dynlib

Обратите внимание на файл проекта. Для создания динамической библиотеки нужно установить в секции TEMPLATE значение lib. Расположение готовой библиотеки будет на один уровень выше каталога с ее исходными файлами, для этого DESTDIR мы зададим равным "..".

Ввиду того, что создаваемая библиотека не нуждается в элементах пользовательского интерфейса, мы сделаем так, чтобы они были изъяты из компоновки, для чего мы с помощью оператора -= исключаем опцию gui в секции QT.

#include <QString>

extern "C" {

QString oddUpper(const QString& str);

}

В динамическую библиотеку должны быть экспортированы прототипы функций для их дальнейшего использования. Эти функции необходимо заключить в спецификаторextern "C" {...}. В этом случае компилятор C++ не будет прикреплять информацию о типе к символьной сигнатуре функции. Без этого спецификатора компилятор может подставить вместо имени

функции oddUpper() совсем другое имя, в котором будет закодирована дополнительная информация. Это нужно, если вы хотите, чтобы вашу динамическую библиотеку можно было загружать в процессе работы основной программы, например, при помощи класса QLibrary, о котором речь пойдет далее.

#include "dynlib.h"

QString oddUpper(const QString& str)

{

QString strTemp;

for (int i = 0; i < str.length(); ++i) {

strTemp += (i % 2) ? str.at(i) : str.at(i).toUpper();

}

return strTemp;

}

Функции преобразует каждый нечетный символ переданной строки в заглавный и возвращает полученный результат.

Существуют два способа использования динамических библиотек. В первом способе связывание с динамической библиотекой производится в процессе компоновки самой программы. В этом случае динамическая библиотека загружается автоматически, при запуске использующей ее программы. Для этого способа потребуется подключить динамическую библиотеку в pro-файле в секции LIB (ее нужно указать с префиксом -l), а также и расположение ее заголовочных файлов в секции INCLUDEPATH. Например:

LIBS += -lmy_dyn_lib

INCLUDEPATH += ../../my_dyn_lib/include

Вам может понадобиться загрузка некоторого кода без явной компоновки во время работы самой программы. Для этого существует второй способ, который заключается в использовании классаQLibrary. Этот класс заботится и о том, чтобы загруженная библиотека оставалась в памяти на протяжении всего времени использования. В коде, приведенном в листинге, реализована загрузка динамической библиотеки и исполнение функции, экспортируемой этой библиотекой. Эта функция изменяет каждую нечетную букву на заглавную.

{рисунок}

 

 

 

 

#include <QtGui>

 

 

 

 

int main(int argc, char** argv)

 

{

 

 

QApplication app(argc, argv);

 

 

 

 

QLabel

lbl("this is the example text");

 

QLibrary

lib("dynlib");

 

 

 

 

typedef QString (*Fct) (const QString&);

 

Fct fct = (Fct)(lib.resolve("oddUpper"));

 

if (fct) {

 

 

lbl.setText(fct(lbl.text()));

 

 

 

 

}

 

 

lbl.show();

 

 

 

 

 

return app.exec();

 

}

 

 

 

 

Создается виджет надписи lbl, которому присваивается текст в конструкторе. Чтобы использовать динамическую библиотеку в программе, нужно создать объект классаQLibrary и передать в его конструктор имя файла динамической библиотеки, но заметьте — только имя, без расширения. Это связано с тем, что на разных платформах динамические библиотеки имеют различные расширения. В ОС Windows наша библиотека будет иметь расширение dll, в UNIX — so, а на MacOS X — dylib. Передавая только имя, мы возлагаем

на Qt всю ответственность на подстановку нужного расширения.

Получение указателей на экспортируемые функции производится с помощью метода resolve(. В этот метод передается символьная сигнатура, по которой будет производиться поиск нужной функции. Этот метод возвращает указатель на тип void, который представляет собой адрес найденной функции. Для вызова функции этот указатель необходимо привести к нужному типу. Возвращаемый методом resolve()нулевой указатель означает, что функция не найдена. В случае успешной проверки указателя мы вызываем саму функцию. В завершение мы вызываем метод show() для отображения виджета надписи

на экране.

Использование расширений это неотъемлемая часть любого профессионального приложения. По сути, расширение — это совместно используемая динамическая библиотека, предназначенная для загрузки в процессе исполнения основного приложения, которая обязательно должна реализовывать хотя бы один специальный интерфейс. Расширения делятся на две группы:

расширения для Qt;

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

Расширение для Qt

Qt предоставляет различные типы расширений, предназначенные для дополнения возможностей библиотеки. Например, поддержка новых форматов растровых файлов, новых драйверов баз данных. Их более 20 и вот некоторые из них:

QSqlDriverPlugin — для драйверов баз данных;

QPictureFormatPlugin, QImageIOPlugin — для поддержки различных форматов растровых изображений;

QTextCodecPlugin — для реализации кодировок текста;

QStylePlugin — для стилей элементов управления.

Все эти классы наследуют класс QObject, который является базовым для всей системы расширений Qt. Свои расширения для Qt нужно строить на базе этих классов, реализовывая в них нужные методы. Возьмем для примера классQStylePlugin. Реализация расширения с применением этого

класса, CustomStyle, могла бы выглядеть так, как это показано в листингах.

class CustomStylePlugin : public QStylePlugin

{

public:

QStringList keys ( ) const;

QStyle* create(const QString &key);

};

В классе, унаследованном от QStylePlugin, необходимо реализовать виртуальные методы keys() и create().

QStringList CustomStylePlugin::keys() const

{

return QStringList() << "CustomStyle";

}

Метод keys() возвращает список строк, реализованных в классе расширений, в нашем примере оно одно — "CustomStyle"

QStyle* CustomStylePlugin::create(const QString &key)

{

if (key == "CustomStyle") return new CustomStyle;

return 0;

}

Метод create() создает запрашиваемый объект и возвращает указатель на него. В том случае, если запрашиваемый объект не найден, метод вернет нулевое значение. Экспортирование класса расширений производится в конце срр-файла при помощи макроса Q_EXPORT_PLUGIN2 следующим образом:

Q_EXPORT_PLUGIN2(CustomStylePlugin)

void main(int argc, char** argv)

{

QApplication::setStyle(QStyleFactory::create("CustomStyle"));

}

Созданный класс расширений должен быть скомпилирован в динамическую библиотеку. Его загрузку можно произвести при помощи класса QStyleFactory.

Для того чтобы Qt мог воспользоваться нашим расширением, его необходимо разместить в директории расширений соответствующего типа, расположенном в каталоге библиотеки. В нашем случае это будет директория <Директория

Qt>/plugins/styles.

Связь с расширением производится с помощью интерфейса, поэтому приложение должно предоставлять по меньшей мере один интерфейс для использования расширения. Расширения загружаются приложением при помощи

класса 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(), мы производим действия над текстом виджета надписи. В этот метод мы передаем в текст виджета надписи и название применяемой операции, которое соответствует названию объекта действия.

Теперь, когда мы имеем приложение, поддерживающее систему расширений, самое время создать для него хотя бы один компонент расширения.

 

 

TEMPLATE

= lib

CONFIG

+= plugin

QT

-= gui

DESTDIR

= ../plugins

SOURCES

= MyPlugin.cpp

HEADERS

= MyPlugin.h \

 

../Application/interfaces.h

TARGET

= myplugin

 

 

Для создания расширения в секции CONFIG pro-файла необходимо добавить опцию plugin, а также включить в секцию HEADERS файл интерфейсов приложения interfaces.h.

#ifndef _MyPlugin_h_ #define _MyPlugin_h_

#include <QObject>

#include "../Application/interfaces.h"

class MyPlugin : public QObject, public StringInterface { Q_OBJECT

Q_INTERFACES(StringInterface)

private:

QString oddUpper(const QString& str);

public:

virtual ~MyPlugin();

virtual

QStringList

operations(

) const;

virtual

QString

operation (const QString&, const QString&);

 

 

 

 

};

#endif //_MyPlugin_h_

Наш класс расширения MyPlugin наследует сразу два

класса: QObject иStringInterface. Добавление макроса Q_INTERFACES() нужно для того, чтобы МОС сгенерировал всю необходимую для расширения метаинформацию.

Виртуальный деструктор нам нужен для того, чтобы компилятор C++ не жаловался на то, что класс, имеющий виртуальные методы, не имеет виртуального деструктора.

/*virtual*/ QStringList MyPlugin::operations() const

{

return QStringList() << "oddUpper" << "lower";

}

Метод operations() возвращает список поддерживаемых расширением операций. В нашем случае их две: oddUpper и lower.

QString MyPlugin::oddUpper(const QString& str)

{

QString strTemp;

for (int i = 0; i < str.length(); ++i) {

strTemp += (i % 2) ? str.at(i) : str.at(i).toUpper();

}