Qt:多线程DLL设计

时间:2015-02-25 16:56:33

标签: qt dll qthread qeventloop qcoreapplication

简介 这是一个开放式的问题,我认为这可能对社区有益,因为我无法找到有关此问题的优秀文件。不幸的是,我学到了很难在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应用程序兼容。

摘要

  • 事件循环在整体架构中的位置是什么?
  • 您应该对信号/插槽进行哪些特殊考虑?
  • 当社区遇到任何问题时 实现类似于我所描述的东西?

2 个答案:

答案 0 :(得分:0)

  

[...]当调用应用程序不是Qt,例如C#时,调用应用程序(也就是主线程)因此无法调用Qt特定的库,因此必须使用事件循环嵌入其中。

这不准确。在Windows上,每个线程需要一个事件循环,并且该事件循环可以使用纯WINAPI,或使用C#或您需要的任何语言/框架来实现。只要该事件循环调度Windows消息,Qt代码就可以工作。

唯一需要存在的Qt特定事项是从主线程创建的QApplication(或QGuiApplicationQCoreApplication的实例,具体取决于您的需求)。

不得在该实例上调用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。在那里分配QThreadQApplication个实例是一个明显的禁忌。从当前"排队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作为框架。