CallNamedPipeで簡易的なパイプ通信を実装する方法
Windows APIでプロセス間通信を行う方法には、名前付きパイプ、匿名パイプ、ソケット、共有メモリなどがあります。なかでも名前付きパイプは、同じコンピューター上で動作する別プロセス間のデータ交換に向いており、通信相手をパイプ名で指定できる点が特徴です。
CallNamedPipeは、名前付きパイプへ接続し、データを書き込み、応答を読み取って切断する処理を一度に扱える関数です。短い要求と応答を同期的にやり取りする用途なら、クライアント側のコードを簡潔に記述できます。
一方、サーバー側ではCreateNamedPipe、ConnectNamedPipe、ReadFile、WriteFileなどを使って待ち受け処理を構築します。APIの役割を分けて理解すると、接続失敗、バッファー不足、タイムアウトといった問題にも対応しやすくなります。
名前付きパイプの仕組みと用途
名前付きパイプは、\\.\pipe\パイプ名という形式の名前を持つ通信エンドポイントです。サーバーはこの名前でパイプを作成して接続を待機し、クライアントは同じ名前を指定して接続します。通信は通常、クライアントから要求を送り、サーバーが結果を返すという流れになります。
CallNamedPipeは、クライアント側でこの一連の処理を簡略化するための関数です。内部的にはパイプへの接続、要求データの送信、応答データの受信、ハンドルのクローズが行われます。短時間で完了するRPC風の処理には便利ですが、長時間維持する接続や双方向の連続通信には、個別のファイルAPIを使う構成が適しています。
サーバー側でパイプを作成する
サーバーはCreateNamedPipeでパイプを作成します。通信方向にはPIPE_ACCESS_INBOUND、PIPE_ACCESS_OUTBOUND、PIPE_ACCESS_DUPLEXがあり、要求と応答の両方を扱う場合は双方向の設定を選びます。データ形式はバイトストリームとして扱うPIPE_TYPE_BYTEと、メッセージ境界を保てるPIPE_TYPE_MESSAGEから選択できます。
概念的なサーバー処理は、次の順序になります。まず名前付きパイプを作成し、ConnectNamedPipeでクライアントを待ちます。接続後にReadFileで要求を読み、処理結果をWriteFileで返します。通信が終わったらFlushFileBuffers、DisconnectNamedPipe、CloseHandleを呼び出して後始末を行います。
HANDLE pipe = CreateNamedPipeW(
L"\\\\.\\pipe\\SamplePipe",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
1, 4096, 4096, 5000, nullptr
);
nMaxInstancesを1にすれば単一クライアント向けになります。複数クライアントを受け付ける場合は、接続ごとに新しいインスタンスを用意する設計が必要です。
CallNamedPipeで要求と応答を処理する
クライアントはCallNamedPipeWにパイプ名、送信バッファー、送信サイズ、受信バッファー、受信サイズ、実際に受信したバイト数、タイムアウト値を渡します。簡単な文字列要求なら、次のように記述できます。
wchar_t request[] = L"status";
wchar_t response[256] = {};
DWORD received = 0;
BOOL ok = CallNamedPipeW(
L"\\\\.\\pipe\\SamplePipe",
request,
sizeof(request),
response,
sizeof(response),
&received,
3000
);
Unicode文字列を送る場合、サーバーも同じ文字コードとサイズの解釈を使う必要があります。sizeofは配列全体のバイト数を返しますが、ポインターに変換した後では期待どおりのサイズになりません。送信するデータの長さを明示し、終端のNULL文字を含めるかどうかも通信仕様として決めておきます。
呼び出しが失敗したときは、戻り値だけでなくGetLastErrorの値を確認します。サーバーが起動していない場合はERROR_FILE_NOT_FOUND、接続待ちの状態や処理遅延ではタイムアウトに関係するエラーが返ることがあります。エラーコードをログへ記録すると、クライアントとサーバーのどちらに問題があるかを切り分けやすくなります。
メッセージ境界と同期処理を設計する
PIPE_TYPE_MESSAGEを使うと、1回の書き込みを1メッセージとして扱いやすくなります。ただし、受信バッファーが小さい場合はメッセージ全体を読み取れず、ERROR_MORE_DATAが発生することがあります。固定長の応答を想定するのではなく、必要に応じて受信処理を繰り返す設計が安全です。
CallNamedPipeは同期関数であるため、呼び出し中のスレッドは応答またはタイムアウトまで待機します。GUIスレッドから直接呼び出すと、サーバー停止時にウィンドウが操作不能になる可能性があります。画面処理の実装で使われる描画処理の基本と同様に、API呼び出しの開始と終了、失敗時の分岐を明確に分けることが重要です。
関連するWindows APIとの使い分け
名前付きパイプは、同一マシン内のサービス連携や補助プロセスとの通信に適しています。TCPソケットより導入が簡単で、パイプ名に対するアクセス権も設定できます。一方、異なるマシンとの通信や多数の同時接続が必要なら、WinsockやHTTPなどの方式のほうが運用しやすい場合があります。
Windows APIには、通信以外にもハンドルやオブジェクトを管理する関数が数多くあります。たとえばGDIオブジェクトを扱うときは、標準オブジェクト取得のようなAPIの戻り値と解放規則を確認します。名前付きパイプでも同じく、取得したハンドルの所有者と解放タイミングを決めておくことが欠かせません。
| 方式 | 通信範囲 | 実装の特徴 | 適した用途 |
|---|---|---|---|
CallNamedPipe |
主に同一マシン | 要求と応答を簡潔に処理 | 短い同期通信 |
| 名前付きパイプの個別API | 同一マシン、構成によりネットワーク | 長時間接続や複数回通信に対応 | サービス連携 |
| 匿名パイプ | 主に親子プロセス | パイプ名を使わず構築が簡単 | 子プロセスの標準入出力 |
| TCPソケット | ネットワーク全体 | 接続管理やプロトコル設計が必要 | リモート通信 |
安全で保守しやすい実装にする
パイプ名を知っているプロセスなら接続できる構成は避け、必要に応じてセキュリティ記述子やアクセス権を設定します。受信データをそのままコマンドとして実行したり、サイズを検証せずに固定長バッファーへコピーしたりする処理も危険です。要求の種類、入力長、応答形式を明確に定義してください。
GUIアプリケーションで通信結果に応じてウィンドウの外観を変える場合でも、通信処理と画面処理を分離すると保守性が高まります。ウィンドウ属性を変更する拡張スタイル変更のような処理を通信関数へ直接混在させず、結果を受け取った側で状態更新を行う構成が適しています。
実装時に確認したいポイント
- サーバーとクライアントでパイプ名、文字コード、データ形式を一致させる
- 送受信サイズをバイト単位で管理し、終端文字の扱いを決める
CallNamedPipeのタイムアウトを無期限にせず、用途に合う値を設定する- 戻り値と
GetLastErrorを確認し、接続失敗とデータ異常を区別する - GUIスレッドを長時間ブロックさせず、必要ならワーカースレッドを使う
- ハンドルを確実に閉じ、サーバー側では切断後に次の接続を受け付ける
CallNamedPipeは、少量のデータを確実に往復させるための便利な入口です。通信が単純なうちはこの関数で十分ですが、接続の維持、並列処理、大容量データ、詳細なエラー回復が必要になった段階では、名前付きパイプの各APIを組み合わせた構成へ移行します。
覚えておきたい点は、関数を短く呼び出せることよりも、サーバーとクライアントが同じ通信契約を守り、タイムアウト、バッファー、権限、ハンドル解放を一貫して管理することです。そこを押さえれば、Windows APIによる簡易的なプロセス間通信を安定して組み立てられます。