Приветствую всех заглянувших.
В этой статье я опишу свой взгляд и приведу пример создания DLL с системой сигнал-слотов Qt.
Внимание!!! Данная статья написана под Windows, IDE Visual Studio 2008 и микрософтовский же компилятор. Для использование данного метода под другими системами, вам придётся переписать платформозависимый код. Это создание потока в Dll и программа для загрузки Dll.
Дальше будет маленькое лирическое вступление.
Иногда возникает необходимость написать программу без Qt. Требование ли это заказчика или архитектурная необходимость, но писать надо. И через некоторое время появляется проблема, которую можно легко и просто решить на Qt, но тяжело в NoQt проекте.
Через некоторое время приходит мысль написать DLL с Си интерфейсов, из которой не видно и не слышно Qt. И вот тут начинается самое интересное.
Сигнал-слотовая система Qt основана на цикле событий. Если в приложении, использующем нашу DLL не используется Qt, то будут работать только сигналы-слоты с типом соединения "Qt::DirectConnection", так называемый прямой вызов методов. Но асинхронные интерфейсы при том работать не будут.
Начнём...
Сигнал-слотовая система Qt основана на цикле событий. Если в приложении, использующем нашу DLL не используется Qt, то будут работать только сигналы-слоты с типом соединения "Qt::DirectConnection", так называемый прямой вызов методов. Но асинхронные интерфейсы при том работать не будут.
Начнём...
- Создаём проект типа Qt Library. Назовём его "QLibrary". По умолчанию создастся проект динамической библиотеки.
- Следуем дальше указаниям менеджера и получаем три файла в проекте - QLibrary.cpp, QLibrary.h, QLibrary_global.h.
- Первым делом убираем макрос "QLIBRARY_EXPORT" в объявлении класса. Экспортировать мы будем только функции интерфейса.
- Добавляем инклуды в проект. Нам нужны QObject, QFile, QCoreApplication.
- По умолчанию класс не унаследован ни от кого. Для использования сигнал-слотов, нам необходимо добавить инклуды QObject, отнаследоваться от него же и добавить макрос Q_OBJECT в тело класса. Теперь мы можем использовать сигнал-слоты.
- Добавим пару методов:
- void createFile (QString filename)
- void createFileSlot (QString filename)
createFile у нас будет обычным методом класса, создающим файл в директории "C:/test".
createFileSlot будет публичным слотом("public slots:") и будет добавлять к имени файла строку "signal".
- Добавляем сигнал void signalFileCreate(QString).
На этом заканчиваем с .h файлом. Открываем .cpp.
- Создаём глобальную переменную - указатель на наш класс "QLibrary * classPoint;". Через неё наш интерфейс будет взаимодействовать с классом.
- В конструкторе прописываем соединение нашего сигнала с слотом. Указываем тип соединения - Qt::QueuedConnection. В ином случае соединение будет Qt::DirectConnect, что нам не надо.
"connect( this, SIGNAL( signalFileCreate(QString) ), this, SLOT( createFileSlot(QString) ), Qt::QueuedConnection);"
- Так же в конструкторе инициализируем указатель, тем самым обеспечивая возможность обращения к нашему классу.
"classPoint = this;"
- Пишем реализации методов класса.
void QLibrary::createFile(QString filename) { QFile file(QString("c:/Test/%1").arg(filename)); file.open(QIODevice::WriteOnly); file.write("test method!"); emit signalFileCreate(filename); } void QLibrary::createFileSlot(QString filename) { QFile file(QString("c:/Test/%1 signal").arg(filename)); file.open(QIODevice::WriteOnly); file.write("test public slot!"); }
- Метод createFile будет создавать файл "filename" и испускать сигнал. Слот createFileSlot будет вызываться сигнал-слотовой системой и создавать файл "filename signal". Содержимое тоже будет различаться, как видите.
- Далее предстоит разобраться с функциями интерфейса и запуска цикла событий. Всего нам потребуются три функции:
- void createFile(char *) - будет вызывать метод createFile(Qstring) нашего класса.
- DWORD MyThreadFunction( LPVOID lpParam ) - функция запуска отдельного потока для цикла событий (и для нашего класса)
- void instance() - будет создавать объект нашего класса и запускать поток цикла событий
DWORD MyThreadFunction( LPVOID lpParam ) { QCoreApplication * app = NULL; int argc = 0; app = new QCoreApplication(argc, NULL); QLibrary * dllClass = new QLibrary(); app->exec(); return DWORD(); } extern "C" QLIBRARY_EXPORT void instance() { LPDWORD lpThreadId = NULL; CreateThread( NULL, // default security attributes 0, // use default stack size (LPTHREAD_START_ROUTINE)MyThreadFunction, // thread function name NULL, // argument to thread function 0, // use default creation flags lpThreadId); } extern "C" QLIBRARY_EXPORT void createFile(char * inChar) { classPoint->createFile(QString::fromStdString(inChar)); }
- Экспортироваться будут только две функции - instance и createFile(char *).
- Разберём что мы делаем.
- Функция instance создаёт поток, в котором исполняется функция MyThreadFinction. В неё же мы создаём QCoreApplication, экземпляр своего класса и запускаем цикл обработки событий.
- Функция createFile будучи вызванной дёрнет метод createFile(QString).
- Компилируем - наслаждаемся отсутствием ошибок.
- Имена экспортируемых методов могут меняться. Для пресечения этого необходимо добавить в проект .def файл со следующим содержимым
LIBRARY "QLibrary"
EXPORTS
; Explicit exports can go here
instance
createFile
- Вот мы и создали Dll с циклом событий внутри.
- Настало время проверить нашу dll. Для этого мы создадим маленькую C++ программу - DllLoader, код которой приведён ниже.
#include "stdafx.h" #include "windows.h" #include <conio.h> #include <ctype.h> int _tmain(int argc, _TCHAR* argv[]) { printf("The program demonstrates the use dll written on Qt. To work correctly, you need to create the directory \"C:/Test/\"\n"); printf("Press any key to boot dll\n"); _getch(); HMODULE library = LoadLibrary(L"QLibrary.dll"); if (!library) { printf("Not exists QLibrary.dll."); return 0; } printf("Dll loaded.\n"); void (*dllInstance) (void); dllInstance = (void (*)(void))GetProcAddress(library, "instance"); if (!dllInstance) { printf("Not export \"instance\" from DLL."); return 0; } dllInstance(); printf("Dll initialized. Press any key to continue.\n"); _getch(); void (*dllCreateFile) (char*); char* filename = "otherName"; dllCreateFile = (void (*)(char*))GetProcAddress(library, "createFile"); if (!dllCreateFile) { printf("Not export \"createFile\" from DLL."); return 0; } dllCreateFile(filename); printf("Work comlete. Check \"C:/Test/\" Press any key to exit. \n"); _getch(); return 0; }
- Программа загружает библиотеку, инициализирует наш класс вызовом instance и вызывает функцию createFile( "OtherName" ).
- Для проверки нам необходимо создать директорию "C:/Test/" и скопировать нашу библиотеку в каталог DllLoader'a. Запускаем DllLoader, жмём любую клавишу два раза с небольшим промежутком и смотрим результат. В каталоге должны появиться файлы "otherName" и "otherName signal".
Профит!!!
Архив с исходниками:
https://docs.google.com/file/d/0B_mrvcleB88yclZjSHhQVTQ5eXM/edit?usp=sharing
Зеркало:
https://dl.dropboxusercontent.com/u/62712483/Blog/QtDll_NoQtProg.ZIP
В архивах проекты для VS2008 и pro файл.
Совет:
При написании dll во всех connect'ах указывайте тип соединения. Из-за автоматического определения типа соединения могут возникать непонятные и опасные для программы ситуации. Так же возможно выпадение волос разработчика в процессе отладки и ранняя седина.
Предупреждение:
Загрузка в одно приложение нескольких экземпляров данной библиотеки может вызвать крах. Данная статья описывает лишь создание одного экземпляра Dll.
Если dll будет использоваться в Qt приложениях, необходимо добавить проверку на наличие уже существующего цикла событий.
Пожелания, предложения, замечания - в этот блог(вроде сообщения тут есть) или же на мыло work.bepec.sb@gmail.com.
Копирайтить копирайте, но ссылочку на источник прошу добавлять ^.^
хорошая статья, спасибо!
ОтветитьУдалитьЗагрузка в одно приложение нескольких экземпляров данной библиотеки может вызвать крах. Данная статья описывает лишь создание одного экземпляра Dll.
ОтветитьУдалить-----------
И почему же?
Наверное, что-то не так в реализации DWORD MyThreadFunction( LPVOID lpParam )
))