簡體   English   中英

加載Qt共享庫時未顯示Qt小部件

[英]Qt widgets not show up when Qt shared lib loaded

要求:對於非Qt應用程序,當Qt共享庫加載時 ,將顯示Qt小部件。

經過一些網絡搜索,我發現:

  1. 所有Qt小部件都必須位於“主線程”中,“主線程”是第一個Qt對象創建的線程。 因此,請創建一個無Qt線程(std :: thread),然后在該線程中創建QApplication和其他一些小部件,但不能使用。

  2. 在該非Qt線程中,不要在創建QApplication之前創建任何與Qt相關的對象或調用任何與Qt相關的靜態方法。

  3. 線程解決方案不適用於Mac OS,我的目標平台僅是Windows,因此沒有關系。

  4. 就我而言,如果應用程序加載了我的Qt庫,並調用了用於顯示小部件的方法,則它可以工作。 但是由於某種原因,調用者無法手動調用我的lib方法。

  5. 如果主機應用程序(加載共享庫的主機應用程序)是Qt應用程序,則應調用QApplication :: processEvents()而不是QApplication :: exec()。 就我而言,我應該在該線程中調用QApplication :: exec()。

源代碼在這里:

  • dll主要版本
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加載庫進行測試的方法,但是,如果我使用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!";
    }
}

這是一個工作示例。 已通過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入口點中可以安全進行的操作有很多限制。 有關在DllMain中調用不安全的特定Windows API的信息,請參見常規最佳實踐。 如果除了最簡單的初始化之外還需要其他任何東西,請在DLL的初始化函數中執行此操作。 您可以要求應用程序在DllMain運行之后並且在調用DLL中的任何其他函數之前調用初始化函數。

-https://docs.microsoft.com/zh-cn/windows/desktop/dlls/dllmain

和動態鏈接庫最佳做法:

您永遠不要在DllMain中執行以下任務:

  • 調用LoadLibrary或LoadLibraryEx(直接或間接)。 這可能導致死鎖或崩潰。
  • 調用GetStringTypeA,GetStringTypeEx或GetStringTypeW(直接或間接)。 這可能導致死鎖或崩潰。
  • 與其他線程同步。 這可能導致死鎖。
  • 獲取由等待獲取加載程序鎖的代碼所擁有的同步對象。 這可能導致死鎖。
  • 通過使用CoInitializeEx初始化COM線程。 在某些情況下,此函數可以調用LoadLibraryEx。
  • 調用注冊表函數。 這些功能在Advapi32.dll中實現。 如果在您的DLL之前未初始化Advapi32.dll,則DLL可以訪問未初始化的內存,並導致進程崩潰。
  • 調用CreateProcess。 創建一個進程可以加載另一個DLL。
  • 調用ExitThread。 在DLL分離期間退出線程可能導致再次獲取加載程序鎖,從而導致死鎖或崩潰。
  • 調用CreateThread。 如果您不與其他線程同步,則可以創建線程,但是這樣做很冒險。
  • 創建一個命名管道或其他命名對象(僅Windows 2000)。 在Windows 2000中,終端服務DLL提供了命名對象。 如果未初始化此DLL,則對該DLL的調用可能導致進程崩潰。
  • 使用動態C運行時(CRT)中的內存管理功能。 如果未初始化CRT DLL,則對這些函數的調用會導致進程崩潰。
  • 調用User32.dll或Gdi32.dll中的函數。 某些函數會加載可能未初始化的另一個DLL。
  • 使用托管代碼。

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

由此,我可以告訴您,至少由於以下原因,您將無法從DllMain創建QApplication並運行Qt應用程序:

  • Qt將使用LoadLibrary加載插件(至少qwindows.dll )。 如果您使用任何音頻或圖像或sql數據庫,Qt還將嘗試加載相應的插件(例如qjpeg.dll )。
  • Qt可能還會嘗試訪問注冊表,特別是如果您使用本機格式的QSettings
  • Qt可能會創建線程。 特別是如果您使用網絡或Qt Quick。
  • Qt將使用mallocfree類的內存管理功能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM