[...] 当调用应用程序不是 Qt 时,例如 C#,调用应用程序(又名主线程)没有能力调用 Qt 特定的库,因此有必要设计嵌入事件循环的 DLL在里面。
这是不准确的。在 Windows 上,每个线程需要一个事件循环,并且可以使用纯 WINAPI、C# 或任何您需要的语言/框架来实现该事件循环。只要该事件循环正在调度 Windows 消息,Qt 代码就可以工作。
唯一需要存在的特定于 Qt 的东西是从主线程创建的QApplication
(或QGuiApplication
或QCoreApplication
,取决于您的需要)的实例。
您不能调用exec()
该实例,因为本机代码(主应用程序)已经在发送 Windows 消息。您确实需要在创建应用程序实例后调用QCoreApplication::processEvents
一次,以“启动”它。您确实需要这样做是一个错误(遗漏),我不确定它在 Qt 5.5 中是否有必要。
之后,本机应用程序中的 gui 线程将正确地将本机事件分派给 Qt 小部件和对象。
您使用 unaltered 创建的任何工作线程都QThread::run
将旋转本机事件循环,并且这些线程中的每一个都可以托管本机对象(Windows 句柄)QObject
等,以及执行异步过程调用。
设置它的最简单方法是initialize
在 DLL 中提供一个函数,该函数由主应用程序调用一次以使 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(); // prime the application instance
new MyWindow(app)->show();
}
不进行初始化的要求DllMain
并不特定于 Qt。禁止使用本机 WINAPI 的代码在其中做任何事情DllMain
——不可能创建窗口等。
我重申,做任何可能分配内存、窗口句柄、线程等的事情都是错误的DllMain
。您只能调用kernel32
API,但有一些例外。分配一个QThread
orQApplication
实例有一个明显的 no-no。将来自“当前”(随机)线程的 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:
// Reasonably safe, doesn't allocate
if (QCoreApplication::instance()) QCoreApplication::instance()->quit();
break;
}
}
一般来说,您永远不需要这样的代码。主应用程序必须从带有事件泵的线程(通常是主线程)调用初始化函数,然后一切都会正常工作 - 就像它只使用本机功能初始化 DLL 一样。