3

简介 这是一个开放式问题,我认为这可能对社区有益,因为我一直无法找到与此相关的出色文档。不幸的是,我学到了在 Qt 中实现 DLL 与在其他语言中不同的艰难方式,稍后我将解释

问题陈述 在 Qt 中实现一个非 Qt 应用程序可以轻松使用的多线程 DLL

背景资料

Qt 是首选工具,因为它固有的跨平台兼容性 API 利用回调函数来告诉调用应用程序何时发生某些事件

假设

- 将链接到 Qt dll 的应用程序与 Qt 编译器兼容(c/c++ -mingw,C# -msvc) - 信号/插槽用于从主线程到工作线程进行通信(例如,告诉工作线程收集数据)以及从工作线程返回到主线程(例如,通过回调函数通知主线程数据收集已完成)

问题描述

由于 Qt 的体系结构,我了解到在 QT 中编写多线程 DLL 与其他语言不同。由于 QT 事件循环处理生成线程、计时器、发送信号和接收槽,会出现问题。当主应用程序是 Qt(Qt 可以访问 QT 特定库)时,可以从主应用程序调用此 Qt 偶数循环(QApplication.exec())。但是,当调用应用程序不是 Qt 时,例如 C#,调用应用程序(也称为主线程)因此无法调用 Qt 特定的库,因此有必要设计您的 DLL,并在其中嵌入事件循环. 重要的是,这在设计中被预先考虑,因为以后很难硬塞它,因为 QApplication.exec 是阻塞的。

简而言之,我正在寻找关于在 Qt 中构建多线程 dll 以使其与非 QT 应用程序兼容的最佳方法的意见。

总之

  • 事件循环驻留在整体架构中的什么位置?
  • 关于信号/插槽,您应该考虑哪些特别事项?
  • 在实施类似于我所描述的内容时,社区是否遇到过任何问题?
4

2 回答 2

0

[...] 当调用应用程序不是 Qt 时,例如 C#,调用应用程序(又名主线程)没有能力调用 Qt 特定的库,因此有必要设计嵌入事件循环的 DLL在里面。

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

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

不能调用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。您只能调用kernel32API,但有一些例外。分配一个QThreadorQApplication实例有一个明显的 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 一样。

于 2015-02-26T20:28:01.370 回答
0

只是为了提供对此的快速更新,以便您可以从我们的错误中吸取教训。由于上面列出的问题,当我们尝试将 Qt 编写的 dll 与 C# 等非 Qt 语言集成时,我们遇到了各种类型的问题。虽然 Qt 擅长提供多平台解决方案,但它的缺点是对 DLL 不太友好,因为很难让 DLL 工作在 Qt 以外的任何语言上。我们目前正在调查我们是否想用标准的可移植 C++ 重写我们的整个 DLL 并废弃 Qt 实现,这将是非常昂贵的。

公平的警告,在创建 DLL 时,我会避免使用 QT 作为您的框架。

于 2015-03-12T16:54:36.857 回答