要求:对于 none-Qt 应用程序,当Qt共享库加载时,将显示Qt小部件。
经过一些网页搜索,我发现:
所有Qt小部件都必须位于“主线程”中,“主线程”是第一个Qt对象创建的线程。因此,创建一个无Qt线程(std :: thread),然后创建 QApplication和该线程中的其他小部件应该可以,但不能。
在创建QApplication之前,不要在该非Qt线程中创建任何与Qt相关的对象或调用任何与Qt相关的静态方法。
线程解决方案不适用于Mac OS,我的目标平台仅是Windows,所以没关系。
对于我来说,如果应用加载了我的Qt库,并调用了显示小部件的方法, 有用。但是由于某些原因,调用者无法手动调用我的lib方法。
如果主机应用程序(加载共享库的一个)是Qt应用程序,则应调用QApplication :: processEvents()而不是QApplication :: exec()。就我而言,我应该在该线程中调用QApplication :: exec()。
此处的源代码:
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;
}
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!";
}
}
答案 0 :(得分:0)
这是一个可行的示例。已通过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发出消息,但永远不会得到它们,因为它们将由真正的主线程处理。
根据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中实现。如果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/en-us/windows/desktop/dlls/dynamic-link-library-best-practices
由此,我可以告诉您,至少由于以下原因,您将无法创建QApplication
并从DllMain
运行Qt应用程序:
qwindows.dll
加载插件(至少LoadLibrary
)。如果您使用任何音频,图像或sql数据库,Qt也会尝试加载相应的插件(例如qjpeg.dll
)。QSettings
。malloc
或free
之类的内存管理功能。