На форуме возник вопрос, как создать динамическую библиотеку и правильно её подключить в сторонний проект. Периодически такие вопросы возникают, поэтому рассмотрим один вариант создание динамической библиотеки 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 файл нашего проекта, который будет использовать динамическую библиотеку.
WithDynamicLibrary.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 со статическими методами, ибо потом меньше проблем с линковкой и компиляцией.