Виджеты Qt не отображаются, когда загружена общая библиотека Qt - PullRequest
0 голосов
/ 13 февраля 2019

Требования: виджеты Qt появляются, когда Qt разделяет lib загружает , для none-Qt приложения.

После некоторого поиска в Интернете я обнаружил:

  1. Все виджеты Qt должны находиться в "главном потоке", "основной поток" является первым потоком, созданным объектом Qt.поэтому создайте поток без Qt (std :: thread), затем создайте QApplication, и некоторые другие виджеты в этом потоке должны работать, но не.

  2. Не создавать никаких связанных с Qtобъект или вызов любых статических методов, связанных с Qt, до создания QApplication в этом потоке без Qt.

  3. Потоковое решение не переносимо для Mac OS, моей целевой платформой является только Windows, поэтомуэто не имеет значения.

  4. В моем случае, если приложение загружает мою библиотеку Qt и вызывает метод для отображения виджетов, это работает.но по какой-то причине вызывающая сторона не может вызвать мой метод lib вручную.

  5. Если хост-приложение (которое загружает разделяемую lib) является приложением Qt, вы должны вызвать QApplication :: processEvents (), а не QApplication :: exec ().в моем случае я должен вызвать QApplication :: exec () в этом потоке.

Исходный код здесь:

  • dllMain версия :
BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        auto t = std::thread([]() {
            // setCodecForLocale is in the same thread, 
            // call it before QApplication created should be OK.

            QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
            int i = 0;
            int argc = 0;
            QApplication app(argc, 0);

            auto dialogLogin = new DialogLogin(); // custom widget

            dialogLogin->setModal(true);
            dialogLogin->show();
            app.exec(); // app.processEvents() not work, too.
        });

        t.join(); // wait for thread ends in dllMain should be BAD, test only
    }

    return true;
}
  • Версия статического класса C ++
class LibExecutor {
public:
    LibExecutor()
    {
        auto t = std::thread([]() {
            QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
            int argc = 0;
            QApplication app(argc, 0);

            auto dialogLogin = new DialogLogin();
            dialogLogin->setModal(true);
            dialogLogin->show();
            app.exec();
        });

        t.join();
    }
};

static LibExecutor libExecutor;

Обе версии успешно вызывают элементы инициализации виджетов, но виджеты нетобъявиться.

Вот как я тестирую его, используя Qt load lib, но событие, при котором я загружаю lib с помощью Win32 API, тоже не удалось.

#include "mainwindow.h"
#include <QApplication>
#include <QLibrary>

int main(int argc, char* argv[])
{
    QLibrary lib("F:/lib_location/lib_name.dll");

    if (lib.load()) {
        qDebug() << "load ok!";
    } else {
        qDebug() << "load error!";
    }
}

1 Ответ

0 голосов
/ 22 февраля 2019

Вот рабочий пример.Протестировано с Qt 5.12, MSVC2017 и MinGW.

// main.cpp
int main(int argc, char *argv[])
{
    run_mylib_t *f= nullptr;
    HMODULE lib = LoadLibraryA("..\\mylib\\debug\\mylib.dll");

    if (!lib) {
        qDebug() << "Failed to load library;";
        return -1;
    }

    f = reinterpret_cast<run_mylib_t *>(GetProcAddress(lib, "run_mylib"));

    if (!f) {
        qDebug() << "Failed to get function";
        return -1;
    }

    f(argc, argv);      

    return 0;
}

// mylib.h
extern "C" MYLIBSHARED_EXPORT int run_mylib(int argc, char *argv[]);
using run_mylib_t = int(int, char *[]);

// mylib.cpp
int loop(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

int run_mylib(int argc, char *argv[])
{
    auto lambda = [argc, argv]() {loop(argc, argv); };

    std::thread thread(lambda);
    thread.join();

    return 0;
}

Обратите внимание, что если вы используете функцию Qt до создания потока, Qt обнаружит, что его нет в основном потоке, и процесс завершится сбоем.Вот почему я не использую QLibrary.

Этот вариант использования не поддерживается Qt.Поэтому, если вы заставите его работать сейчас, вы не гарантируете, что он будет работать в будущем.

Вы не можете загружать одновременно 2 dll, как это.

В зависимости от того, что вы делаете вВаше основное приложение, может случиться так, что некоторые функции Qt не работают должным образом.Например, может случиться, что Qt ожидает сообщения от Windows, но никогда не получит их, потому что они будут обрабатываться реальным основным потоком.

О DllMain

Из документации Windows:

Предупреждение

Существуют значительные ограничения на то, что вы можете безопасно делать в точке входа DLL.См. Общие рекомендации для конкретных API-интерфейсов Windows, которые небезопасны для вызова в DllMain.Если вам нужно что-то кроме самой простой инициализации, сделайте это в функции инициализации для DLL.Вы можете требовать, чтобы приложения вызывали функцию инициализации после запуска DllMain и до того, как они вызовут любые другие функции в DLL.

- https://docs.microsoft.com/en-us/windows/desktop/dlls/dllmain

и Dynamic-Link Library BestПрактика:

Никогда не выполняйте следующие задачи из DllMain:

  • Вызовите LoadLibrary или LoadLibraryEx (прямо или косвенно).Это может вызвать тупик или сбой.
  • Вызовите GetStringTypeA, GetStringTypeEx или GetStringTypeW (прямо или косвенно).Это может вызвать тупик или сбой.
  • Синхронизация с другими потоками.Это может вызвать взаимоблокировку.
  • Получить объект синхронизации, принадлежащий коду, который ожидает получения блокировки загрузчика.Это может вызвать взаимоблокировку.
  • Инициализировать потоки COM с помощью CoInitializeEx.При определенных условиях эта функция может вызывать LoadLibraryEx.
  • Вызывать функции реестра.Эти функции реализованы в Advapi32.dll.Если Advapi32.dll не инициализируется до вашей DLL, DLL может получить доступ к неинициализированной памяти и вызвать сбой процесса.
  • Вызовите CreateProcess.Создание процесса может загрузить другую DLL.
  • Вызов ExitThread.Выход из потока во время отсоединения DLL может привести к повторному получению блокировки загрузчика, вызывая тупик или сбой.
  • Вызов CreateThread.Создание потока может работать, если вы не синхронизируете его с другими потоками, но это рискованно.
  • Создание именованного канала или другого именованного объекта (только в Windows 2000).В Windows 2000 именованные объекты предоставляются библиотекой служб терминалов.Если эта DLL не инициализирована, вызовы DLL могут вызвать сбой процесса.
  • Используйте функцию управления памятью из динамического C-Run-Time (CRT).Если библиотека CRT не инициализирована, вызов этих функций может привести к сбою процесса.
  • Вызов функций в User32.dll или Gdi32.dll.Некоторые функции загружают другую DLL, которая не может быть инициализирована.
  • Использовать управляемый код.

- https://docs.microsoft.com/en-us/windows/desktop/dlls/dynamic-link-library-best-practices

Из этого я могу сказать,Если вы не сможете создать QApplication и запустить приложение Qt из DllMain по крайней мере по следующим причинам:

  • Qt будет загружать плагины (по крайней мере qwindows.dll) используя LoadLibrary.Если вы используете какую-либо базу данных аудио или изображений или sql, Qt также попытается загрузить соответствующие плагины (например, qjpeg.dll).
  • Qt также может попытаться получить доступ к реестру, в частности, если вы используете QSettingsс собственным форматом.
  • Qt может создавать потоки.В частности, если вы используете сеть или Qt Quick.
  • Qt будет использовать функции управления памятью, такие как malloc или free.
...