繁体   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