На форумі постало питання, як створити динамічну бібліотеку та правильно її підключити до стороннього проекту. Періодично такі питання виникають, тому розглянемо один варіант створення динамічної бібліотеки dll для Windows з використанням стандартних візардів в Qt Creator.
В даному випадку не розглядатиметься варіант, коли проект розбивається на підпроекти, які компілюються як бібліотеки і потім підключаються до головного проекту. Бо це будуть динамічні внутрішні бібліотеки проекту. Створимо зовнішню бібліотеку, яку теоретично можна буде поширювати у вигляді бінарників.
Створимо два проекти:
- QuiLib - це буде зовнішня динамічна бібліотека, яка міститиме одне діалогове вікно. Дане діалогове вікно відкриватиметься в основному проекті.
- WithDynamicLibrary - проект, який використовуватиметься саме для підключення даної динамічної бібліотеки.
Крок 1
Виберемо у меню Qt Creator створення проекту та виберемо тип нашого проекту. Це буде бібліотека C++.
Крок 2
Пропишемо назву проекту та місце його розміщення
Крок 3
Виберемо комплекти для збирання проекту.
Тут є дуже важливий момент, на якому новачки можуть забувати. Якщо ви збираєте проект за допомогою компілятора певної версії, то використовувати дані бібліотеки можна буде тільки в проекті, який буде збиратися компілятором тієї ж версії.
Я збираю цю бібліотеку компілятором MSVC2017.
Крок 4
Вибираємо потрібні модулі. Для нашої бібліотеки вистачить базового функціоналу.
Крок 5
Дамо назву класу бібліотеки, який використовуватиметься в ньому. Назва в даному випадку буде такою ж, як і назва бібліотеки. Але можна і змінити. Це не принципіально.
Крок 6
Чи використовуєте систему контролю версій? Тож додайте проект під контроль версій. Якщо ні, нічого не робіть. І просто завершіть створення бібліотеки.
Крок 7
Подивимося на файли проекту та трохи модифікуємо їх.
Структура проекту
QuiLib.pro
У цьому файлі є інформація про те, що це бібліотека. Ось у цьому рядку.
TEMPLATE = lib
Ось повний код pro-файлу
#------------------------------------------------- # # Project created by QtCreator 2018-10-09T19:33:33 # #------------------------------------------------- QT += widgets TARGET = QuiLib TEMPLATE = lib DEFINES += QUILIB_LIBRARY # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ QuiLib.cpp HEADERS += \ QuiLib.h \ quilib_global.h unix { target.path = /usr/lib INSTALLS += target }
quilib_global.h
Хедер для визначення дефайнів експорту до бібліотеки. Класи, які будуть позначені дефайном експорту, будуть доступні для використання поза бібліотекою.
#ifndef QUILIB_GLOBAL_H #define QUILIB_GLOBAL_H #include <QtCore/qglobal.h> #if defined(QUILIB_LIBRARY) # define QUILIBSHARED_EXPORT Q_DECL_EXPORT #else # define QUILIBSHARED_EXPORT Q_DECL_IMPORT #endif #endif // QUILIB_GLOBAL_H
QuiLib.h
Підкоригуємо трохи заголовний файл діалогового вікна, адже нам потрібен саме діалог, а значить клас в даному заголовному файлі потрібно успадковувати від QDialog.
#ifndef QUILIB_H #define QUILIB_H #include "quilib_global.h" #include <QDialog> class QUILIBSHARED_EXPORT QuiLib : public QDialog { public: explicit QuiLib(QWidget* parent = nullptr); }; #endif // QUILIB_H
QuiLib.cpp
Також напишемо реалізацію конструктора діалогового вікна, щоб він нам повідомив за допомогою QLabel, що це діалогове вікно із зовнішньої бібліотеки.
#include "QuiLib.h" #include <QLabel> #include <QGridLayout> QuiLib::QuiLib(QWidget* parent) : QDialog(parent) { QGridLayout* gridLayout = new QGridLayout(this); setLayout(gridLayout); gridLayout->addWidget(new QLabel("Hello world from dynamic library", this)); }
Крок 8
Скомпілюємо проект у Debug та Release версіях.
Ось наприклад, що буде в каталозі release збирання бібліотеки. З тих файлів нам знадобляться тільки файли QuiLib.dll та QuiLib.lib. Крім цих файлів потрібні ще будуть заголовні файли з самого проекту, але про це трохи пізніше.
Крок 9
Створення проекту, який використовуватиме цю динамічну бібліотеку. Процес створення буде стандартним, через візард у Qt Creator. Потрібно буде вибрати програму на Qt.
Додати назву проекту та місце його розміщення
Крок 10
Вказати комплект збирання.
Крок 11
Введемо назву класу головного вікна, а також вкажемо від якого класу успадковуватися. Я обрав QWidget.
Крок 12
Знову вибір системи контролю версій та завершення процесу створення проекту.
Крок 13
Структура проекту
У каталозі даного проекту створимо каталог QuiLib , до якого помістимо каталоги debug, release, include.
У цих каталогах будуть міститися скомпіловані бібліотеки QuiLib.dll та QuiLib.lib відповідно до налагоджувальної версії та версії, що випускається. У каталозі include будуть заголовні файли QuiLib.h та quilib_global.h.
Тобто ми представили ту ситуацію, в якій ми комусь передали скомпільовану бібліотеку, щоб він міг її підключити та використати.
Крок 14
Додаємо бібліотеку до проекту за допомогою візарду. Можна звичайно і вручну все прописати, але якщо сумніваєтеся у своїх силах, а це так і є, інакше не читали б цю статтю, то використовуємо Візард.
Крок 15
Ми знаємо, що зовнішня бібліотека
Крок 16
А також, що використовуватимемо її тільки для Windows. Тут налаштовано, що файли Debug та Release версій знаходяться у різних каталогах без жодних префіксів бібліотек налагодження. Я їх просто не налаштовував. Достатньо вказати одну з .lib * бібліотек або в debug, або release каталозі. Шлях до іншої версії буде додано автоматично. Також потрібно вказати каталог, в якому розміщуються заголовні файли. Зазвичай це include** каталог.
Крок 17
Завершуємо додавання
Крок 18
Потрібно написати метод, який викличе діалогове вікно із зовнішньої бібліотеки. Але спочатку подивися, куди були додані рядки підключення сторонньої бібліотеки, які ми бачили на сторінці візарду в кроці 17.
Отже, тепер глянемо в pro файл нашого проекту, який використовуватиме динамічну бібліотеку.
За допомогою DynamicLibrary.pro
#------------------------------------------------- # # Project created by QtCreator 2018-10-09T19:45:20 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = WithDynamicLibrary TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 CONFIG += c++11 SOURCES += \ main.cpp \ Widget.cpp HEADERS += \ Widget.h FORMS += \ Widget.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target win32:CONFIG(release, debug|release): LIBS += -L$$PWD/QuiLib/release/ -lQuiLib else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/QuiLib/debug/ -lQuiLib INCLUDEPATH += $$PWD/GuiLib/include DEPENDPATH += $$PWD/GuiLib/include
Це останні рядки в даному файлі.
Widget.ui
Через графічний дизайнер додамо кнопку в головне вікно, натисканням якої буде викликатися діалог із зовнішньої бібліотеки.
Widget.h
Пропишемо слот для обробки кліка кнопки.
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); ~Widget(); private slots: void onPushButtonClicked(); // Слот для обработки клика по кнопке private: Ui::Widget *ui; }; #endif // WIDGET_H
Widget.cpp
А тепер обробимо клік кнопки та викличемо діалогове вікно із зовнішньої бібліотеки.
#include "Widget.h" #include "ui_Widget.h" #include <QPushButton> // Подключаем заголовочный файл библиотеки #include <QuiLib/include/QuiLib.h> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); // Подключаем слот к сигналу от кнопки connect(ui->pushButton, &QPushButton::clicked, this, &Widget::onPushButtonClicked); } Widget::~Widget() { delete ui; } void Widget::onPushButtonClicked() { // Вызываем диалоговое окно QuiLib libWidget(this); libWidget.exec(); }
Зауважте, що викликати діалогове вікно потрібно обов'язково методом exec() щоб запустилася внутрішня петля діалогу, яка чекатиме на події. Інакше діалог відразу закриється, оскільки слот відпрацює, а діалог у разі створено на стеку методу і після завершення методу діалог знищиться. А метод exec() завершиться лише тоді, коли відбудеться відповідна подія, яка закриє діалог.
Хороший урок, все подробно расписано. Такой вопрос: версия Qt для дин.библиотеки не обязательно должна совпадать с версией Qt проекта, который эту библиотеку использует?
Спасибо ))
Из того, что я читал в документации, следует, что библиотеки Qt бинарно совместимы по минорным версиям. То есть, если проект работал с Qt 5.6, то можно поменять библиотеки на Qt 5.7 и по прежнему всё будет работать. На практике, конечно, не всегда всё гладко проходит. То есть по идее, если динамическая библиотека использует Qt 5.6, а подключили её в проект с Qt 5.7, то должно работать. Но опять же оговорюсь, на практике может выйти иначе, особенно, если динамическая библиотека использовала Qt 5.7, а подключили проект на Qt 5.6. Как минимум мождете оказаться, что в Qt 5.6 в каком-то классе отсутствуют некоторые методы.
То есть теоретически возможно, практически, как карта ляжет.
Вот какой вопрос возник: для запуска программы вне Qt приходится тащить с ехе'шником кучу dll. А для использования созданной dll не придется ли тащить с собой всё те же Qt5Core.dll, Qt5Gui.dll, Qt5Widgets.dll...? Особенно если дальнейшее использование созданной dll планируется без участия Qt
Погодите. Если речь идёт о библиотеке, которая использует Qt, от естественно, что ей понадобятся все те модули, от которых зависит бибилотека. Например в данном примере используются модули Qt5Core, Qt5Gui, Qt5Widgets, соответсвенно их тоже придётся тащить с собой. Если же вы создаёте библиотеку без участия Qt, то и модули Qt не будут нужны.
Вы не можете запланировать использование библиотеки без Qt, если она использует модули Qt, но если вы отказываетесь от использования Qt в библиотеке, то тогда получаете возможность не тащить все выше перечисленные модули, поскольку библиотека от них не зависит тогда.
Круто было бы прочитать про приложение с подключаемыми плагинами.
Типо как в Qt Creator?
Самому бы интересно было о таком почитать. В данный момент я бы мог написать только о написании плагинов для Qt Designer. С этим есть некоторый опыт.
ну типа того, создание программы, функционал которой можно расширять плагинами, и, в перспективе, создание API.
О плагинах к QtCreator в целом, тоже интересно.
Если и начинать писать о плагинах, то нужно тогда с Qt Creator начинать, там наверняка будет одинаковый принцип, но по Qt Creator хотя бы информация есть.
наверняка, так и есть)
В принципе у меня есть опыт реализации плагинов, могу что-нибудь накропать как будет время
Это было бы здорово и полезно ))
При запуске приложения библиотека должна лежать рядом с исполняемым файлом. А как сделать так, чтобы библиотека лежала в папке на уровень ниже чем сам исполняемый файл?
QApplication::addLibraryPath()
Можно в каталоге приложения создать файл qt.conf в котором прописать пути библиотек:
А можно динамическую библиотеку, скомпелированную в Visual Studio и никак не связанную с Qt, подключить в проект который разрабатывается в Qt?
Какие действия для этого нужно сделать?
Достаточно ли будет просто заменить эти строки:
на эти:
?
Полагаю, что да, нужно переписать экспорт, как вы написали. А подключение в Qt проекте будет аналогичным, такженаличие пути к библиотеке и заголовочные файлы. Главное, чтобы компиляторы были одной версии.
здравствуйте! при компиляции библиотеки выскакивает окно особая программа( не удалось найти программу, укажите путь к ней), и в папке debug создается файл .dll, а .lib нет. подскажите, пожалуйста, в чем проблема.
Добрый день!
Очень мало информации, как писать классы с методами для компиляции в динамическую библиотеку.
Пример: в классе QuiLib дополнительно есть методы, которые могут вызываться, например
то этот метод следует обьявлять как virtual, чтобы потом вызвать его где надо, верно?
Нет, не верно. Модификатор virtual помечает метод класса как виртуальный, что позволяет переопределять методы при наследовании классов. К библиотекам вообще никакого отношения не имеет.
Обычно функции отдельно помечаются макросом типо QUILIBSHARED_EXPORT, но проще написать класс helper со статическими методами, ибо потом меньше проблем с линковкой и компиляцией.