「常駐サービスと設定画面アプリの間で命令をやり取りしたい」「管理者権限が必要な処理だけを別プロセスに分離したい」「同一PC内のツール同士でデータを渡したい」── Windowsでこの種のプロセス間通信(IPC)が必要になったとき、最初に検討すべき定番が名前付きパイプ(named pipe)です。
プロセス間通信の判断表の記事では、名前付きパイプを「同一マシンIPCの第一候補」と位置づけました。本記事はその各論です。なぜ第一候補なのか、モードやサーバー構成をどう選ぶのか、そして特権サービスが使うときに何を守らなければならないのか ── Windowsで業務アプリやサービスを書く開発者を対象に、設計判断の材料を一次情報にもとづいて整理します。
1. まず結論
- 名前付きパイプは
\\.\pipe\名前の名前空間を持つ、双方向のプロセス間通信路です。同じ名前で複数インスタンスを作って複数クライアントを同時に受けられます。1 - 同一マシンIPCで第一候補になる理由はセキュリティモデルです。ACLで接続者を制御でき、サーバーはクライアントのWindowsアカウントを確認・借用(偽装)できます。localhost TCPにはどちらもありません。2
- 「1書き込み=1件」で扱いたいならメッセージモード、自前フレーミングを持つならバイトモードです。メッセージモードでもバッファ不足時の分割読み(ERROR_MORE_DATA)への対応は必要です。3
- 複数クライアントは「インスタンス複数+overlapped I/O」か「.NETの async/await」で捌きます。公式サンプルは単一スレッドで複数インスタンスを処理する形を示しています。4
- セキュリティの最低ラインは、リモート拒否(PIPE_REJECT_REMOTE_CLIENTS)、ACLの明示、FILE_FLAG_FIRST_PIPE_INSTANCEによる乗っ取り検出、クライアント側の偽装レベル最小化の4点です。56
ImpersonateNamedPipeClientは戻り値確認が生命線です。失敗を無視するとサーバー権限のまま処理が走ります。6
2. 名前付きパイプとは ── 名前空間・インスタンス・接続の形
名前付きパイプは、\\.\pipe\MyCompany.MyApp.Control のような名前で識別される通信路です。サーバーが CreateNamedPipe で作り、クライアントは同じ名前を CreateFile で開きます。開いてしまえば、あとは両者とも ReadFile / WriteFile で読み書きする ── ファイルI/Oと同じ形で使えるのが特徴です。1
重要な概念がインスタンスです。同じ名前のパイプは複数インスタンス作成でき、1インスタンスが1クライアントとの1本の通信路になります。最初の CreateNamedPipe 呼び出しで最大インスタンス数(または無制限)を決めます。3
クライアント側の接続には定番の作法があります。全インスタンスが使用中のとき CreateFile は ERROR_PIPE_BUSY で失敗するので、WaitNamedPipe で空きを待ってから再試行します。また、開くときのアクセス指定はサーバーが作った方向と噛み合っている必要があります ── 双方向パイプなら読み書きどちらの指定でも開けますが、サーバーが書くだけのアウトバウンドは読み取り専用で、サーバーが読むだけのインバウンドは書き込み専用で開かないと CreateFile は失敗します。7
flowchart TB
accTitle: パイプの方向とクライアントのアクセス指定
accDescr: 双方向パイプにはクライアントは読み書きいずれの指定でも開けるが、サーバーが書くだけのアウトバウンドには読み取り専用で、サーバーが読むだけのインバウンドには書き込み専用で開く必要がある
q{"サーバーが作った方向は?"} -->|"双方向"| dc["読み書きどちらでも可"]
q -->|"アウトバウンド"| oc["読み取り専用で開く"]
q -->|"インバウンド"| ic["書き込み専用で開く"]
図1: 方向とアクセス指定の食い違いはCreateFileの失敗になる。接続エラー調査ではまずここを確認する。
flowchart TB
accTitle: 名前付きパイプの基本構造
accDescr: サーバーは同じ名前のパイプインスタンスを複数作成してConnectNamedPipeで接続を待ち、各クライアントはCreateFileで名前を開いて1つのインスタンスと1対1の双方向通信路を持つ
s["サーバー"] --> i1["インスタンス1"]
s --> i2["インスタンス2"]
s --> i3["インスタンス3"]
c1["クライアントA"] <--> i1
c2["クライアントB"] <--> i2
c3["クライアントC"] <--> i3
図2: 同じ名前のインスタンスを複数持つことで、1つのサーバーが複数クライアントと同時に1対1通信できる。
なお名前付きパイプはSMB経由でリモートからも開けますが(\\server\pipe\名前)、現代の設計で積極的に使う理由はほぼなく、むしろ使わないのに開いたままにしないことが論点になります(5章)。
3. バイトモードとメッセージモード
パイプには2つの転送モードがあります。3
- バイトモード(PIPE_TYPE_BYTE): TCPと同じ「切れ目のないバイト列」。どこまでが1件かは自分で決める(長さプレフィックスなどのフレーミングを設計する)必要があります。
- メッセージモード(PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): 書き込み1回が1メッセージとして扱われ、読み取り側はその単位で受け取れます。要求・応答型のやり取りにはこちらが楽です。
メッセージモードには便利な相棒として、要求の送信と応答の受信を1回で行う TransactNamedPipe もあります。8 一方で落とし穴もあります。受信バッファがメッセージ全体より小さいと、読み取りは ERROR_MORE_DATA を返して分割読みになります。メッセージモードだからといって「1回のReadで必ず全体が来る」と決め打ちせず、残りを読み足すループは書いておく必要があります。なお、読み取りモードはハンドルごとの設定で、CreateNamedPipe が決めるのはサーバー側だけ。クライアントは CreateFile 後に SetNamedPipeHandleState(.NETは接続後の ReadMode)で指定します。7
flowchart TB
accTitle: メッセージモードの分割読みループ
accDescr: ReadFileが成功すればメッセージは完成し、ERROR_MORE_DATAならバッファに入りきらなかった残りを読み足して連結し、それ以外のエラーなら切断として処理する
read["ReadFileで読み取る"] --> r{"結果は?"}
r -->|"成功"| done["メッセージ完成"]
r -->|"ERROR_MORE_DATA"| more["残りを読み足して連結"]
more --> read
r -->|"その他のエラー"| dis["切断として処理"]
図3: メッセージモードでも「読み足しループ」は必要で、これを書かないと大きいメッセージだけ壊れる。
flowchart TB
accTitle: バイトモードとメッセージモードの違い
accDescr: バイトモードでは3回の書き込みが切れ目のないバイト列になり受信側が自前で区切る必要があるが、メッセージモードでは書き込み1回ごとの単位が保存されて受信側にそのまま届く
bw["バイトモード:AAA・BB・CCCCを書き込み"] --> br["受信はAAABBCCCCのバイト列"]
br --> bf["区切り(フレーミング)は自前で設計"]
mw["メッセージモード:同じ3回の書き込み"] --> mr["受信はAAA・BB・CCCCの3件"]
mr --> mf["書き込みの単位が保存される"]
図4: メッセージモードは「書き込みの単位」を保存して届ける。区切り設計が不要になるぶん、分割読みへの対応だけ忘れない。
どちらを選ぶかの実務指針は単純です。やり取りが「要求と応答」の形ならメッセージモード。既にフレーミングを内蔵した形式(長さ付きのシリアライズデータやストリーム転送)を流すならバイトモード。.NETでは PipeTransmissionMode.Message の指定が前者に対応します。9
4. サーバーの設計 ── 同期スレッド型か、overlapped型か
サーバーの基本動作は「インスタンスを作る → ConnectNamedPipe でクライアントを待つ → 読み書き → 切断して次のクライアントへ」の繰り返しです。複数クライアントを同時に相手にする構成は2通りあります。
同期スレッド型。インスタンスごとにスレッドを1本割り当て、それぞれが同期I/Oで自分のクライアントを相手にします。コードは素直ですが、クライアント数ぶんスレッドを消費し、全体の停止(シャットダウン)時にブロッキングI/Oを抜けさせる工夫も必要になります。
overlapped(非同期)型。FILE_FLAG_OVERLAPPED でインスタンスを作り、ConnectNamedPipe / ReadFile / WriteFile を非同期で発行して、少数のスレッドで全インスタンスの完了を捌きます。Microsoftの公式サンプルには、イベントの配列を WaitForMultipleObjects で待って単一スレッドで複数インスタンスを処理するサーバーが示されています。4 非同期I/Oの一般論はI/O連載の記事で解説したとおりで、規模が大きければIOCPやスレッドプールI/Oへの接続も選べます。
flowchart TB
accTitle: overlapped型サーバーの構造
accDescr: 各インスタンスの非同期操作の完了をイベントの配列で受け、少数のスレッドがWaitForMultipleObjectsで待って完了したインスタンスの処理を進めることで、スレッド数をクライアント数から切り離す
i1["インスタンス1の非同期操作"] --> ev["イベントの配列"]
i2["インスタンス2の非同期操作"] --> ev
i3["インスタンス3の非同期操作"] --> ev
ev --> wait["WaitForMultipleObjectsで完了を待つ"]
wait --> proc["完了したインスタンスの処理を進める"]
proc --> wait
図5: overlapped型はスレッド数をクライアント数から切り離す。公式サンプルは単一スレッドでこの回転を回している。
.NETならこの選択はほぼ悩まずに済みます。NamedPipeServerStream の WaitForConnectionAsync / ReadAsync / WriteAsync と async/await を使えば、overlapped型の効率を同期型並みの素直なコードで手に入れられます。9
// C#: 複数クライアントを受けるサーバーの骨格
while (!token.IsCancellationRequested)
{
var server = new NamedPipeServerStream(
"MyCompany.MyApp.Control",
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message,
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
try
{
await server.WaitForConnectionAsync(token);
}
catch
{
await server.DisposeAsync(); // 接続前に抜けるときは自分で破棄
throw;
}
_ = HandleClientAsync(server, token); // 接続後の所有権はハンドラー側へ
}
PipeOptions.CurrentUserOnly は「同じユーザーのプロセスからの接続だけを許す」指定で、ACLを自分で書かずに済む手軽で安全な既定です。10 ユーザーをまたぐ構成(サービス⇔ユーザーセッションのアプリなど)では使えないため、その場合は次章のACL設計に進みます。
flowchart TB
accTitle: .NET非同期サーバーの受付ループ
accDescr: 受付ループはNamedPipeServerStreamを生成してWaitForConnectionAsyncで接続を待ち、接続が来たらクライアント処理を非同期に切り離してすぐ次の受付に戻ることで、同時接続を素直なコードで捌く
mk["サーバーストリーム生成"] --> wc["WaitForConnectionAsyncで待つ"]
wc --> got["接続到着"]
got --> hd["クライアント処理を非同期に切り離す"]
hd --> mk
図6: 受付ループは「待つ→切り離す→次へ」の回転に徹し、個々のクライアント処理は並行して進む。
5. セキュリティ ── 特権サービスが使うなら必須の4点
名前付きパイプが同一マシンIPCの第一候補である最大の理由はセキュリティモデルですが、それは正しく設定すればの話です。特に「管理者権限のサービス+低権限のUIアプリ」というブローカー構成では、パイプは権限の境界そのものになります。押さえるべきは4点です。
(1) リモートを拒否する。ローカルIPCのつもりのパイプがネットワークから開けるのは、それだけで攻撃面です。CreateNamedPipe で PIPE_REJECT_REMOTE_CLIENTS を指定すれば、リモートクライアントの接続は自動的に拒否されます。5
(2) ACLを明示する。SECURITY_ATTRIBUTES にセキュリティ記述子を渡し、接続を許すユーザー・グループを絞ります。このときクライアントに GENERIC_WRITE を与えてはいけません ── そこに含まれる FILE_CREATE_PIPE_INSTANCE 権により、許可されたクライアント自身が同名のサーバーインスタンスを作って後続の接続を横取りできてしまいます。読み書きは個別の権利で与え、インスタンス作成権は渡さないでください。11
(3) 名前の乗っ取りを防ぐ。パイプ名は早い者勝ちです。悪意あるプロセスが先に同名パイプを作って待ち構えると、クライアントは偽サーバーに接続してしまいます。サーバーは最初のインスタンスの作成時に FILE_FLAG_FIRST_PIPE_INSTANCE を指定して「自分が最初である」ことを保証し、失敗したら乗っ取りを疑って停止します。このフラグは名前を確保する1本目専用で、2本目以降に付けると作成が失敗します。3
(4) クライアントは偽装レベルを必要最小限にする。接続先が偽サーバーだった場合への備えです。クライアントが CreateFile で **SECURITY_SQOS_PRESENT |
SECURITY_IDENTIFICATION** を指定すると、サーバー側はクライアントの身元確認まではできても、その権限を借用して行動することはできなくなります。2 ただしこれは偽装ワークフローとのトレードオフです ── サーバーがクライアント権限で実アクセスを行うブローカー構成では識別レベルでは偽装が成立せず、SECURITY_IMPERSONATION の許可が必要になります。その許可は正規サーバーにつながっている確証とセットが条件です。乗っ取り対策はサーバー側が起動失敗で気づく仕組みにすぎず、正規サービスの不在時に攻撃者が先に同名パイプを作ればクライアントは偽サーバーに接続しえます。サービスの起動保証や接続後の相互認証で接続先を確認できる場合に限って許可してください。 |
サーバー側の身元確認・権限借用が ImpersonateNamedPipeClient です。パイプから要求を読み取った後にこれを呼ぶと、呼び出しスレッドは最後に読んだメッセージの送り主のセキュリティ文脈で動き始めます。クライアントの権限でファイルを開けば、アクセス判定はクライアント基準で行われる ── 特権サービスが「頼まれた操作を、頼んだ人の権限で」実行するための仕組みです。6 使ううえでの絶対条件は戻り値の確認です。偽装に失敗したまま処理を続けると、以後の操作はサーバー自身の高い権限で走ります。公式ドキュメントは「失敗時はクライアントの要求を実行してはならない」と明記しています。作業後の RevertToSelf とあわせて、偽装トークンの記事の作法がそのまま適用されます。
sequenceDiagram
accTitle: 偽装を使った要求処理の流れ
accDescr: サーバーはパイプから要求を読み、ImpersonateNamedPipeClientの成功を確認してからクライアントの権限で操作を行い、RevertToSelfで自分の文脈に戻る。偽装に失敗した場合は要求を実行せず拒否する
participant C as クライアント
participant S as サーバー
C->>S: 要求を送信
S->>S: 要求を読み取る
S->>S: ImpersonateNamedPipeClient
Note over S: 失敗なら要求を実行せず拒否
S->>S: クライアント権限で操作を実行
S->>S: RevertToSelfで元に戻す
S->>C: 結果を応答
図7: 偽装の成功確認と確実なRevertToSelfまで含めてワンセット。失敗時に続行するとサーバー権限で実行される。
flowchart TB
accTitle: 特権サービスのパイプを守る4点
accDescr: サーバー側はリモート拒否・ACLの明示・最初のインスタンス保証で入口を固め、クライアント側は偽装レベルを必要最小限に指定して偽サーバーへの権限借用を防ぐ(サーバーに権限借用をさせない設計なら識別レベルに絞る)
subgraph sv["サーバー側"]
r1["PIPE_REJECT_REMOTE_CLIENTS"]
r2["ACLで接続者を限定"]
r3["FIRST_PIPE_INSTANCE(1本目のみ)"]
end
subgraph cl["クライアント側"]
r4["偽装レベルを必要最小限に指定"]
end
sv --> safe["権限境界としてのパイプ"]
cl --> safe
図8: パイプが権限の境界になる構成では、サーバー側3点+クライアント側1点をセットで実装する。
6. 実務の落とし穴
起動順の競合。サーバーがまだパイプを作っていないタイミングでクライアントが接続に来ると、「パイプが存在しない」エラーになります。クライアント側は「存在しない→少し待って再試行」を織り込みます。逆にサーバー側は、クライアントの起動より前に ConnectNamedPipe で待ち受けを開始しておくのが原則です。8
flowchart TB
accTitle: クライアント接続の再試行フロー
accDescr: CreateFileでパイプを開き、パイプが存在しなければ少し待って再試行し、全インスタンス使用中のERROR_PIPE_BUSYならWaitNamedPipeで空きを待ってから再試行し、成功したら通信に入る
cf["CreateFileで開く"] --> ok{"結果は?"}
ok -->|"成功"| go["通信開始"]
ok -->|"パイプが存在しない"| wait1["少し待つ(サーバー未起動)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["WaitNamedPipeで空きを待つ"]
wait1 --> cf
wnp --> cf
図9: クライアントの接続処理は「存在しない」と「満員」の2種類の失敗を区別して、どちらも再試行に合流させる。
切断の検知。相手が終了するとRead/WriteはERROR_BROKEN_PIPEなどで失敗します。これは異常ではなく通信の日常です。サーバーは切断を検知したらインスタンスを DisconnectNamedPipe して次の接続に備える、クライアントは再接続する ── スリープ復帰の記事で述べた「冪等な再接続」の考え方がここでも通用します。
メッセージサイズの想定。メッセージモードの分割読み(3章)に加え、「1メッセージは最大何バイトか」をプロトコルとして決めておかないと、悪意ある(あるいはバグった)相手からの巨大メッセージでメモリを浪費させられます。上限を決め、超えたら切断が安全です。
書き込みの完了と相手の受領は別。WriteFile の成功は相手のアプリがデータを処理したことを意味しません。確実性が要る操作は応答メッセージで確認する、要求と応答の対応をプロトコルに含める、といった設計で担保します。
7. まとめ
- 名前付きパイプは同一マシンIPCの第一候補。理由はファイルI/Oと同じ扱いやすさと、ACL・偽装というWindowsセキュリティモデルとの統合。
- モード選択は「要求と応答ならメッセージモード、自前フレーミングがあるならバイトモード」。メッセージモードでも分割読み(ERROR_MORE_DATA)対応は必要。
- 複数クライアントはインスタンス複数+overlapped、または.NETの async/await。新規なら.NETの非同期型が素直。
- 権限境界になるパイプでは、リモート拒否・ACL明示・FIRST_PIPE_INSTANCE(1本目のみ)・クライアント側の偽装レベル最小化をセットで。
ImpersonateNamedPipeClientは戻り値確認とRevertToSelfが生命線。- 起動順・切断・メッセージ上限・応答確認という「通信の日常」をプロトコル設計に織り込む。
名前付きパイプは古いAPIですが、「同一マシンで、Windowsのアカウント境界を尊重しながらプロセス同士を話させる」という用途では、いまだに最も筋のよい道具です。設計判断のポイントは本記事の範囲でほぼ尽きています。あとは自分のプロトコルを1枚の紙に書き出してから実装に入ってください。
関連記事
- Windowsのプロセス間通信をどう選ぶか ── 名前付きパイプ / TCP / gRPC / 共有メモリ / COM 判断表
- Windowsアプリで「管理者権限が必要な処理だけ」を分離する具体的な書き方
- Windowsの偽装トークンを正しく扱う ── スレッド単位の権限借用と安全な戻し方
- Windows I/Oの深層(第2回) ── 同期I/Oと非同期I/O:OVERLAPPEDの本当の意味
- 共有メモリの落とし穴と実務ベストプラクティス
関連する相談領域
合同会社小村ソフトでは、サービスとUIアプリの分離・管理者権限の分離といったプロセス間通信を伴う設計と実装、既存IPC(共有メモリ・独自ソケット・COMなど)の名前付きパイプへの置き換え、特権サービスのパイプ通信のセキュリティレビューを扱っています。プロトコル設計の壁打ちからでもご相談ください。
参考リンク
-
Microsoft Learn, Named Pipes. 名前付きパイプがパイプサーバーと1つ以上のパイプクライアントの間の一方向または双方向の通信路であること、すべてのインスタンスが同じ名前を共有しつつ独立したバッファとハンドルを持つこと、ローカルとリモートのプロセスから利用できることについて。 ↩ ↩2
-
Microsoft Learn, Impersonating a Named Pipe Client. 偽装によりサーバースレッドがクライアントの権限の範囲で動作できること、既定の偽装レベルがSecurityImpersonationであること、クライアントがCreateFile時にSECURITY_SQOS_PRESENTフラグで偽装レベルを制御できる(SECURITY_IDENTIFICATIONなら身元確認のみ許す)ことについて。 ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). パイプの方向(インバウンド・アウトバウンド・双方向)、バイト型とメッセージ型(PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE)および読み取りモード(PIPE_READMODE_MESSAGE)、最大インスタンス数(PIPE_UNLIMITED_INSTANCES)、FILE_FLAG_OVERLAPPEDによる非同期モード、FILE_FLAG_FIRST_PIPE_INSTANCEによる最初のインスタンス保証、WaitNamedPipe用の既定タイムアウトについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. 単一スレッドのサーバーがoverlapped操作で複数クライアントとの同時接続を処理する公式サンプルについて。各インスタンスのOVERLAPPED構造体とイベントをWaitForMultipleObjectsで待ち、完了したインスタンスの状態機械を進める構成、GetOverlappedResultによる保留I/Oの完了確認について。 ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). PIPE_ACCEPT_REMOTE_CLIENTS(リモート接続を受け付けセキュリティ記述子で検査する)とPIPE_REJECT_REMOTE_CLIENTS(リモートクライアントの接続を自動的に拒否する)の2つのリモートクライアントモードについて。 ↩ ↩2
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). サーバー側スレッドがパイプから最後に読んだメッセージのクライアントのセキュリティ文脈で偽装を開始すること、完了後はRevertToSelfで戻すこと、偽装に失敗した場合に処理を続けるとサーバープロセス自身の(特権的な)文脈で実行されるため、戻り値を必ず確認し失敗時はクライアントの要求を実行してはならないことについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Client. クライアントがCreateFileでパイプを開くこと、全インスタンスが使用中の場合はERROR_PIPE_BUSYとなりWaitNamedPipeで空きを待つこと、開いたハンドルの既定がバイト読み取り・ブロッキング・非overlappedでありSetNamedPipeHandleStateでメッセージ読み取りモードへ変更できることについて。 ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. ReadFileEx / WriteFileExによるoverlapped操作、PeekNamedPipeによる取り出さない読み取り、メッセージ型双方向パイプで要求送信と応答受信を1回で行うTransactNamedPipe、クライアント起動前のブロッキング読み取りが競合を招きうることについて。 ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). NamedPipeServerStream / NamedPipeClientStreamによる接続と読み書き、PipeTransmissionMode.Messageによるメッセージ単位の転送、非同期メソッドによる複数クライアントの処理について。 ↩ ↩2
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Asynchronousによる非同期I/Oの有効化と、CurrentUserOnlyにより同一ユーザー(かつ同一の昇格レベル)のプロセスとの接続だけを許可できることについて。 ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. 名前付きパイプのアクセス権の構成、GENERIC_WRITEにFILE_CREATE_PIPE_INSTANCEが含まれるため、クライアントに汎用書き込みを与えるとサーバーインスタンスの作成まで許してしまうこと、データの読み書きは個別のアクセス権で付与すべきことについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Win32スレッドプールAPI ── CreateThreadpoolWorkで「スレッドを作らない」並行処理
ネイティブコードでCreateThreadを乱立させていませんか。Vistaで刷新されたWin32スレッドプールAPIのwork・timer・wait・ioの4オブジェクト、クリーンアップグループ、コールバックでの禁止事項までを一次情報にもとづいて解説します。
DllMainとローダーロック ── 「DLLの初期化で何もするな」と言われる本当の理由
DllMainでLoadLibraryやスレッド同期をしてはいけないのはなぜか。全DLL通知を直列化するローダーロックの仕組みから、デッドロックが成立する典型シナリオ、遅延初期化などの正しい設計、ハング調査の手順までを一次情報で解説します。
スプリアスウェイクアップ ── 条件変数が「通知なしに目覚める」理由とWindowsでの正しい待ち方
条件変数のwaitは通知が来ていなくても目覚めることがあります(スプリアスウェイクアップ)。仕様がそれを許す理由をWindowsの実装から解き明かし、whileと述語で書く正しい待ち方をWin32・C++・C#のコードで示します。
スリープ復帰で壊れるアプリ ── 電源イベントの仕組みと復帰に強い業務アプリの作り方
ノートPCを開いたら業務アプリの通信が切れていた――原因はスリープを想定しない設計です。WM_POWERBROADCASTによる通知の流れ、Modern Standbyの挙動、切断・再接続の設計、スリープ抑止と調査コマンドまでを一次情報で解説します。
「応答なし」の正体 ── Windowsがアプリを「固まった」と判定する仕組みと、固まらない設計
Windowsの「応答なし」は、ウィンドウが5秒間メッセージを取り出さないとOSが判定し、幽霊ウィンドウに差し替える仕組みです。判定の内部動作から、固まる定番原因、UIスレッドから重い処理を追い出す設計、ハングの調査手順まで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 名前付きパイプとTCP(localhostソケット)はどう使い分ければよいですか?
- 同一マシン内のプロセス間通信であれば、名前付きパイプが第一候補です。理由はセキュリティモデルにあります。パイプはWindowsのセキュリティ記述子(ACL)で「誰が接続できるか」をOSレベルで制御でき、サーバー側はImpersonateNamedPipeClientで接続相手のWindowsアカウントを確認・借用できます。localhostのTCPポートには誰でも接続でき、相手が誰かを自前の認証で確かめる必要があるのと対照的です。一方、将来リモート通信に発展する可能性が高い、他OSのプロセスとも話す、gRPCなど既存のプロトコル資産を使いたい、といった場合はTCPベースが有利です。この判断はプロセス間通信の判断表の記事でも整理しています。
- バイトモードとメッセージモードはどちらを使うべきですか?
- 「1回の書き込み=1件の意味の単位」として扱いたいならメッセージモード(PIPE_TYPE_MESSAGE+PIPE_READMODE_MESSAGE)が便利です。受信側は送信側が書いた単位で読み取れるため、自前で区切りを管理する必要がありません。バイトモードはTCPと同じ「切れ目のないバイト列」で、長さプレフィックスなどの区切り(フレーミング)を自分で設計する必要があります。既にフレーミングを持つプロトコル(例えば長さ付きのシリアライズ形式)を流すならバイトモードで問題ありません。注意点として、メッセージモードでも受信バッファがメッセージより小さいと分割読み(ERROR_MORE_DATA)が発生するため、その処理は必要です。また読み取りモードはハンドルごとの設定で、CreateNamedPipeで設定されるのはサーバー側だけです。クライアント側はCreateFileの後にSetNamedPipeHandleStateでPIPE_READMODE_MESSAGEを指定する必要があります。.NETではサーバーはPipeTransmissionMode.Messageを指定し、クライアントは接続後にNamedPipeClientStream.ReadModeをMessageに設定します。
- 複数のクライアントを同時に相手にするサーバーはどう作りますか?
- 名前付きパイプは同じ名前で複数のインスタンスを作成でき、1インスタンスが1クライアントを受け持ちます。構成は2通りあります。ひとつはクライアントごとにスレッドを割り当てる同期型で、実装は素直ですがクライアント数分のスレッドを消費します。もうひとつはFILE_FLAG_OVERLAPPEDで非同期I/Oにし、少数のスレッドで全インスタンスのConnectNamedPipe・ReadFile・WriteFileを捌く形で、Microsoftの公式サンプルにも単一スレッドで複数インスタンスを処理する実装が示されています。.NETならNamedPipeServerStreamのWaitForConnectionAsyncとasync/awaitで、非同期型を同期型並みの素直さで書けます。新規実装で特に理由がなければ.NETの非同期型をおすすめします。
- 名前付きパイプのセキュリティで最低限やるべきことは何ですか?
- 4点あります。第一に、リモート接続が不要ならPIPE_REJECT_REMOTE_CLIENTSを指定してネットワーク越しの接続を明示的に拒否する。第二に、SECURITY_ATTRIBUTESで適切なACLを設定し、接続してよいユーザー・グループを絞る(既定のACLは用途によっては緩すぎます)。第三に、最初のインスタンスの作成時にFILE_FLAG_FIRST_PIPE_INSTANCEを指定し、同名パイプを先に作られる「名前の乗っ取り」を検出する(2本目以降のインスタンスには付けません)。第四に、サーバーに身元確認だけをさせたいクライアントは、CreateFileでSECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATIONを指定して、偽サーバーに自分の権限を借用(偽装)されないよう制限する。サーバーがクライアント権限で実アクセスを行うブローカー構成では偽装の許可が必要になるため、この制限は「サーバーに権限借用をさせない設計かどうか」で使い分けます。
- ImpersonateNamedPipeClientを使うときの注意点はありますか?
- 最重要なのは戻り値の確認です。偽装に失敗したのに処理を続けると、以後の操作はサーバープロセス自身の(多くは高い)権限で実行されてしまい、クライアントに許されないはずの操作が通ってしまいます。公式ドキュメントも、失敗時はクライアントの要求を実行してはならないと明記しています。また、偽装は「最後にパイプから読んだメッセージ」の文脈で行われるため、必ず何かを読み取ってから呼ぶこと、作業が終わったらRevertToSelfで確実に元の文脈へ戻すことも必要です。偽装まわりの仕組み(トークン、偽装レベル、SeImpersonatePrivilege)は偽装トークンの記事で詳しく解説しています。