简介 这是一个开放式的问题,我认为这可能对社区有益,因为我无法找到有关此问题的优秀文件。不幸的是,我学到了很难在Qt中实现DLL的方法与其他语言不同,我将在后面解释
问题陈述 在Qt中实现一个非Qt应用程序可以轻松使用的多线程DLL
背景资讯
Qt是首选工具,因为它具有固有的跨平台兼容性 API使用回调函数在发生某些事件时告诉调用应用程序
假设
- 链接到Qt dll的应用程序与Qt编译器兼容(c / c ++ -mingw,C#-msvc) -Signals / slots用于从主线程到工作线程(例如告诉工作线程收集数据)以及从工作线程返回主线程(例如,通过数据收集已完成的回调函数通知主线程)
问题说明
由于Qt的架构,我学到了在QT中编写多线程DLL与其他语言不同的困难方法。由于QT事件循环处理spwaning线程,定时器,发送信号和接收插槽而出现问题。当主应用程序是Qt(Qt可以访问QT特定库)时,可以从主应用程序调用此Qt偶数循环(QApplication.exec())。但是,当调用应用程序不是Qt,例如C#时,调用应用程序(也就是主线程)因此无法调用Qt特定的库,因此必须设计一个嵌入了内部事件循环的DLL。 。重要的是,这在设计中被认为是最重要的,因为由于QApplication.exec阻塞,因此很难在以后对其进行鞋拔。
简而言之,我正在寻找在Qt中构建多线程dll的最佳方法的oppinions,以便它与非QT应用程序兼容。
摘要
答案 0 :(得分:0)
[...]当调用应用程序不是Qt,例如C#时,调用应用程序(也就是主线程)因此无法调用Qt特定的库,因此必须使用事件循环嵌入其中。
这不准确。在Windows上,每个线程需要一个事件循环,并且该事件循环可以使用纯WINAPI,或使用C#或您需要的任何语言/框架来实现。只要该事件循环调度Windows消息,Qt代码就可以工作。
唯一需要存在的Qt特定事项是从主线程创建的QApplication
(或QGuiApplication
或QCoreApplication
的实例,具体取决于您的需求)。
你不得在该实例上调用exec()
,因为本机代码(主应用程序)已经在传输Windows消息。创建应用程序实例后,您需要调用QCoreApplication::processEvents
一次,以及#34; prime"它。您确实需要这样做是一个错误(遗漏),我不确定它在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(); // prime the application instance
new MyWindow(app)->show();
}
DllMain
中未进行初始化的要求是not specific to Qt。使用原生的WINAPI代码禁止在DllMain
中执行任何操作 - 它无法创建窗口等。
我重申从DllMain
开始做任何可能分配内存,窗口句柄,线程等的错误都是错误的。除了一些例外,您只能调用kernel32
API。在那里分配QThread
或QApplication
个实例是一个明显的禁忌。从当前"排队APC呼叫(随机)线程是你能做的最好的,并且仍然不能保证线程能够存活足够长的时间来执行你的APC,或者它会提醒等待以便APC有机会运行
如果您感觉冒险,according to this answer,您可以将呼叫排队到initialize()
作为APC。那么主要的问题是你无法确定从正确的线程调用DllMain
。它被调用的线程必须以可警告的等待状态结束(比如抽取消息循环)。那么你可以创建一个专用的应用程序线程,并且不可能弄清楚是否有任何特定的其他" main"应该使用的线程而不是新线程。必须分离线程句柄,因此我们必须使用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一样。
答案 1 :(得分:0)
只是提供一个快速更新,以便您可以从我们的错误中吸取教训。当我们尝试将Qt编写的dll与非Qt语言(如C#)集成时,我们遇到了所有类型的问题,原因是上面列出的问题。虽然Qt非常擅长提供多平台解决方案,但它的缺点是不具备DLL友好性,因为很难让DLL使用除Qt以外的任何语言。我们目前正在研究是否要在标准便携式C ++中重写整个DLL并废弃Qt实现,这将非常昂贵。
公平警告,我会避免在创建DLL时使用QT作为框架。