markdown
はじめに (対象読者・この記事でわかること)
この記事は、QtでGUIツールやバックグランドサービスを開発しており、「他のプロセスへ何らかのイベントを即座に伝えたい」と考えているC++開発者を対象にしています。
記事を読むことで、QLocalSocket・QSharedMemory・QSystemSemaphoreを使ったIPC(プロセス間通信)の基本がわかり、シンプルな「通知」から「バイナリデータのやり取り」まで実装できるようになります。Windows/macOS/Linuxのクロスプラットフォーム対応も意識したコードを紹介します。
前提知識
- Qt Core・Qt Networkの基本的な知識(信号/スロット、Qtのメモリ管理)
- C++11以降のラムダ式とスマートポインタの読み書き
- プロセス・スレッドの違いをある程度理解していること
QtでIPCを選ぶ前に知っておくべき3つの選択肢
Qtアプリ同士の連携で最も手軽なのは「Qt自身が提供するIPCクラス」を使うことです。代表的な3つを比較すると:
| 手法 | 特徴 | レイテンシ | データサイズ | クロスプラットフォーム |
|---|---|---|---|---|
| QLocalSocket/QLocalServer | 名前付きパイプ or ドメインソケット | 低〜中 | 数MBまで | ◯ |
| QSharedMemory | 共有メモリ | 最低 | 数MB〜数GB | ◯(注意点あり) |
| QSystemSemaphore | セマフォだけ | 低 | 0(通知のみ) | ◯ |
「通知だけならQLocalSocket」「大容量データを高速にやり取りしたいならQSharedMemory」「排他制御だけならQSystemSemaphore」という具合に使い分けます。今回は「通知+α」を実現するため、QLocalSocketをメインに据えたハイブリッド手法を紹介します。
ステップバイステップで実装するQLocalSocket通知システム
ステップ1:サーバー側(通知を受ける側)を作る
プロジェクトファイル(.pro)にネットワークモジュールを追加します。
ProQT += core network CONFIG += c++17 console TARGET = notification_server
main.cpp
Cpp#include <QCoreApplication> #include <QLocalServer> #include <QLocalSocket> #include <QTextStream> #include <QTimer> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); const QString serverName = "qt_demo_ipc"; QLocalServer server; if (!server.listen(serverName)) { // 既存のサーバーが残っている場合は削除してリトライ QLocalServer::removeServer(serverName); server.listen(serverName); } QTextStream out(stdout); QObject::connect(&server, &QLocalServer::newConnection, [&]() { QLocalSocket *client = server.nextPendingConnection(); out << "Client connected\n"; QObject::connect(client, &QLocalSocket::readyRead, [client, &out]() { QByteArray msg = client->readAll(); out << "Received: " << msg << Qt::endl; // Echo 返しで応答 client->write("ACK"); }); QObject::connect(client, &QLocalSocket::disconnected, client, &QLocalSocket::deleteLater); }); out << "Server listening on " << serverName << Qt::endl; return a.exec(); }
ビルドして起動すると、名前付きパイプ/ドメインソケットが qt_demo_ipc として作成されます。
ステップ2:クライアント側(通知を送る側)を作る
クライアントも同じくnetworkモジュールが必要です。
Cpp#include <QCoreApplication> #include <QLocalSocket> #include <QTextStream> #include <QTimer> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); const QString serverName = "qt_demo_ipc"; QLocalSocket socket; socket.connectToServer(serverName); if (!socket.waitForConnected(3000)) { qWarning() << "Cannot connect:" << socket.errorString(); return 1; } QTextStream out(stdout); out << "Connected to server\n"; // 通知+任意のバイナリデータを送信 QByteArray payload("Hello from Qt!"); socket.write(payload); socket.flush(); // 応答を待つ if (socket.waitForReadyRead(3000)) { QByteArray resp = socket.readAll(); out << "Server replied:" << resp << Qt::endl; } socket.disconnectFromServer(); return 0; }
クライアントを起動すると、サーバー側コンソールに
Client connected
Received: Hello from Qt!
と表示され、通知が届いたことがわかります。
ステップ3:QLocalSocketだけだと複雑な通知が書きにくい問題をQDataStreamで解決する
単純な文字列だけなら上記で十分ですが、構造化データを送りたい場合はQDataStreamを使います。
送信側:
CppQByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_5_15); // バージョンは固定しておく out << quint32(0); // サイズプレースホルダ out << QString("image") << QImage(":/cat.png"); out.device()->seek(0); out << quint32(block.size() - sizeof(quint32)); socket.write(block);
受信側:
CppQDataStream in(client); in.setVersion(QDataStream::Qt_5_15); quint32 blockSize; in >> blockSize; QString type; in >> type; if (type == "image") { QImage img; in >> img; emit imageReceived(img); }
これで、画像や独自構造体を通知として送れます。
ステップ4:QLocalSocket+QSharedMemoryで大容量データを高速に渡す
QLocalSocketだけで100MB越えの画像を送ると、内部的にコピーが発生し遅くなります。そこで「通知はQLocalSocket、実データはQSharedMemory」というハイブリッドを使います。
サーバー側:
Cpp// 1. 共有メモリ確保 QSharedMemory shared("qt_demo_shm"); shared.create(100 * 1024 * 1024); // 100MB shared.lock(); // ...データをコピー... shared.unlock(); // 2. 通知を送る QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out << QString("shm") << quint64(shared.size()); socket.write(block);
クライアント側:
CppQDataStream in(socket); QString type; quint64 size; in >> type >> size; QSharedMemory shared("qt_demo_shm"); shared.attach(QSharedMemory::ReadOnly); shared.lock(); QByteArray raw((const char*)shared.data(), size); shared.unlock();
これで、巨大なデータをコピーなしで転送できます。
ハマった点と解決策
1. WindowsでQLocalServerがlistenに失敗する
症状: QLocalServer::listen() が false を返し、エラー文字列に「Access is denied」が出る。
原因: 名前付きパイプが以前の起動時に残ったままです。
解決: 起動時に必ず QLocalServer::removeServer(name) を呼ぶ。macOS/Linuxでは不要ですが、Windowsだけでなく安全側に倒しておくとよいです。
2. QSharedMemoryがクリーンアップされない
症状: アプリがクラッシュした後、次回起動時に create() が失敗する。
解決:
- 開発中は QSharedMemory::attach() して detach() するだけで解放できる。
- 本番では、クライアントが異常終了してもサーバー側で QSharedMemory::setKey("qt_demo_shm"); shared.detach(); してやるとクリーンアップできる。
3. 送信側が複数クライアントから同時に通知しても大丈夫?
QLocalServerは内部的にイベントループで処理されるため、同時接続はキューイングされます。ただし、共有メモリのロックは開発者が管理する必要があるため、次のようなRAIIラッパーを使うと安全です。
Cppstruct ShmLock { QSharedMemory &m; explicit ShmLock(QSharedMemory &s) : m(s) { m.lock(); } ~ShmLock() { m.unlock(); } };
まとめ
本記事では、Qtアプリが他プロセスへ通知を送る代表的な3手法(QLocalSocket、QSharedMemory、QSystemSemaphore)を比較し、QLocalSocketを中心としたハイブリッドIPCを実装しました。
- QLocalSocket/QLocalServerだけで、通知+小〜中容量データを簡単に送れる
- QDataStreamで構造化データを安全に直列化できる
- QSharedMemoryと組み合わせると、大容量データもコピーなしで高速に転送できる
この知識を使えば、Qt製メインウィンドウが別プロセスのバッチ処理を起動・監視したり、マルチプロセスで負荷分散したデスクトップアプリケーションを作ることができます。
次回は、Qt Remote Objects(QtRO)を使ったリモートAPI呼び出しや、WebSocket経由でブラウザと連携する方法を紹介する予定です。
参考資料
- Qt Documentation – Inter-Process Communication in Qt https://doc.qt.io/qt-5/ipc.html
- Qt Documentation – QLocalSocket https://doc.qt.io/qt-5/qlocalsocket.html
- Qt Documentation – QSharedMemory https://doc.qt.io/qt-5/qsharedmemory.html
- プロセス間通信 パターン&ベストプラクティス(オライリージャパン)
