3

はじめ に これは自由回答式の質問ですが、これに関する優れたドキュメントを見つけることができなかったため、コミュニティにとって有益であると考えました。残念ながら、後で説明するように、Qt での DLL の実装は他の言語とは異なるという難しい方法を学びました。

問題文 Qt 以外のアプリケーションで簡単に使用できるマルチスレッド DLL を Qt に実装する

背景情報

Qt は、固有のクロスプラットフォーム互換性があるため、最適なツールです API はコールバック関数を使用して、特定のイベントが発生したときに呼び出し元のアプリケーションに通知します

仮定

-Qt dll にリンクするアプリケーションは、Qt コンパイラ (c/c++ -mingw、C# -msvc) と互換性があります。ワーカー スレッドからメイン スレッドに戻るだけでなく (たとえば、データ収集が完了したことをコールバック関数を介してメイン スレッドに通知する)

問題の説明

Qt のアーキテクチャが原因で、QT でマルチスレッド DLL を作成することが他の言語とは異なるという難しい方法を学びました。spwaning スレッド、タイマー、シグナルの送信、およびスロットの受信を処理する QT イベント ループが原因で、問題が発生します。この Qt 偶数ループ (QApplication.exec()) は、メイン アプリが Qt の場合 (Qt が QT 固有のライブラリにアクセスできる場合)、メイン アプリケーションから呼び出すことができます。ただし、呼び出し元のアプリケーションが Qt ではない場合、たとえば C# の場合、呼び出し元のアプリケーション (別名メイン スレッド) には Qt 固有のライブラリを呼び出す機能がないため、イベント ループが埋め込まれた DLL を設計する必要があります。 . QApplication.exec がブロックされているため、後で押し込むのは難しいため、これを設計の前もって考慮することが重要です。

一言で言えば、Qt 以外のアプリケーションと互換性があるように、Qt でマルチスレッド dll を設計するための最良の方法についての意見を探しています。

要約すれば

  • イベント ループはアーキテクチャ全体のどこにありますか?
  • シグナル/スロットに関して特に考慮すべきことは何ですか?
  • 私が説明したものと同様のものを実装する際に、コミュニティが遭遇した落とし穴はありますか?
4

2 に答える 2

0

[...] 呼び出し元のアプリケーションが Qt ではない場合、たとえば C# の場合、呼び出し元のアプリケーション (別名メイン スレッド) には Qt 固有のライブラリを呼び出す機能がないため、イベント ループが埋め込まれた DLL を設計する必要があります。その中に。

これは正確ではありません。Windows では、スレッドごとに 1 つのイベント ループが必要です。そのイベント ループは、純粋な WINAPI、C#、または必要な言語/フレームワークを使用して実装できます。そのイベント ループが Windows メッセージをディスパッチしている限り、Qt コードは機能します。

存在する必要がある唯一の Qt 固有のものは、メイン スレッドから作成されたQApplication(またはQGuiApplication、必要に応じて ) のインスタンスです。QCoreApplication

ネイティブ コード (メイン アプリケーション) は既に Windows メッセージを送信しているため、そのインスタンスを呼び出してはなりません。アプリケーションインスタンスを作成した後、それを「準備」するために一度exec()呼び出す必要があります。そうする必要があるのはバグ(省略)であり、Qt 5.5でも必要かどうかはわかりません。QCoreApplication::processEvents

その後、ネイティブ アプリケーションの GUI スレッドは、ネイティブ イベントを Qt ウィジェットおよびオブジェクトに適切にディスパッチします。

unaltered を使用して作成したワーカー スレッドはQThread::runネイティブ イベント ループをスピンし、これらの各スレッドはネイティブ オブジェクト (Windows ハンドル) などをホストQObjectし、非同期プロシージャ コールを実行できます。

すべてを設定する最も簡単な方法は、initializeQt を起動するためにメイン アプリケーションによって一度呼び出される関数を DLLに提供することです。

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();
}

初期化を行わないという要件は、 Qt に固有DllMainのものではありません。ネイティブの WINAPI を使用するコードは、ほとんど何もすることが禁止されています。ウィンドウなどを作成することはできません。DllMain

からメモリ、ウィンドウ ハンドル、スレッドなどを割り当てる可能性のある操作を行うのはエラーであることを繰り返しますDllMainkernel32一部の例外を除いて、APIのみを呼び出すことができます。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 に答える