Russian Qt Forum
Декабрь 22, 2024, 23:23 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3   Вниз
  Печать  
Автор Тема: Урок: Создание динамических библиотек  (Прочитано 92007 раз)
Eugene Efremov
Гость
« : Декабрь 20, 2008, 19:23 »

Создание динамических библиотек

Вступление

Тема динамических библиотек, казалось бы, раскрыта 42 главе фундаментального труда Шлее. Однако, при ближайшем рассмотрении выясняется, что раскрыта она, прямо скажем, недостаточно. В приведенном примере Шлее ограничивается рассмотрением создания и использования DLL, содержащей только функции, но не классы. После чего сразу переходит к рассмотрению плагинов.

Между тем, при создании динамических библиотек, содержащих классы, возникают некоторые тонкости, которые необходимо учитывать. Об этом и пойдет речь в данном howto.

При описании я буду предполагать, что читатель в общих чертах знаком с основами Qt, и у него не возникнет вопросов типа «а что такое QVBoxLayout. Так что я не буду описывать текст исходников примера (он сам по себе весьма прост), а сосредоточусь непосредственно на особенностях, характерных для DLL. Если с пониманием примера все же возникнут трудности — рекомендую сперва прочитать того же Шлее, а уже потом обратится к этому тексту.

И еще — здесь рассматривается только явная компоновка приложения и библиотеки. Если необходимо подгрузить класс динамически — лучше оформить его как плагин. В противном случае вас ждут большие проблемы...


1.Библиотека

Лежит в директории src/dll.

Библиотека содержит простейший виджет, производный от QLabel. Им можно пользоваться точно также, как любым другим виджетом Qt: добавлять в свои виджеты, использовать слоты/сигналы, создавать производные классы и т.д.

Отличие от обычного Qt приложения сосредоточены, в основном, в двух файлах: ddll.h и dll.pro.

ddll.h:
Код
C++ (Qt)
#ifndef D_DLL
#define D_DLL
#include <QtGlobal>
 
#ifdef D_SHARED_LIB
#define D_SHARED Q_DECL_EXPORT
#else
#define D_SHARED Q_DECL_IMPORT
#endif
 
#endif
 

Вообще говоря, без этого файла можно обойтись. Если пользоваться нормальным компилятором. Но, помимо нормальных компиляторов, существуют еще компиляторы от microsoft. И в них, по слухам, требуется подобное извращение.

Что здесь написано. Q_DECL_EXPORT и Q_DECL_IMPORT — это два недокументированных (пока?) макроса, которые введены разработчиками Qt специально, чтобы обойти эти грабли в MSVS. Первый из них требуется писать перед объявлением класса в DLL, второй — в приложениях, которые этот DLL используют. Во всех остальных компиляторах они равны пустой строке.

Соответственно, перед всеми классами в нашей библиотеке мы должны проставить наш макрос D_SHARED:
Код
C++ (Qt)
#include <QLabel>
#include "ddll.h"
 
class D_SHARED DLabel : public QLabel
{
Q_OBJECT
....
 


На этом отличия исходного кода кончаются. Главные различия касаются файла проекта:

dll.pro:
Код
TEMPLATE = lib
CONFIG += shared
 
# in
VERSION = 1.0.0
DEFINES += D_SHARED_LIB
SOURCES = dlabel.cpp
HEADERS = dlabel.h ddll.h
 
# out
TARGET = dlabel
DLLDESTDIR = ../../bin
DESTDIR = ../../lib
 

Полагаю, TEMPLATE и CONFIG в комментариях не нуждаются. На некоторых других вещах стоит остановится подробнее.

DEFINES — определяем макрос D_SHARED_LIB, который используется в ddll.h (см. выше).
DESTDIR и DLLDESTDIR — мы помещаем получившуюся библиотеку в lib. Но само приложение у нас будет находится в bin, поэтому мы копируем DLL туда.
VERSION — а вот здесь нам необходимо небольшое лирическое отступление.

Существует такая вещь, как бинарная совместимость. Если кратко — мы создаем некоторую структуру, помещаем ее в DLL. Потом наши приложения этот DLL юзают. А потом мы ее изменяем. И перезаписываем DLL поверх старого. А приложения обращаются к ней по старым адресам... Результат, думаю, ясен.
Так вот, это называется — отсутствие бинарной совместимости. В просторечии — dll hell.

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

Однако, вернемся к нашим баранам. Увидев VERSION, qmake во-первых, принимает необходимые меры, чтобы эта версия прописалась вo внутренних свойствах DLL, а, во-вторых — приписывает ее старший номер к имени создаваемого DLL. Т.е. в нашем примере это будет dlabel1.dll. Соответственно, если мы изменим интерфейс нашей библиотеки до полной несовместимости, для предотвращения использования ее старыми приложениями мы должны будем просто сменить VERSION на 2.0.0. И тогда новые версии будут использовать dlabel2.dll, старые — dlabel1.dll.


2.Приложение

Лежит в директории src/main.

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

main.pro:
Код
TEMPLATE = app
CONFIG += qt warn_on
 
#in
DEPENDPATH += ../dll
INCLUDEPATH += ../dll
LIBS += -L../../lib -ldlabel
HEADERS = dlcat.h
SOURCES = dlcat.cpp dmain.cpp
RESOURCES = longcat.qrc
 
#out
DESTDIR = ../../bin
TARGET = longcat
 

Остановимся на этих отличиях подробнее.

INCLUDEPATH — указываем путь к хидерам библиотеки.
DEPENDPATH — объясняем qmake, что хидеры библиотеки необходимо учитывать при построении зависимостей.
LIBS — подключаем библиотеку. При этом путь и сам библиотека указывается отдельно и в самом общем виде (без расширений и т.п.). В такой форме это будет правильно обработано для любого компилятора. VERSION у библиотеки должен быть указан, иначе это не сработает. При наличии нескольких версий будет выбрана последняя.


3. Возможные грабли

При использовании динамической линковки следует помнить о некоторых общих вещах, которые необходимо учитывать во избежание ошибок. Впрочем, при нормальной работе Qt, в большинстве случаев, сама заботится о том, чтобы сделать «нарушение правил» достаточно сложным...
Итак:

1. Пользуйтесь для сборки библиотеки и приложения одним и тем же компилятором.

Разные компиляторы по-разному кодируют в объектном коде имена функций, классов и т.д. Более того, могут отличаться и смещения полей внутри класса. По этому попытка подключить dll, собранный в другом компиляторе, скорее всего, увенчается успехом лишь в том случае, если все ф-ции отмечены как extern "C" и смещения структур выравнены принудительно. Для классов это, разумеется, не подходит.

Как ни трудно понять, при самостоятельной разработке библиотеки эта проблема вряд ли возникнет. А вот если мы имеем чужой dll и закрытый код... Тем, кто попал в такую ситуацию, остается только посочувствовать.


2. ...И одними и теми же опциями командной строки — в части, влияющей на свойства генерируемого кода.

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

В Qt об опциях командной строки компилятора заботится qmake, так что в обычной ситуации эта проблема возникать не должна. Иными словами — если не делать явных глупостей, вроде смешения debug и release в одну кучу, то этого и не произойдет.


3. Используя низкоуровневые ресурсы, обеспечте инкапсуляцию всей фактической работы с ними в рамках одного  модуля, линкуемого динамически.

Если CRT скомпонована статически, то ресурс (например — память), выделенный в рамках одного модуля, невозможно корректно освободить в рамках другого: каждый из них будет оперировать со своим адресным пространством и своими копиями внутренних структур, обеспечивающих нормальный доступ к ресурсам. Кроме того, разумеется, нужно следить, чтобы во всех модулях использовалась CRT одной и той же версии.

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

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


И еще, касательно данного примера. Следует иметь в виду, что он писался в предположении, что библиотека будет линковаться только динамически. Для для случая статической линковки макросы Q_DECL_IMPORT/EXPORT следует убрать. Как средствами qmake отличить статическую линковку от динамической и обеспечить включение/выключение нужных макросов — продемонстрировано ниже по треду в письме от pastor, на модификации моего примера. За что ему отдельное спасибо.

Собственно, на этом всё. Enjoy! Улыбающийся


Проверено
  • под Windows 2003 в Qt 4.4.1, в компиляторе mingw, gcc version  3.4.5
  • под Windows XP x64 в Qt 4.4.3, в MSVS 2008
  • под openSuse 11 в Qt 4.4.3, в gcc version 4.3.1 20080507
За последние 2 варианта — спасибо pastor!
« Последнее редактирование: Май 19, 2010, 08:40 от xintrea » Записан
pastor
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 2901



Просмотр профиля WWW
« Ответ #1 : Декабрь 20, 2008, 20:20 »

Да, действительно, __dllimport (Q_DECL_IMPORT) и __dllexport (Q_DECL_EXPORT) это приблуда MS Visual Studio. Если использовать другой компилятор, отличный от MS Visual Studio, то без этого можно обойтись. Но это ещё не все, что касается этих макросов и MS Visual Studio. Если разрабатываемую вами библиотеку сделать статической, то приложение, использующее её, линковатся не будет. Причина в том, что при статической линковке эти макросы также не нужны.

Я немного модифицировал код Eugene Efremov.

Проверено: Windows XP x64, Qt 4.4.3, MSVS 2008
                  openSuse 11, Qt 4.4.3, gcc version 4.3.1 20080507
« Последнее редактирование: Декабрь 22, 2008, 16:23 от pastor » Записан

Integrated Computer Solutions, Inc. (ICS)
http://www.ics.com/
Rcus
Гость
« Ответ #2 : Декабрь 20, 2008, 20:34 »

На самом деле MinGW тоже поддерживает эти директивы (другое дело что если их не указывать msvc не будет экспортировать символы вообще, а MinGW экспортирует все указанные в хедерах).

И еще не хватает части про несовместимость name mangling, проблемы с dynamic_cast и crt. А так описываются почти все грабли Улыбающийся
Записан
Eugene Efremov
Гость
« Ответ #3 : Декабрь 22, 2008, 17:13 »

И еще не хватает части про несовместимость name mangling, проблемы с dynamic_cast и crt. А так описываются почти все грабли Улыбающийся

Проблемы с dynamic_cast — имеется в виду, что для его нормальной работы оно оба модуля должны быть скомпилированы с поддержкой RTTI? Или там еще какие-то грабли есть, про которые я не знаю?

В общем, секцию с граблями я добавил, если про dynamic_cast нужно что-то еще — поправлю...


Я немного модифицировал код Eugene Efremov.

Спасибо.
Я указал в конце, что рассматривался только случай динамической линковки, а если нужно еще и статическую — см. ниже. И дал ссылку на этот новый пример.
Записан
Rcus
Гость
« Ответ #4 : Декабрь 22, 2008, 19:09 »

Проблемы с dynamic_cast — имеется в виду, что для его нормальной работы оно оба модуля должны быть скомпилированы с поддержкой RTTI? Или там еще какие-то грабли есть, про которые я не знаю?

В общем, секцию с граблями я добавил, если про dynamic_cast нужно что-то еще — поправлю...

если мы создаем в одном исполняемом модуле некоторый объект и передаем указатель на его в динамическую библиотеку, то dynamic_cast будет выдавать 0. Это связано с особенностью реализации rtti. Отчасти эту проблему решает qobject_cast, но работает только для потомков QObject.
Грабли crt заключены в том, что оба модуля должны линковаться с одной версией crt (например релизная и отладочная версии несовместимы)
Записан
Eugene Efremov
Гость
« Ответ #5 : Декабрь 22, 2008, 21:33 »

если мы создаем в одном исполняемом модуле некоторый объект и передаем указатель на его в динамическую библиотеку, то dynamic_cast будет выдавать 0. Это связано с особенностью реализации rtti. Отчасти эту проблему решает qobject_cast, но работает только для потомков QObject.

Ммм... У меня в gcc этот баг не получается воспроизвести. Все dynamic_cast отлично работают и там, и там. Можно пример кода?

Или это еще один личный глюк MSVS?

Грабли crt заключены в том, что оба модуля должны линковаться с одной версией crt (например релизная и отладочная версии несовместимы)

Т.е., фактически попадает под то, что у меня описано в п.2. Добавил там про отладку.
Записан
Rcus
Гость
« Ответ #6 : Декабрь 22, 2008, 22:06 »

На самом деле это не баг, а особенность механизма линковки. Демо проект прикреплен,
Код:
main@rcuhome:~$ g++ --version
g++ (Ubuntu 4.3.2-1ubuntu11) 4.3.2
Записан
Eugene Efremov
Гость
« Ответ #7 : Декабрь 22, 2008, 22:40 »

На самом деле это не баг, а особенность механизма линковки. Демо проект прикреплен,
Код:
main@rcuhome:~$ g++ --version
g++ (Ubuntu 4.3.2-1ubuntu11) 4.3.2

Ну и? У меня (windows 2003, g++ 3.4.5) все работает:
Код:
Dynamic? Foo::test, upcast ? = 0
Dynamic? Bar::test, upcast ? = 2293584

Хотя, если учесть, что там мешается в одну кучу статическая и динамическая линковка — меня это очень удивляет. При таком раскладе действительно странно, что оно работает. Если заменить в foobar.pro static на shared — оно под linux'ом заработает?
Если да — dynamic_cast тут несколько сбоку...

Кстати, а я ведь в явном виде так и не написал, что статическую и динамическую линковку лучше не мешать. Исправляюсь...
Записан
lex_newton
Гость
« Ответ #8 : Август 05, 2009, 10:04 »

Собственно, хочу рассказать о граблях Подмигивающий
Windows XP SP3, g++ (GCC) 3.4.2 (mingw-special), QT 4.4.3

Создаю динамическую библиотеку в которой находится класс унаследованный от QWidget и имеет макрос Q_OBJECT
Код:
class MyClass : public QWidget
{
Q_OBJECT
public:
    MyClass( QWidget* parent = 0 );
    ...
}

Собираю динамическую библиотеку (все собирается без ошибок и т.п.)
Код:
CONFIG += shared

И в главной программе использую MyClass.
В чем грабли?
Если использовать MyClass то ничего особенного не происходит. все работает прекрассно.
НО! Если попытаться в главной программе сделать нечто подобное, то возникает ошибка 0xc0000005 при запуске программы
Код:
class MyClass2 : public MyClass
{
Q_OBJECT
public:
    MyClass2( QWidget* parent = 0 );
}

Причем, если закомментировать Q_OBJECT то программа перестает вылетать.

Как оказалось, дело в том, что под windows даже используя gcc необходимо указывать Q_DECL_EXPORT Q_DECL_IMPORT.
И VS тут не при чем, это фишка платформы win32.

+ если мы хотим экспортировать не класс, а функцию, поступаем тем же самым способом. пишем перед прототипом функции соотвествующие макросы.

Записан
polyakovp84
Гость
« Ответ #9 : Сентябрь 29, 2009, 17:38 »

Разбираясь с данным примером понял, что библиотека загружается сразу при запуске программы.

А как сделать чтобы при выполнении некого условия библиотека загружалась динамически, затем содавался класс для последующей работы с ним?
Если можно простой работающий пример.
Заранее благодарен.
Записан
Rcus
Гость
« Ответ #10 : Сентябрь 29, 2009, 17:43 »

"Plug & Paint Example"
Записан
SASA
Гость
« Ответ #11 : Октябрь 01, 2009, 15:37 »

Есть проблема с qobject_cast.
Если класс описан в dll и создан там же, то в app попытка привести указатель к этому классу даёт ноль.
Ответ сказали искать здесь
http://www.qtcentre.org/forum/f-qt-programming-2/t-casting-result-of-qwidgetstack-currentwidget-5037.html
Но я чё-то не понял и проблему решить не смог. Может есть у кого какие мысли.
З.Ы. Билбиотека загружается динамически - плагин.
« Последнее редактирование: Октябрь 02, 2009, 08:10 от SASA » Записан
UVV
Гость
« Ответ #12 : Октябрь 01, 2009, 16:10 »

Там в принципе описано решение, использовать третью библиотеку и линковаться с ней.
Но у меня прокатило просто dynamic_cast =)
Записан
Elfet
Гость
« Ответ #13 : Апрель 06, 2010, 18:20 »

Приветствую!

У меня возникла такая проблема с dll, не знаю в чём дело. Пожалуйста, помогите!

Есть два проекта: один интерфейс, другой библиотека dll на чистом C++

Всё собираю в QtSDK (Qt Creator + minGW).

Одно время было всё хорошо, а вот недавно по непонятной мне причине появились глюкт с dll. Нажимаю пересобрать библиотеку, затем пересобрать интерфейс, запускаю - вроде бы всё хорошо, но выполняется совсем не тот код (видно по тому что отображается на интерфейсе)

Однако если я нажимаю F5 (отладка) то всё запускается нормально и выполняется нормально.

В чём тут дело?  Непонимающий

Может быть я что-то неправильно настроил?
SmartFlow.pro:
Цитировать
...
# SmartFlowLib
DEPENDPATH += ../SmartFlowLib/Source
INCLUDEPATH += ../SmartFlowLib/Source
LIBS += ./debug/smartflowlib1.dll
...


SmartFlowLib.pro
Цитировать
QT -= gui
TEMPLATE = lib
CONFIG += shared

TARGET = smartflowlib
DESTDIR = ../SmartFlow/debug

VERSION = 1.0.0.1

Весь код можно найти тут: http://code.google.com/p/smart-flow/source/browse/#svn/trunk

Заранее спасибо!
Записан
Amigo_sa
Гость
« Ответ #14 : Апрель 06, 2010, 18:22 »

у нас такое было, когда в папке с объектными файлами случайно оказывались одноименные. проверьте пути, скорее всего просто либа собирается из старых объектников
Записан
Страниц: [1] 2 3   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.181 секунд. Запросов: 24.