【问题标题】:Qt widgets not show up when Qt shared lib loaded加载 Qt 共享库时不显示 Qt 小部件
【发布时间】:2019-07-07 03:06:00
【问题描述】:

要求:none-Qt 应用程序在 Qt 共享库 加载 时显示 Qt 小部件。

经过一番网络搜索,我发现:

  1. 所有 Qt 小部件都必须存在于“主线程”中,“主线程”是 Qt 对象创建的第一个线程。所以,创建一个非 Qt 线程(std::thread),然后,创建 QApplication 和该线程中的其他一些小部件应该可以工作,但不能。

  2. 在创建 QApplication 之前,不要在非 Qt 线程中创建任何 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!";
    }
}

【问题讨论】:

  • 新线程好像没有运行,你看看:stackoverflow.com/questions/54702090/…
  • 好像我不能在静态变量初始化块中创建线程,因为这部分是在dllMain函数中运行的,在dllMain中创建线程会导致死锁,但是,有什么办法可以打破这个规则?
  • 不要在 DllMain 中创建线程 - 请参阅 MSDN
  • @DDrmmr 感谢您的回复 :) 是的,在“dllMain”中创建线程是个坏主意,它会导致我的应用程序阻塞。我在 dllMain 中使用线程找到了其他人的演示,效果很好。不是在 C++ 中,而是在 Delphi 中。睡眠 1 秒,UI 的东西开始,但在 C++ 中,失败:(
  • @vg0x00 我不明白你的第 4 点。你能解释一下吗?

标签: c++ qt qt5 dllmain


【解决方案1】:

这是一个工作示例。使用 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 条目中安全地执行的操作有很大的限制 观点。请参阅适用于特定 Windows API 的一般最佳实践 调用 DllMain 不安全。如果你需要什么,但最简单的 然后在 DLL 的初始化函数中进行初始化。 您可以要求应用程序在之后调用初始化函数 DllMain 已经运行并且在它们调用 DLL 中的任何其他函数之前。

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

和动态链接库最佳实践:

您不应该在 DllMain 中执行以下任务:

  • 调用 LoadLibrary 或 LoadLibraryEx(直接或间接)。这可能会导致死锁或崩溃。
  • 调用 GetStringTypeA、GetStringTypeEx 或 GetStringTypeW(直接或间接)。这可能会导致死锁或崩溃。
  • 与其他线程同步。这可能会导致死锁。
  • 获取由等待获取加载程序锁的代码拥有的同步对象。这可能会导致死锁。
  • 使用 CoInitializeEx 初始化 COM 线程。在某些情况下,此函数可以调用 LoadLibraryEx。
  • 调用注册表函数。这些函数在 Advapi32.dll 中实现。如果 Advapi32.dll 未在您的 DLL 之前初始化,则 DLL 可以访问未初始化的内存并导致进程崩溃。
  • 调用 CreateProcess。创建一个进程可以加载另一个 DLL。
  • 调用退出线程。在 DLL 分离期间退出线程可能会导致再次获取加载程序锁,从而导致死锁或崩溃。
  • 调用 CreateThread。如果不与其他线程同步,创建线程可以工作,但有风险。
  • 创建命名管道或其他命名对象(仅限 Windows 2000)。在 Windows 2000 中,命名对象由终端服务 DLL 提供。 如果此 DLL 未初始化,则对该 DLL 的调用可能会导致进程 崩溃。
  • 使用动态 C 运行时 (CRT) 中的内存管理功能。如果 CRT DLL 未初始化,则可以调用这些函数 导致进程崩溃。
  • 在 User32.dll 或 Gdi32.dll 中调用函数。某些函数会加载另一个可能未初始化的 DLL。
  • 使用托管代码。

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

据此,我可以告诉您,您将无法创建 QApplication 并从 DllMain 运行 Qt 应用程序,至少有以下原因:

  • Qt 将使用LoadLibrary 加载插件(至少qwindows.dll)。如果您使用任何音频或图像或 sql 数据库,Qt 也会尝试加载相应的插件(例如qjpeg.dll)。
  • Qt 也可能会尝试访问注册表,尤其是当您使用带有本机格式的 QSettings 时。
  • Qt 可能会创建线程。特别是如果您使用网络或 Qt Quick。
  • Qt 将使用mallocfree 等内存管理函数。

【讨论】:

  • 感谢您的回复 :) 是的,如果您加载 lib 并从 lib 获取函数指针,直接调用它,小部件的东西就会毫无问题地显示出来(在我的测试中,QLibrary 和 Windows 都加载了 lib原生 API 运行良好,只有在加载器进程中没有 QApplication 对象),emmm,我想要的是在加载 lib 时显示小部件而不调用 UI init 方法,即在 dllMain 函数或其他地方创建小部件。跨度>
  • @vg0x00 我已经编辑了我的答案,我已经添加了有关为什么您无法从 DllMain 创建和运行 Qt 应用程序的详细信息。
猜你喜欢
  • 2014-11-11
  • 2017-10-23
  • 2021-02-26
  • 2010-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多