加载Qt共享库时,Qt小部件不会显示



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

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

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

  2. 在创建QApplication之前,不要创建任何与Qt相关的对象或调用任何与Qt相关的静态方法,因为没有Qt线程。

  3. 线程解决方案不适用于Mac OS,我的目标平台仅适用于Windows,因此,这无关紧要。

  4. 在我的情况下,如果应用程序加载我的Qt-lib,并调用用于显示小部件的方法,它是有效的。但由于某些原因,调用者无法手动调用我的lib方法。

  5. 如果主机应用程序(加载共享库的应用程序)是Qt应用程序,则应该调用QApplication::processEvents(),而不是QApplication::exec()。在我的情况下,我应该在该线程中调用QApplication::exec()。

此处的源代码:

  • dllMain版本
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;

两个版本都成功地调用了小部件init,但小部件没有如约赶到

下面是我如何使用Qt加载库测试它的,但是,事件I使用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!";
}
}

这里是一个工作示例。采用5.12 Qt、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://learn.microsoft.com/en-us/windows/desktop/dlls/dllmain

和动态链接库最佳实践:

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

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

-https://learn.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的内存管理功能

最新更新