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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Qt из dll без GUI  (Прочитано 11406 раз)
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« : Апрель 07, 2014, 17:47 »

Доброго всем времени.

Есть необходимость запихнуть Qt-шный QCoreApplication в DLL. Знаю что тема уже сто-пицот раз обсуждалась, но моя проблема немного специфичная.

Имеем некую DLL которая экспортирует чисто COM-интерфейсы без всякого GUI и прочего. Эту dll-ку подгружает
некий виндовый сервис самостоятельно, без моего участия. В принципе, если реализовать все необходимые "фичи"
внутри dll-ки, используя Win API - то проблем нет. Но дело в том, что внутри dll-ка общается при помощи пайпов с
некой программной-сервером с использованием некоего самопального RPC протокола. Проблема в том, что этот
сервер и RPC протокол реализован на Qt, и кроме dll-ки к серверу коннектятся и иные Qt-шные программы-клиенты.
Поэтому RPC протокол сделан на Qt и он как-бы общий; делать этот RPC протокол еще и отдельно на Win API для
dll-ки у меня не хватает духу - да и накладно. Улыбающийся

Хотелось бы впихнуть уже готовое это дело внутрь DLL.

Знаю как-бы два способа это сделать:

1. Через создание экземпляра QCoreApplication в отдельном треде QThread внутри dll-ки.
2. Использовать QtWinMigrate solution

Сделал пока что по п.1. Вроде с наскоку оно работает, но сыпет всякими ворнингами:
Цитировать
...
[4152] [default] bool __cdecl check_parent_thread(class QObject *,class QThreadData *,class QThreadData *): QObject: Cannot create children for a parent that is in a different thread.
...

мне не нравится это, ну вообще, не нравится..

Хочу как-то передалеть на п.2. , немного перекодив солюшен под себя (выдернуть только необходимое из него).
Но проблема в том, что этот солюшен заточен для GUI приложений. И Qt-шный цикл событий "замещается" циклом
событий от WinAPI - шного окна (т.е. как-бы берется от WinAPI - шного окна, как я понял).

Но в моей dll-ке нету никаких внешних методов которые создавали бы WinAPI-шные окошки, есть только медод:

DllGetClassObject

Код
C++ (Qt)
HRESULT __stdcall DllGetClassObject(
 _In_   REFCLSID rclsid,
 _In_   REFIID riid,
 _Out_  LPVOID *ppv
);
 

в котором создаются некие COM-объекты.. Улыбающийся

ЗЫ: Естественно, есть и DllMain() в которой можно обработать DLL_PROCESS_DETACH/ATTACH но я умалчиваю об этом.

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

Пока думаю о возможности запускать WinAPI-шный таймер и в его callback-функции дергать:
Код
C++ (Qt)
qApp->processEvents()
 

например, с периодичностью в 10 мсек... Но, кажется, что это будет сказываться на работе RPC с использованием QLocalSocket..

Есть у кого какие-нить идеи? Может кто победил это?  Улыбающийся




 
« Последнее редактирование: Апрель 07, 2014, 17:50 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
Bepec
Гость
« Ответ #1 : Апрель 07, 2014, 18:50 »

Эммм. По моей статейке делали тред внутри?

Насколько я помню network работает нормально в таких dll без варнингов.
Записан
vregess
Гость
« Ответ #2 : Апрель 07, 2014, 20:25 »

Тоже выдирал из  QtWinMigrate.
Может такое подойдет:

Код
C++ (Qt)
// DLLWrapper.h
class DLLWrapper
{
public:
   static bool init(HINSTANCE inst = NULL);
   static void uninit();
 
private:
   DLLWrapper();
};
 
// DLLWrapper.cpp
HHOOK hook = 0;
 
 
LRESULT CALLBACK hook_callback(int code, WPARAM wParam, LPARAM lParam)
{
   if (qApp)
       qApp->sendPostedEvents();
   return CallNextHookEx(hook, code, wParam, lParam);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
 
 
bool DLLWrapper::init(HINSTANCE inst)
{
   if (qApp)
       return false;
 
   QT_WA({
       hook = SetWindowsHookExW(WH_GETMESSAGE, hook_callback, 0, GetCurrentThreadId());
   }, {
       hook = SetWindowsHookExA(WH_GETMESSAGE, hook_callback, 0, GetCurrentThreadId());
   });
 
   if (!hook)
       return false;
 
   int argc = 0;
   new QApplication(argc, 0);
 
   return true;
}
//------------------------------------------------------------------------------
 
void DLLWrapper::uninit()
{
   if (hook)
   {
       UnhookWindowsHookEx(hook);
       hook = 0;
   }
}
//------------------------------------------------------------------------------
 
 
Единственное поменять QApplication на QCoreApplication. Это для qt4.

Код
C++ (Qt)
void init_dll()
{
   ...
   DLLWrapper::init();
   ...
}
 
void uninit_dll()
{
   qApp->quit();
   DLLWrapper::uninit();
   delete qApp;
}
 
 

А создавать QApplication и GUI в потоке плохая идея. По крайней мере у меня ничего не получилось.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #3 : Апрель 07, 2014, 20:29 »

Цитата: Bepec
Эммм. По моей статейке делали тред внутри?


Продублирую тут похожие посты:

Создание dll с сигнал-слотами внутри для NoQt приложений.
Dll на Qt + QTcpSocket для вызова не в Qt-приложении

Да, сделал примерно то-же самое что и ты, только использовал QThread вместо ::CreateTread(), вроде оно работает и так (хотя, ты упоминал что не должно Улыбающийся ) Но мне так не нравится, т.к. нужно заморачиваться с критическими секциями и прочей синхронизацией..

Глянул еще на твой код отсюда:

Код
C++ (Qt)
DWORD  MyThreadFunction( LPVOID lpParam )
{
QCoreApplication * app = NULL;
int argc = 0;
app = new QCoreApplication(argc, NULL);
QLibrary * dllClass =  new QLibrary();
  app->exec();
return DWORD();
}
 

и вижу, что ты делаешь:
Код
C++ (Qt)
QLibrary * dllClass =  new QLibrary();
 

зачем?
Записан

ArchLinux x86_64 / Win10 64 bit
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #4 : Апрель 07, 2014, 20:41 »

Цитата: ck
Тоже выдирал из  QtWinMigrate.

Да, я аналогичное пробовал, но не понял, как использовать.. Улыбающийся

Например, (грубо) реализовал я init()/deinit() с хуком и имею единственную паблик ф-ю DllGetClassObject (подставь любое имя Улыбающийся ) из dll-ки:

Код
C++ (Qt)
HRESULT __stdcall DllGetClassObject(
 _In_   REFCLSID rclsid,
 _In_   REFIID riid,
 _Out_  LPVOID *ppv
);
 

в которой нужно создавать не виджеты, а обычные QObject-ы..

Так вот, вопрос: как это сделать чтобы цикл заработал?

Нужно что-то вроде этого:
Код
C++ (Qt)
 
class TimerHandler : public QObject
{
   Q_OBJECT
public:
   explicit TimerHandler(QObject *parent = 0) {}
public slots:
   void handleTimeout() {
       static int counter = 0;
       ++counter ;
   }
}
 
void foo() // или DllGetClassObject или любая другая ф-я
{
   QTimer *timer = new QTimer();
   QObject *obj = new TimerHandler();
   QObject::connect(timer, SIGNAL(timeout()), obj, SLOT(handleTimeout)));
   timer->start(1000);
}
 


Т.е., мне не нужно экспортировать создаваемые Qt-шные объекты во вне dll-ки, нужно просто заставить крутиться луп внутри..
« Последнее редактирование: Апрель 07, 2014, 20:46 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
Bepec
Гость
« Ответ #5 : Апрель 07, 2014, 21:26 »

1) Я делаю new для того, чтобы созданную мной переменную впоследствии можно было бы удалить.

Но не пойму проблемы. Цикл в моём примере работает 100%. Он до сих пор на одной машине крутится.
 
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #6 : Апрель 07, 2014, 21:46 »

Цитировать
1) Я делаю new для того, чтобы созданную мной переменную впоследствии можно было бы удалить.

Не понял, о какой переменной речь? Меня смущает создание QLibrary из library... Это просто для примера и можно заменить на QObject, или в вызове new QLibrary есть сакральный смысл? Улыбающийся

Цитировать
Но не пойму проблемы. Цикл в моём примере работает 100%. Он до сих пор на одной машине крутится.

Да, работать оно будет, но много проблем с синхронизацией и пр.. хотелось бы без лишних тредов использовать..
« Последнее редактирование: Апрель 07, 2014, 21:49 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #7 : Апрель 07, 2014, 21:56 »

UPD: Попробовал для Qt5 собрать этот пример из солюшенов:

https://qt.gitorious.org/qt-solutions/qt-solutions/source/fd22bee22274975c56f1c10d87ee9fd2c0818f83:qtwinmigrate/examples/qtdll

но при вызове ф-ции
Код
C++ (Qt)
bool showDialog( HWND parent )
 

из dll-ки, оно крешится (все зависимости по Qt-шным и прочим рантайм dll-кам удовлетворил)..
Правда, я вызываю ее с нулевым HWND parent - может в этом проблема... Улыбающийся

Цитировать
e:\git\qtwinmigrate\examples\build-qtdllapp-Desktop_Qt_5_2_1_MinGW_4_8_32bit-Debug\debug>qtdllapp.exe
QWidget: Must construct a QApplication before a QWidget

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

также смущает: "Must construct a QApplication before a QWidget".. Как так? Ведь QApplication уже должно быть создано первым после вызова LoadLibrary()..


« Последнее редактирование: Апрель 07, 2014, 21:59 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
Bepec
Гость
« Ответ #8 : Апрель 07, 2014, 22:05 »

Да, чуть невнятно.

dllClass это любой класс, который будет находиться в dll Улыбающийся
Сакральный смысл в областях видимости.

При выходе из функции локальный объект удалится. Создание объекта с New даёт ему возможность жить бессрочно в потоке с активным евент лупом.

Насчёт синхронизации, тут у меня пробелы с теорией. Сия проблема мне не ясна. Разбалован я сигнально-слотовой системой.

Все доступные на момент написания моей темы статьи и примеры отказывались работать, или работали через раз. Мигрейт помоему не запускал цикл и ругался подобным способом.

PS Проще выражаясь, QWidget был создан в потоке, в котором не запущен/не существует QApplication Улыбающийся Это и было моей основной проблемой, сподвигнувший на изыскания Улыбающийся

PPS создан то он создан, но вот виджет к нему доступ не может получить.

PPPS если тестовый примерчик сделаете с нужным вам функционалом, я его и свои изыскания попытаюсь совместить.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #9 : Апрель 07, 2014, 22:19 »

Накопал кое-что тут: http://stackoverflow.com/questions/2150488/using-a-qt-based-dll-in-a-non-qt-application

суть - не использовать всякие треды, а просто вызывать qApp->processEvents() в каллбеке от WinAPI-шного таймера (как я и хотел ранее сделать)..
завтра попробую это.. Улыбающийся
Записан

ArchLinux x86_64 / Win10 64 bit
Bepec
Гость
« Ответ #10 : Апрель 07, 2014, 23:08 »

В путь, если получится будет прикольно Улыбающийся
Записан
vregess
Гость
« Ответ #11 : Апрель 08, 2014, 07:18 »

Да, я аналогичное пробовал, но не понял, как использовать.. Улыбающийся

Не совсем понял, оно уже должно работать...
Код
C++ (Qt)
void foo()
{
   DLLWrapper::init();
   QTimer *timer = new QTimer();
   QObject *obj = new TimerHandler();
   QObject::connect(timer, SIGNAL(timeout()), obj, SLOT(handleTimeout)));
   timer->start(1000);
}
 

суть - не использовать всякие треды, а просто вызывать qApp->processEvents() в каллбеке от WinAPI-шного таймера (как я и хотел ранее сделать)..
Так я это и делаю, разве не так? Ну почти. У меня хук не на события таймера, а на все события очереди.

Хочу сказать, DLLWrapper работает в одном проекте, полет нормальный. Есть виндовая прога, к ней подключаются плагины. Вот один из плагинов написан на qt4. DLLWrapper ставит хук на очередь сообщений главного потока хост-программы, и там обрабатываются евенты Qt. Проверить это просто. Выполнить долгую операцию в каком-нить обработчике, например в слоте. В это время хост-программа перестанет обрабатывать свои события.

Поэтому
В путь, если получится будет прикольно Улыбающийся
уже прикольно, тк получилось.

А все эти заигрывания с потоками до добра не доведут)
Записан
Bepec
Гость
« Ответ #12 : Апрель 08, 2014, 11:04 »

Собственно нужна очередь сообщений окна. А для консольных приложений как делать?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #13 : Апрель 08, 2014, 11:19 »

Прошлой осенью "инжектировал" Qt окна в не-Qt приложение. QApplication создал прямо в главной нитке, сначала поставил нативные обработчики и, если событие не обработано, то автоматом вызываются обработчики Qt. Да, работает, но это коряво, все время приходится латать
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #14 : Апрель 08, 2014, 13:19 »

В общем, вроде заработало в методе с таймером (у меня на примере моего проекта с dll-кой COM интерфейсов):

dll.h

Код
C++ (Qt)
#ifndef DLL_H
#define DLL_H
 
#include <windows.h>
 
class Dll
{
public:
   static BOOL Main(HINSTANCE dllInstance, DWORD reason, LPVOID reserved);
 
   static HRESULT GetClassObject(REFCLSID classId, REFIID interfaceId, PVOID *object); // это, наверное, можно заменить на foo() и прочее.
public:
   static HINSTANCE instance;
 
private:
   static bool attachQtApplication();
   static bool detachQtApplication();
   static VOID CALLBACK qtEventsTimerProc(HWND hwnd, UINT msg, UINT_PTR eventId, DWORD time);
 
   static UINT_PTR m_timerIdentifier;
   static UINT m_timerInterval;
};
 
#endif // DLL_H
 
 

dll.cpp

Код
C++ (Qt)
#include "dll.h"
 
#include <QCoreApplication>
 
HINSTANCE Dll::instance = NULL;
 
UINT_PTR Dll::m_timerIdentifier = 0;
UINT Dll::m_timerInterval = 1; // every 1 msec
 
BOOL Dll::Main(HINSTANCE dllInstance, DWORD reason, LPVOID reserved)
{
   UNREFERENCED_PARAMETER(reserved);
 
   Dll::instance = dllInstance;
 
   switch (reason) {
   case DLL_PROCESS_DETACH:
       Dll::detachQtApplication();
       break;
   case DLL_PROCESS_ATTACH:
       ::DisableThreadLibraryCalls(dllInstance);
       Dll::attachQtApplication();
       break;
   case DLL_THREAD_ATTACH:
       break;
   case DLL_THREAD_DETACH:
       break;
   default:
       break;
   }
 
   return TRUE;
}
 
HRESULT Dll::GetClassObject(REFCLSID classId, REFIID interfaceId, PVOID *object)
{
   // Здесь, собственно (внутри MyFactory и прочих кишках), я могу уже создавать QObject-ы, коннектиться к сигналам/слотам и прочее..
   // тут ваш код должен быть..
   return MyFactory::Create(classId, interfaceId, object);
}
 
bool Dll::attachQtApplication()
{
   if (!qApp) {
       int argc = 1;
       QCoreApplication*app = new QCoreApplication(argc, NULL);
       Q_UNUSED(app);
 
       if (!Dll::m_timerIdentifier) {
            Dll::m_timerIdentifier =
                    ::SetTimer(NULL, 0, Dll::m_timerInterval, Dll::qtEventsTimerProc);
            if (!Dll::m_timerIdentifier)
                return false;
       }
   }
   return true;
}
 
bool Dll::detachQtApplication()
{
   if (Dll::m_timerIdentifier) {
       ::KillTimer(NULL, Dll::m_timerIdentifier);
       Dll::m_timerIdentifier = 0;
   }
 
   if (qApp)
       delete qApp;
 
   return true;
}
 
VOID Dll::qtEventsTimerProc(HWND hwnd, UINT msg, UINT_PTR eventId, DWORD time)
{
   Q_UNUSED(hwnd);
   Q_UNUSED(msg);
   Q_UNUSED(eventId);
   Q_UNUSED(time);
 
   if (qApp)
       qApp->processEvents();
}
 
//
// Native calls
//
 
STDAPI DllGetClassObject(REFCLSID classId, REFIID interfaceId, PVOID *object)
{
   return Dll::GetClassObject(classId, interfaceId, object);
}
 
STDAPI_(BOOL) DllMain(HINSTANCE dllInstance, DWORD reason, LPVOID reserved)
{
   return Dll::Main(dllInstance, reason, reserved);
}
 
 

Ранее я пробовал вариант с хуками, и, кажется, они не работали.. Хотя, может я накосячил где-то там... Улыбающийся
Записан

ArchLinux x86_64 / Win10 64 bit
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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