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

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

Страниц: [1]   Вниз
  Печать  
Автор Тема: [SOLVED] Подключение dll на Qt к приложению на С  (Прочитано 16164 раз)
Gorthaur
Гость
« : Ноябрь 29, 2005, 17:47 »

Вопрос вот в чем:
Есть виндозное консольное приложение. И есть dll, написаная на Qt. В длл реализуется наследник QServerSocket, через который консольное приложение производит обмен данными по сетке.

Код:

#include <windows.h>
#include <iostream>
using namespace std;

int main(int argc, char ** argv)
{
    // loading dll manualy
    HMODULE hModule = LoadLibrary("fwdll.dll");
   
    // Importing DLLInit() function from DLL
    typedef void (*PDLLInit)();
    PDLLInit pDllInit = (PDLLInit)GetProcAddress(hModule, "DLLInit");
   
    pDllInit();
 
 /* .... Что-то делаем .... */

    BOOL b = FreeLibrary(hModule);
    return 0;
}


pDllInit() - функция, импортируемая из длл. Её роль - инициализация длл. В даном варианте сильно упрощена:
Код:

extern "C" __declspec(dllexport) void DLLInit()
{
    // FWDLLServer - наследник QServerSocket
    FWDLLServer* DLLserver = new FWDLLServer();
}


И все бы хорошо, но проблема в том, что в длл присутствуют сигналы-слоты и для их обработки нужно стартовать event loop... Если переделать функцию инициализации:
Код:

// решение в лоб
extern "C" __declspec(dllexport) void DLLInit()
{
    int argcc = 0;
    QApplication* app = new QApplication(argcc, 0, FALSE);

    FWDLLServer* DLLServer = new FWDLLServer();
   
    app.exec();
}

то все прекрасно работает, сервер подхватывает сетевые соединения клиентов но естественно до завершения app.exec() управление в main() не возвращается (тоесть фактически никогда не возвращается).
А нет ли способа запустить event loop "внутри" dll и вернуться в main для дальнейшей работы? (Напомню что в main() стартануть event loop нет возможности поскольку он не использует qt)
 Если есть какие-нибудь идеи - поделитесь плз...

Заранее thanks.
Записан
Dendy
Гость
« Ответ #1 : Ноябрь 29, 2005, 21:43 »

Есть такой способ!  Веселый

Для работы событийного механизма и всего прочего, что завязано на главном цикле собственно сам главный цикл и не нужен.

Итак, в dll полюбому нужно создать класс приложения, который будет жить пока dll висит в памяти:

Код:
FWDLLServer * DLLServer = 0;

extern "C" __declspec(dllexport) void DLLInit()
{
    int argcc = 0;
    new QApplication(argcc, 0, FALSE); // глобальный указатель хранится в qApp

    DLLServer = new FWDLLServer();
}


Обязательно нужно прибить все данные, что насоздавала либа, поетому не обойдёмся без:

Код:
extern "C" __declspec(dllexport) void DLLDestroy() 
{
    delete DLLServer;
    delete qApp;
}


А вот события будут передаваться когда ручками буим проталкивать пришедшие события:

Код:
extern "C" __declspec(dllexport) void DLLProcessEvents() 
{
    qApp->processEvents();
}


Гатова! Осталось периодически вызывать DLLProcessEvents() из Си-шной проги  :wink:
Записан
Gorthaur
Гость
« Ответ #2 : Ноябрь 29, 2005, 21:49 »

Проблема, кажется, нашла свое решение... В Qt Solutions присутствует замечательная фича под названием QMfcApp... В принципе можно обойтись и без нее
Код:

BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpvReserved )
{
    static bool ownApplication = FALSE;
    if ( dwReason == DLL_PROCESS_ATTACH )
        ownApplication = QMfcApp::pluginInstance( hInstance );
    if ( dwReason == DLL_PROCESS_DETACH && ownApplication )
        delete qApp;

    return TRUE;
}

 
Воспользовавшись идеями из этого полезного решения можно написать свой DllMain в длл...
Код:

QApplication *pApp;

bool APIENTRY DllMain( HANDLE hModule,
DWORD  ul_reason_for_call, LPVOID lpReserved)
{
 int argc = 0;    
 
switch (ul_reason_for_call){
 case DLL_PROCESS_ATTACH:
        pApp = new QApplication(argc, 0);
        break;

case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
        break;
case DLL_PROCESS_DETACH:
        delete pApp;
        break;    
 }    
return TRUE;
}

Сие позволяет иметь Qt event loop в длл отдельно от main event loop приложения (наск я понял...)

Единственная проблема - при компиляции выскакивает ошибка
Цитировать

moc_fwdll.obj : error LNK2005: _DllMain@12 already defined in fwdll.obj

Гугл показал что даная ошибка - обычное дело при создании dll со своим DllMain в майкрософтовских IDE и касается не только qt проектов. Причем фиксится она только при помощи шаманского бубна... (У некоторых она возникает только если закрыть Visual Studio а потом снова зайти и сделать полный Rebuild проекта). Если кто сталкивался с подобной багой - помогите пофиксить плз!

[/quote]
Записан
Gorthaur
Гость
« Ответ #3 : Ноябрь 29, 2005, 21:52 »

Dendy спасибо за ответ!
К сожалению твой вариант потестить сегодня не успеваю. Завтра обязательно кину результат.

regards
Записан
Gorthaur
Гость
« Ответ #4 : Ноябрь 30, 2005, 14:25 »

Цитата: "Dendy"

Осталось периодически вызывать DLLProcessEvents() из Си-шной проги  :wink:

Да решение работает! Но проблема при таком подходе полностью не исчезает  Грустный  Если сделать так:
Код:

//main.cpp
int main(int argc, char ** argv)
{
//...
DLLInit();

    while(!exitCondition)
    {
        // ....
        pDllProcessEvents();
        Sleep(1);
     }
// ...
}


То управление хоть и вернется в мейн но паралельных event loops для main и dll не получится Грустный Дело в том, что для уведомления сишной части я dll-ке при ее инициализации передаю указатели на callback функции, которые собственно и уведомляют потом сишную часть о новых подключениях, прибытии/отправке данных и т.д. Поэтому сами события внутри dll для сишной части (блин запарил уже этот "термин" Улыбающийся ) не особо интересны. Тут скорее вопрос стоит так - как бы запустить для dll отдельный поток со своим event loop...
Чесно говоря под windows до этого мало приходилось писать... Видимо придется покупать Рихтера... Грустный
Записан
Dendy
Гость
« Ответ #5 : Ноябрь 30, 2005, 15:42 »

Дик, друже! Не розумію у чому проблема. Мережева частина у Qt зроблена таким чином, що не потребує зайвих асінхроних потоків. Навпаки - ти тільки погіршиш архітектуру та наробиш помилок, якщо заюзаєш зайвий потік.

Тож все просто: робиш у DLL якийсь екземпляр класу, що буде приймати сигнали від сокетів та смикати callback-функції з якимись параметрами.
Записан
Gorthaur
Гость
« Ответ #6 : Ноябрь 30, 2005, 16:34 »

Цитата: "Dendy"
Дик, друже! Не розумію у чому проблема. Мережева частина у Qt зроблена таким чином, що не потребує зайвих асінхроних потоків. Навпаки - ти тільки погіршиш архітектуру та наробиш помилок, якщо заюзаєш зайвий потік.

Тож все просто: робиш у DLL якийсь екземпляр класу, що буде приймати сигнали від сокетів та смикати callback-функції з якимись параметрами.


Я так и делаю. Ты наверное неправильно меня понял насчет потока...  Смотри:
например мне на сокет приходят новые данные - откуда я знаю что они пришли? Я об этом знаю потому что происходит автоматически emit  readyRead(), который я перехватываю например так
Код:

connect( s, SIGNAL(readyRead()), this, SLOT(readClient()) );

А все эти дела с событиями-обработчиками в qt диспетчерит QApplication. Если бы этот код выполнялся в обычном exe, я бы просто добавил return app.exec() в main() и все бы работало на ура. Но мой код должен выполняться в dll, где я не могу создать int main() { ... return app.exec(); }, и выполняться этот код должен независимо (т.е все части приложения работают паралельно, обмениваясь информацией через callback функции, а не ждут завершения друг друга для продолжения работы).  Твой пример как раз показывает это - для того, чтобы обрабатывались собития в длл, основной поток приложения (в данном случае main()) должен явно вызывать функцию из длл. Тоесть тот же обычный main qt-шного приложения, вынесеный в сишную прогу а не два паралельно выполняющихся "приложения" как хотелось бы...  Мне кажется что если я в DllMain при DLL_PROCESS_ATTACH создам отдельный поток, в который и помещу экземпляр моего сервера - то все должно получиться. Основной поток при загрузке либы вызовет DllMain, и вернется к выполнению своих задач,  после чего получится то что мне нужно - 2 независимых потока - в одном С в другом Qt... Улыбающийся
Осталось только разобраться как поредачить Makefile чтобы исчезла ошибка дублирования DllMain и протестировать все на практике Грустный Никак не пойму в чем именно проблема у линкера...
Записан
Dendy
Гость
« Ответ #7 : Ноябрь 30, 2005, 16:54 »

Якщо ти вважаєш, що ввімкнення циклу подій розв'яже та полегшить твою задачу, то трохи помиляєся. Головна програма зі своїм циклом однаково буде у С-програмі. CALLBACK функції з іншого потока, створенного у DLL викликатися не будуть у головному потоці! Вони будуть викликатися з потоку Qt, що призведе до конфліктів з данними. Бо ті самі дані у той самий час юзаю головний поток С. Фактично ці CALLBACK функції повинні викликатися асінхронно між ітераціями головного циклу С-програми, у потоці С-програми. Щоб цього добитися треба буде обв'язатися мутексами, але це однаково призведе до архітектури сінхронної однопоточної програми. Тож якщо Qt-потік не повинен дані оброблювати, то він нах. не потрібен. Він заважає та робить архітектуру складніше заради... заради нічого  Улыбающийся

Твою С-програму я бачу так:

Код:
void connectionCallback( int client );
void readDataCallback( int client, const char * data, int dataSize );
...
int main( int argc, char ** argv )
{
...
  while ( not_end_of_cycle )
  {
    qtDllProcessEvents(); // тут смикаються калбеки
...
    // оброблюємо дані та все таке
  }
...
}


Сінхрона прога має виглядати саме так. Так чи інакше ти прийдеш саме до цього варіанту  :wink:
Записан
Gorthaur
Гость
« Ответ #8 : Ноябрь 30, 2005, 19:55 »

Да, ты 100 раз прав что с синхронной прогой намного легче работать и меньше гемороя, но к сожалению реальная ситуация такова, что код сишной проги у меня практически нет возможности модифицировать, кроме того не до конца ясна ее архитектура и выяснить ее тоже нет особой возможности (разве что дезасемблированием сопутствующих длл)... Известен только публичный интерфейс и набор каллбеков которые можно использовать. Фактически сетевая часть лепится сбоку и потому вынуждена на себя брать кучу несвойственных функций. К примеру приложение часто выполняет длительные операции (например дефрагментацию FS) и может быть остановлена по команде "Стоп"... Я могу только предполагать как она эту команду в процессе выполнения  дефрага получит - возможно она разбита на итерации в конце которых идет проверка нажат ли стоп (дёрнулся ли каллбек отвечающий за останов). И если принять теперь вариант с синхронной работой длл, то эти предполагаемые итерации в принципе можно было бы дополнить вызовом qtDllProcessEvents() чтобы дать возможность каллбекам сработать (причем опытным путем выяснилось, что processEvents() нужно вызвать несколько раз подряд иначе вся цепочка взаимозависимых событий\слотов не успевает сработать и длл не дергает за каллбек), но сделать это не представляется возможным в виду вышеизложеного... Да и с точки зрения красивой архитектуры неизвестно что лучше в этом случае (да и вообще тут уже о красотах и речи нет... Достаточно бы было кривой, но работоспособности) Улыбающийся  В общем с потоками скорее всего мучиться придется все равно...  Или забить на qt в этом проэкте. Грустный
Записан
Zmey
Гость
« Ответ #9 : Декабрь 01, 2005, 09:54 »

Имейте совесть, пишите на русском языке. Я же не пишу на белорусском, а кто-то на казахском, кто-то на татарском?
Записан
Gorthaur
Гость
« Ответ #10 : Декабрь 01, 2005, 17:44 »

В общем после того как я разобрался с ошибкой изза DllMain (проблема была в том, что я совместил объявление и реализацию этой функции в хидере а не разнес их в разные файлы - это не понравилось кьютешному moc ), и создал отдельной поток, в котором создается мой DLLServer и запускается событийный цикл qt ( qApp->exec(); )- все получилось как я хотел. Обмен инфой происходит через глобальные указатели на каллбек функции, которые мне присылает екзешник при инициализации длл. По видимому мне очень повезло и все вопросы с правильными блокировками внутренних вызовов в exe решает сам exe... Кьютешной же либе остается только вовремя дергать за нужный каллбек.
Записан
Dendy
Гость
« Ответ #11 : Декабрь 01, 2005, 19:19 »

Ухх... Дивись щоб програма не завалилася на рівному місці!  Веселый  Ти хочаб організував безпечний доступ к даним з різних потоків?
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


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