Qt widgets not show up when Qt shared lib loaded

2020-03-08 06:24发布

问题:

Requirements: Qt widgets show up when Qt shared lib loads, for none-Qt application.

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. so, create a none-Qt thread (std::thread), then, create QApplication and some other widgets in that thread should work, but not.

  2. Do not create any Qt related object or call any Qt related static methods before QApplication created, in that none-Qt thread.

  3. The thread solution is not portable for Mac OS, my target platform is Windows only, so, it does not matter.

  4. In my case, if app load my Qt lib, and invoke the method for showing widgets, it works. but for some reason, caller can not call my lib method manually.

  5. If host application (one that loads the shared lib) is Qt application, you should call QApplication::processEvents(), not QApplication::exec(). in my case, I should call QApplication::exec() in that thread.

Source code here:

  • dllMain version:
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
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.

#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:

Here is a working example. Tested with Qt 5.12 and MSVC2017 and 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. That is why I do not use QLibrary.

This use case is not supported by 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.

Depending on what you do in your main application, it could happens that some Qt features are not working as expected. 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.

About DllMain

From Windows documentation:

Warning

There are significant limits on what you can safely do in a DLL entry point. See General Best Practices for specific Windows APIs that are unsafe to call in DllMain. If you need anything but the simplest initialization then do that in an initialization function for the DLL. You can require applications to call the initialization function after DllMain has run and before they call any other functions in the DLL.

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

and Dynamic-Link Library Best Practices:

You should never perform the following tasks from within DllMain:

  • Call LoadLibrary or LoadLibraryEx (either directly or indirectly). This can cause a deadlock or a crash.
  • Call GetStringTypeA, GetStringTypeEx, or GetStringTypeW (either directly or indirectly). 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. Under certain conditions, this function can call LoadLibraryEx.
  • Call the registry functions. These functions are implemented in Advapi32.dll. If Advapi32.dll is not initialized before your DLL, the DLL can access uninitialized memory and cause the process to crash.
  • Call CreateProcess. Creating a process can load another DLL.
  • Call ExitThread. Exiting a thread during DLL detach can cause the loader lock to be acquired again, causing a deadlock or a crash.
  • Call 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). In Windows 2000, named objects are provided by the Terminal Services DLL. If this DLL is not initialized, calls to the DLL can cause the process to crash.
  • Use the memory management function from the dynamic C Run-Time (CRT). If the CRT DLL is not initialized, calls to these functions can cause the process to crash.
  • Call functions in User32.dll or Gdi32.dll. Some functions load another DLL, which may not be initialized.
  • Use managed code.

-- https://docs.microsoft.com/en-us/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:

  • Qt will load plugins (at least qwindows.dll) using LoadLibrary. If you use any audio or image or sql database, Qt will also try to load the corresponding plugins (e.g. qjpeg.dll).
  • Qt might also try to access the registry, in particular if you use QSettings with native format.
  • Qt may create threads. In particular if you use network or Qt Quick.
  • Qt will use memory management functions like malloc or free.


标签: c++ qt qt5 dllmain