简体   繁体   English

加载Qt共享库时未显示Qt小部件

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

Requirements: Qt widgets show up when Qt shared lib loads , for none-Qt application. 要求:对于非Qt应用程序,当Qt共享库加载时 ,将显示Qt小部件。

After some web searching, I found: 经过一些网络搜索,我发现:

  1. All Qt widgets must live in "main thread", the "main thread" is the first Qt object created thread. 所有Qt小部件都必须位于“主线程”中,“主线程”是第一个Qt对象创建的线程。 so, create a none-Qt thread (std::thread), then, create QApplication and some other widgets in that thread should work, but not. 因此,请创建一个无Qt线程(std :: thread),然后在该线程中创建QApplication和其他一些小部件,但不能使用。

  2. Do not create any Qt related object or call any Qt related static methods before QApplication created, in that none-Qt thread. 在该非Qt线程中,不要在创建QApplication之前创建任何与Qt相关的对象或调用任何与Qt相关的静态方法。

  3. The thread solution is not portable for Mac OS, my target platform is Windows only, so, it does not matter. 线程解决方案不适用于Mac OS,我的目标平台仅是Windows,因此没有关系。

  4. In my case, if app load my Qt lib, and invoke the method for showing widgets, it works. 就我而言,如果应用程序加载了我的Qt库,并调用了用于显示小部件的方法,则它可以工作。 but for some reason, caller can not call my lib method manually. 但是由于某种原因,调用者无法手动调用我的lib方法。

  5. If host application (one that loads the shared lib) is Qt application, you should call QApplication::processEvents(), not QApplication::exec(). 如果主机应用程序(加载共享库的主机应用程序)是Qt应用程序,则应调用QApplication :: processEvents()而不是QApplication :: exec()。 in my case, I should call QApplication::exec() in that thread. 就我而言,我应该在该线程中调用QApplication :: exec()。

Source code here: 源代码在这里:

  • dllMain version : 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;
}
  • Simple C++ static class version 简单的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;

Both version invoke widgets init stuff successfully, but widgets not show up. 两种版本均成功调用了小部件初始化内容,但小部件未显示。

Here is how I test it, using Qt load lib, but, event I load lib using Win32 API, failed too. 这是我使用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!";
    }
}

Here is a working example. 这是一个工作示例。 Tested with Qt 5.12 and MSVC2017 and MinGW. 已通过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;
}

Note that if you use Qt function before the thread is created, Qt will detect that it is not in the main thread and the process will crash. 请注意,如果在创建线程之前使用Qt函数,则Qt将检测到它不在主线程中,并且进程将崩溃。 That is why I do not use QLibrary . 这就是为什么我不使用QLibrary

This use case is not supported by Qt. Qt不支持此用例。 So if you make it work now, you are not guaranteed that it will work in the future. 因此,如果您现在使它工作,则不能保证它将来会工作。

You cannot load at the same time 2 dll like this. 您不能同时加载2个这样的dll。

Depending on what you do in your main application, it could happens that some Qt features are not working as expected. 根据您在主应用程序中所做的操作,可能会发生某些Qt功能无法按预期运行的情况。 For instance it could happen that Qt expects messages from Windows, but will never get them because they will be handled by the real main thread. 例如,Qt可能期望Windows发出消息,但永远不会得到它们,因为它们将由真正的主线程处理。

About DllMain 关于DllMain

From Windows documentation: 从Windows文档:

Warning 警告

There are significant limits on what you can safely do in a DLL entry point. 在DLL入口点中可以安全进行的操作有很多限制。 See General Best Practices for specific Windows APIs that are unsafe to call in DllMain. 有关在DllMain中调用不安全的特定Windows API的信息,请参见常规最佳实践。 If you need anything but the simplest initialization then do that in an initialization function for the DLL. 如果除了最简单的初始化之外还需要其他任何东西,请在DLL的初始化函数中执行此操作。 You can require applications to call the initialization function after DllMain has run and before they call any other functions in the DLL. 您可以要求应用程序在DllMain运行之后并且在调用DLL中的任何其他函数之前调用初始化函数。

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

and Dynamic-Link Library Best Practices: 和动态链接库最佳做法:

You should never perform the following tasks from within DllMain: 您永远不要在DllMain中执行以下任务:

  • Call LoadLibrary or LoadLibraryEx (either directly or indirectly). 调用LoadLibrary或LoadLibraryEx(直接或间接)。 This can cause a deadlock or a crash. 这可能导致死锁或崩溃。
  • Call GetStringTypeA, GetStringTypeEx, or GetStringTypeW (either directly or indirectly). 调用GetStringTypeA,GetStringTypeEx或GetStringTypeW(直接或间接)。 This can cause a deadlock or a crash. 这可能导致死锁或崩溃。
  • Synchronize with other threads. 与其他线程同步。 This can cause a deadlock. 这可能导致死锁。
  • Acquire a synchronization object that is owned by code that is waiting to acquire the loader lock. 获取由等待获取加载程序锁的代码所拥有的同步对象。 This can cause a deadlock. 这可能导致死锁。
  • Initialize COM threads by using CoInitializeEx. 通过使用CoInitializeEx初始化COM线程。 Under certain conditions, this function can call LoadLibraryEx. 在某些情况下,此函数可以调用LoadLibraryEx。
  • Call the registry functions. 调用注册表函数。 These functions are implemented in Advapi32.dll. 这些功能在Advapi32.dll中实现。 If Advapi32.dll is not initialized before your DLL, the DLL can access uninitialized memory and cause the process to crash. 如果在您的DLL之前未初始化Advapi32.dll,则DLL可以访问未初始化的内存,并导致进程崩溃。
  • Call CreateProcess. 调用CreateProcess。 Creating a process can load another DLL. 创建一个进程可以加载另一个DLL。
  • Call ExitThread. 调用ExitThread。 Exiting a thread during DLL detach can cause the loader lock to be acquired again, causing a deadlock or a crash. 在DLL分离期间退出线程可能导致再次获取加载程序锁,从而导致死锁或崩溃。
  • Call CreateThread. 调用CreateThread。 Creating a thread can work if you do not synchronize with other threads, but it is risky. 如果您不与其他线程同步,则可以创建线程,但是这样做很冒险。
  • Create a named pipe or other named object (Windows 2000 only). 创建一个命名管道或其他命名对象(仅Windows 2000)。 In Windows 2000, named objects are provided by the Terminal Services DLL. 在Windows 2000中,终端服务DLL提供了命名对象。 If this DLL is not initialized, calls to the DLL can cause the process to crash. 如果未初始化此DLL,则对该DLL的调用可能导致进程崩溃。
  • Use the memory management function from the dynamic C Run-Time (CRT). 使用动态C运行时(CRT)中的内存管理功能。 If the CRT DLL is not initialized, calls to these functions can cause the process to crash. 如果未初始化CRT DLL,则对这些函数的调用会导致进程崩溃。
  • Call functions in User32.dll or Gdi32.dll. 调用User32.dll或Gdi32.dll中的函数。 Some functions load another DLL, which may not be initialized. 某些函数会加载可能未初始化的另一个DLL。
  • Use managed code. 使用托管代码。

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

From this I can tell you that you will not be able to create a QApplication and run a Qt app from DllMain for, at least, the following reasons: 由此,我可以告诉您,至少由于以下原因,您将无法从DllMain创建QApplication并运行Qt应用程序:

  • Qt will load plugins (at least qwindows.dll ) using LoadLibrary . Qt将使用LoadLibrary加载插件(至少qwindows.dll )。 If you use any audio or image or sql database, Qt will also try to load the corresponding plugins (eg qjpeg.dll ). 如果您使用任何音频或图像或sql数据库,Qt还将尝试加载相应的插件(例如qjpeg.dll )。
  • Qt might also try to access the registry, in particular if you use QSettings with native format. Qt可能还会尝试访问注册表,特别是如果您使用本机格式的QSettings
  • Qt may create threads. Qt可能会创建线程。 In particular if you use network or Qt Quick. 特别是如果您使用网络或Qt Quick。
  • Qt will use memory management functions like malloc or free . Qt将使用mallocfree类的内存管理功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM