Skip to content

MS_InterProcessCommunication

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

プロセス間通信

概要

プロセス間通信についてまとめた。

補足(IPC の選択軸): 手段が多いので、
**先に「何で選ぶか」**を整理しておくと読みやすい。

内容
同一マシンか、ネットワーク越しか 共有メモリ・パイプは同一マシン限定。TCP は跨げる
親子関係があるか 匿名パイプは親子限定。名前付きなら無関係でもよい
速度 共有メモリ > パイプ > ローカル TCP
データ量 大量・高頻度なら共有メモリ/小さなメッセージならパイプ
方向 一方向か双方向か
プラットフォーム Windows 専用か、Linux でも動くか ★
【選択の目安】
   親プロセスが子を起動して制御したい      → 匿名パイプ / 標準入出力
   常駐サービスと GUI が通信する(同一 PC) → 名前付きパイプ
   大量データを高速に共有する               → 共有メモリ
   別マシンにも展開する可能性がある         → 【TCP / HTTP / gRPC】★
   Linux コンテナでも動かす                 → TCP / Unix ドメイン ソケット

最後の 2 つが現在では重要である。
「同一マシン前提の IPC」はコンテナ化・クラウド移行で足枷になるため、
迷ったら TCP(あるいは HTTP / gRPC)にしておく方が寿命が長い。

詳細

基本

  • 共有メモリ、パイプ(名前付き/匿名)、TCP, UDP などがある。
  • 子プロセスとの通信なら匿名パイプを使用すると良い。

共有メモリ

  • 内部的には、ページングファイルに支持されたメモリマップド・ファイルを使用する。
  • 実装については、下記の「SharedMem.xaml.cs」を参照のこと。
    ただし、Touryo.Infrastructure.Public.Util.SharedMemory に依存している。

移行メモ(「ページングファイルに支持された」): 原文の表現は
**「ページ ファイルによってバックされた(backed by the page file)」**の
訳と読める。内容は正確である。

【メモリ マップト ファイルの 2 形態】

 ① 【ファイルにバックされる】
     実ファイルをメモリに写像する
     → 大きなファイルの一部だけを読み書きできる

 ② 【ページ ファイルにバックされる】★ 共有メモリはこちら
     実ファイルを持たず、名前だけを付ける
     → プロセス間で「名前」を共有すれば、同じ領域を見られる
     → プロセスが全部終了すると消える
// .NET での共有メモリ(System.IO.MemoryMappedFiles)
// 書く側
using var mmf = MemoryMappedFile.CreateOrOpen("MySharedMem", 1024);
using var acc = mmf.CreateViewAccessor();
acc.Write(0, 12345);

// 読む側(別プロセス)
using var mmf2 = MemoryMappedFile.OpenExisting("MySharedMem");
using var acc2 = mmf2.CreateViewAccessor();
Console.WriteLine(acc2.ReadInt32(0));

共有メモリ固有の注意点:

① 【同期を自前で行う必要がある】★
     共有メモリ自体は「同時に書いたらどうなるか」を保証しない
     → Mutex / Semaphore(名前付き)で排他する

② 【セッション分離】([(ログオン)セッション](MS_LogonSession))
     サービス(Session 0)とユーザー アプリで名前空間が違う
     → "Global\\MySharedMem" と Global 名前空間を明示する
     → [Windowsの外字](MS_WindowsGaiji) の Session 0 問題と同根

③ 【クロス プラットフォーム】
     名前付き共有メモリは .NET Core の Linux では未対応
     → Linux でも動かすなら、この方式は選べない

パイプ

  • 名前付きパイプ

    • 名前付きパイプを使用して、プロセス間通信を行う場合、
      パイプ・オブジェクトの始点と終端を2プロセス間で持つ。

    • このとき、パイプ・オブジェクトを ID のようなもので
      識別するのが、名前付きパイプになる。

    • 従って、名前付きパイプの場合、
      2プロセスがパイプ名を共有している必要がある。

    • 詳細については、下記の「NdPipe.xaml.cs」を参照のこと。

  • 匿名パイプ

補足(原文の推測は概ね正しい): 「内部(Win32)的に、
DuplicateHandle 関数と思われる」という推測は、方向として正しい
ただし、もう少し正確にしておく。

【HandleInheritability.Inheritable の実体】

 ① ハンドルを作る際、SECURITY_ATTRIBUTES.bInheritHandle = TRUE を指定
     → 「このハンドルは子に継承できる」という印を付ける

 ② CreateProcess を bInheritHandles = TRUE で呼ぶ
     → 【OS が継承可能なハンドルをまとめて複製する】★

 → DuplicateHandle を明示的に呼ぶわけではないが、
   OS 内部で行われるのは実質的に同じ複製処理である

fork との違いも補足しておく。

【Unix の fork】
   プロセスを丸ごと複製する
     → ファイル記述子も【自動的に引き継がれる】
     → パイプを作ってから fork するだけでよい

【Windows の CreateProcess】
   fork に相当するものがない(新規プロセスを作る)
     → ハンドルを【明示的に継承させる】必要がある
     → さらに、子プロセスに「どのハンドルか」を教える必要がある ★
        (通常はコマンドライン引数で渡す)
// .NET での匿名パイプ(親)
using var pipe = new AnonymousPipeServerStream(
    PipeDirection.Out, HandleInheritability.Inheritable);

var psi = new ProcessStartInfo("Child.exe",
    pipe.GetClientHandleAsString());     // ← ハンドル値を引数で渡す ★
var child = Process.Start(psi);

using var sw = new StreamWriter(pipe) { AutoFlush = true };
sw.WriteLine("hello");
pipe.DisposeLocalCopyOfClientHandle();  // ← これを忘れると子が終了を検知できない

DisposeLocalCopyOfClientHandle() の呼び忘れ
典型的な不具合で、
子プロセスが「まだ書き込む人がいる」と誤認して待ち続ける

UDP

  • コネクションレス型の通信
  • 実装については、下記の「UDP.xaml.cs」を参照のこと。

TCP

  • コネクション型の通信
  • 実装については、下記の「TCP.xaml.cs」を参照のこと。

補足(同一マシンで TCP を使う選択): 「プロセス間通信」の文脈で
TCP を挙げるのは一見遠回りに見えるが、
現在はむしろ有力な選択肢である。

【ループバック TCP の利点】
   ・【同一マシンでも別マシンでも同じコードで動く】★
      → 後からサービスを別サーバに移せる
      → コンテナに分割できる
   ・OS を問わない(Windows / Linux / macOS)
   ・既存のツール(telnet、curl、Wireshark)で調査できる
   ・HTTP / gRPC といった上位プロトコルが使える

【欠点】
   ・共有メモリ・パイプより遅い(とはいえ十分速い)
   ・ポート番号の管理が要る(競合、ファイアウォール)

Unix ドメイン ソケットは、その中間である。

// .NET 5 以降、Windows でも Unix ドメイン ソケットが使える
var ep = new UnixDomainSocketEndPoint("/tmp/myapp.sock");
using var socket = new Socket(AddressFamily.Unix, SocketType.Stream,
                              ProtocolType.Unspecified);
socket.Connect(ep);
【Unix ドメイン ソケットの位置付け】
   ・TCP と同じ API(Socket)で書ける
   ・ネットワーク スタックを通らないので【速い】
   ・ファイル システムの権限で【アクセス制御できる】★
   ・Windows 10 / Server 2019 以降で利用可能
   → 同一マシン限定で良いなら、名前付きパイプの代替になる

その他

  • DCE/RPC

    • DCOM
    • .NET Remoting
  • Web サービス

    • XML-RPC
    • JSON-RPC
  • WCF (Windows Communication Foundation)

補足(この一覧の現況): 挙げられているものは、
ほぼすべて非推奨または終了
している。

技術 現況
DCOM 動くが新規採用しない。ファイアウォール越えが困難
.NET Remoting 非推奨。.NET Core 以降存在しない
XML-RPC 歴史的
JSON-RPC 一部で現役(Language Server Protocol 等)★
WCF .NET Core 以降なし(クライアントのみ部分対応/CoreWCF が受け皿)

現在の選択肢:

手段 向く場面
HTTP + JSON(REST) 最も汎用的。どこからでも呼べる
gRPC サービス間通信。高速・型安全・双方向ストリーミング ★
メッセージ キュー(Service Bus、RabbitMQ) 非同期・疎結合。落ちても失われない
SignalR サーバからクライアントへのプッシュ
名前付きパイプ / UDS 同一マシン内の高速通信
【.NET Remoting / WCF からの移行先】
   社内サービス間、性能重視     → 【gRPC】
   Web ブラウザからも呼ぶ       → REST(+ OpenAPI)
   非同期・信頼性重視           → メッセージ キュー
   既存の SOAP 契約を残したい   → CoreWCF
// gRPC は .NET でファースト クラスに扱える
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "world" });

gRPC は Unix ドメイン ソケットや名前付きパイプ上でも動くため、
**「同一マシンでは高速に、別マシンでは TCP で」**という
使い分けが 1 つの API でできる。

補足(WM_COPYDATA による IPC): ウィンドウ メッセージも
古典的な IPC 手段である。

【WM_COPYDATA】
   SendMessage で【任意のバイト列を別プロセスに渡せる】
     → OS がプロセス境界を越えてデータをコピーしてくれる

【制約】
   ・受信側に【ウィンドウとメッセージ ループ】が要る
   ・【同期呼び出し】(SendMessage)なので、相手が固まると自分も固まる ★
   ・UIPI(整合性レベル)により、
     管理者権限プロセス → 標準権限プロセスへは届くが、逆は弾かれる
      → ChangeWindowMessageFilterEx で明示的に許可が要る
   ・Session 0 分離により、サービスからは届かない

「既に起動しているインスタンスに引数を渡す」(多重起動の抑止)
といった用途では今も使われるが、
新規に汎用の IPC として選ぶ理由はない

POSIX

  • UNIX ドメインソケット
  • POSIX メッセージキュー

補足(.NET から POSIX の IPC を使う): .NET のクロスプラットフォーム対応
により、Linux 上で .NET を動かす場面が増えたため補足する。

POSIX の機構 .NET からの利用
UNIX ドメイン ソケット UnixDomainSocketEndPoint(標準。Windows でも可)★
POSIX メッセージ キュー 標準 API なし(P/Invoke か、別手段)
POSIX 共有メモリ MemoryMappedFileファイル バックのみ(名前付きは不可)
シグナル PosixSignalRegistration(.NET 6 以降)★
パイプ(FIFO) FileStream で開ける
// .NET 6 以降:SIGTERM を受けて綺麗に終了する(コンテナで必須)
using var reg = PosixSignalRegistration.Create(PosixSignal.SIGTERM, ctx =>
{
    ctx.Cancel = true;      // 既定の終了処理を止める
    _shutdown.Cancel();     // 自前の後始末を行う
});
【コンテナ運用での重要性】
   ・Kubernetes は停止時に【SIGTERM】を送る
   ・受け取って処理中のリクエストを捌き切ってから終了する
      → これをしないと【処理中のリクエストが切れる】★
   ・ASP.NET Core の汎用ホストは【既定で対応済み】
     (IHostApplicationLifetime)

参考

POSIX

Wikipedia

その他

OpenTouryoProject/SampleProgram · GitHub

InterProcComm at master

https://github.com/OpenTouryoProject/SampleProgram/tree/master/Other/InterProcComm

PipesFamilyHouse at master

https://github.com/OpenTouryoProject/SampleProgram/tree/master/Other/PipesFamilyHouse

  • AnonymousPipe(匿名パイプによる親子プロセス間通信)
  • StdIOAndPipe(標準入出力による親子プロセス間通信)

補足(標準入出力による IPC は今も現役): PipesFamilyHouse
StdIOAndPipe(標準入出力)方式は、
現在最もよく使われる IPC の 1 つである。

【標準入出力 IPC が使われている例】
   ・Language Server Protocol(VS Code ↔ 言語サーバ)★
   ・Model Context Protocol(MCP)
   ・多くの CLI ツールの連携(パイプで繋ぐ)
      → [自作CUI(CLI)の話](MS_BuildingYourOwnCLI) 参照
var psi = new ProcessStartInfo("child.exe")
{
    RedirectStandardInput = true,
    RedirectStandardOutput = true,
    RedirectStandardError  = true,      // ← stderr も必ず読む ★
    UseShellExecute = false,
    StandardOutputEncoding = System.Text.Encoding.UTF8,  // ← 文字コード
};
【定番の落とし穴】
 ① 【stderr を読まないとデッドロックする】
     子が stderr に大量出力 → バッファが埋まる → 子が止まる
     → 親は stdout を待ち続ける → 相互に停止 ★
     → 必ず両方を【非同期で読む】(BeginOutputReadLine 等)

 ② 【文字コード】
     既定はコンソールのコードページ(日本語なら CP932)
     → UTF-8 を明示する([アプリケーションのUnicode化](MS_ApplicationUnicodeMigration))

 ③ 【終了を待つ順序】
     WaitForExit の前に、出力の読み切りを完了させる

Microsoft Learn


Tags: 移行, プログラミング, その他、開発の色々, .NET開発

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally