当调用应用程序不是Qt时,例如C#,调用应用程序(也称为主线程)无法调用Qt特定库,因此有必要在DLL中嵌入事件循环。但这并不准确。在Windows上,每个线程都需要一个事件循环,并且可以使用纯WINAPI或使用C#或任何语言/框架来实现该事件循环。只要该事件循环正在分派窗口消息,Qt代码就会起作用。唯一需要存在的Qt特定内容是从主线程创建的QApplication(或根据您的需求创建的QGuiApplication或QCoreApplication)的实例。不应在该实例上调用exec(),因为本机代码(主应用程序)已经在泵送窗口消息。您确实需要在创建应用程序实例后调用一次QCoreApplication::processEvents来“启动”它。这是一个错误(遗漏),我不确定在Qt 5.5中是否甚至需要这样做。
随后,本地应用程序中的GUI线程将适当地将本地事件分派到Qt小部件和对象。
使用未更改的QThread::run
创建的任何工作线程都将旋转本机事件循环,并且这些线程中的每个线程都可以托管本机对象(窗口句柄)和QObject
,以及执行异步过程调用。
设置所有内容的最简单方法是在DLL中提供一个initialize
函数,该函数由主应用程序调用一次以启动Qt:
static int argc = 1;
static char arg0[] = "";
static char * argv[] = { arg0, nullptr };
Q_GLOBAL_STATIC_WITH_ARGS(QApplication, app, (arc, argv))
extern "C" __declspec(dllexport) void initialize() {
app->processEvents();
new MyWindow(app)->show();
}
在DllMain
中不进行初始化的要求不是特定于Qt的(参见此处)。使用本机WINAPI的代码被禁止在DllMain
中执行大部分操作,如创建窗口等。
我再次强调,在DllMain
中执行可能会分配内存、窗口句柄、线程等任何操作都是错误的。你只能调用kernel32
API,但也有一些例外。在这里分配QThread
或QApplication
实例显然是不可以的。从“当前”(随机)线程排队APC调用是你所能做的最好的选择,但仍然无法保证线程能够存活足够长的时间来执行你的APC,或者它是否会可警报地等待,以使APC有机会运行。
如果您感觉冒险,
根据这个答案,您可以将对
initialize()
的调用排队为APC。主要问题是你永远无法确定
DllMain
是从正确的线程调用的。它被调用的线程必须最终进入可警报的等待状态(例如泵送消息循环)。然后,您可以创建一个专用的应用程序线程,而不可能找出是否有任何特定的其他“主”线程应该使用新线程而不是现有线程。线程句柄必须分离,因此我们必须使用
std::thread
而不是
QThread
。
void guiWorker() {
int argc = 1;
const char dummy[] = "";
char * argv[] = { const_cast<char*>(dummy), 0 };
QApplication app(argc, argv);
QLabel label("Hello, World!");
label.show();
app.exec();
}
VOID CALLBACK Start(_In_ ULONG_PTR) {
std::thread thread { guiWorker };
thread.detach();
}
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID)
{
switch (reason) {
case DLL_PROCESS_ATTACH:
QueueUserAPC(Start, GetCurrentThread(), NULL);
break;
case DLL_PROCESS_DETACH:
if (QCoreApplication::instance()) QCoreApplication::instance()->quit();
break;
}
}
通常情况下,您不应该需要这样的代码。主应用程序必须从具有事件泵的线程(通常是主线程)调用初始化函数,然后一切都会正常工作 - 就像使用本地功能初始化 DLL 时那样。