Windows 的行程間通訊該怎麼選 ── 具名管道 / TCP / gRPC / 共享記憶體 / COM 判斷表
· 小村 豪 · 行程間通訊, 具名管道, Windows, .NET, C#, gRPC, 共享記憶體, COM, 設計, 判斷表, 技術諮詢
「把 UI 和服務分開之後,中間的通訊該怎麼辦」「想從 32bit 的應用程式使用 64bit DLL 的功能」「想讓另一個行程的量測引擎把資料傳到畫面上」。把應用程式拆成多個行程的設計,經常是應付堅固性、權限分離、位元數問題的現實解法,但拆開的那一刻,「中間的通訊要怎麼做」這個選擇就一定會出現。
這個部落格目前為止已經寫過個別的通訊手段,像是共享記憶體的陷阱、TCP 的訊框化、子行程的安全處理、檔案協作與鎖定。不過「到底該選哪一種手段」這個總論篇還沒有寫過。這篇文章會把 Windows 行程間通訊(IPC)的主要選項──檔案協作・具名管道・本機 TCP・gRPC・共享記憶體・COM──的強項與陷阱,用這個部落格慣用的判斷表格式整理出來。
1. 先講結論
- 同一台電腦內的請求-回應(送出指令並取得結果)的話,具名管道是首選。 是 OS 標準功能、不需要連接埠,且與 Windows 的存取控制整合,在 .NET 中可以用
System.IO.Pipes直接寫出來。1 - 未來如果有可能跨越網路,一開始就選本機 TCP 或 gRPC。 從管道換成 socket 往往不是「之後小改一下」就能解決的差異。不過純 TCP 是位元組串流,因此訊框化的設計是必須的。
- 當服務或呼叫的種類增加,自訂協定的維護開始變成負擔時,就用 gRPC。 可以取得 proto 定義的結構與程式碼產生、雙向串流,.NET 8 以後 ASP.NET Core(Kestrel)可以把具名管道當作傳輸層。2
- 只有大量・高頻率的資料(影像畫面、波形)才用共享記憶體。 速度最快,但同步機制要全部自己設計,因此原則是不要連控制訊息都放進共享記憶體。3
- 鬆散耦合・非同步就夠、且想留下稽核記錄的協作,現在依然以檔案協作為優。 只是成敗取決於排他控制的設計。
- COM(跨行程)不會是新設計的首選,但在 VBA・其他語言的使用情境,或是 32bit/64bit 橋接的場合,現在依然是現役工具。4
- 不管選哪一種手段,訊息的分界・版本號・逾時・重新連線這類共同的設計課題都逃不掉(第 6 章)。花在這裡的時間,應該和選手段一樣多。
2. 選項的底細
2.1 檔案協作 ── 鬆散耦合・非同步・可稽核
「A 把檔案放到輸出資料夾,B 拿去處理」是經典的協作方式。雙方不需要同時啟動,處理過程會留下檔案作為證據,發生問題時人可以直接看檔案、修改後重新送入。在不要求即時性的批次式協作中,這種方式至今依然強大。
陷阱幾乎集中在一點:排他控制。「讀到寫入中途的檔案」「兩個處理搶同一個檔案」是常見事故,需要遵循先用暫存檔名寫入再重新命名之類的定石。詳細內容整理在《檔案協作與鎖定的最佳實踐》中,選擇這種方式時務必參考。不適合需要即時回應的互動式通訊,或是每秒發生幾十次的交換。
2.2 具名管道 ── 同一台電腦 IPC 的本命
具名管道是 Windows 在核心層提供的雙向通訊路徑,是同一台電腦內用戶端・伺服器型 IPC 的本命。在 .NET 中以 NamedPipeServerStream / NamedPipeClientStream 處理,一個管道名稱可以讓多個用戶端連線。5 和 TCP 不同,不需要管理連接埠,也不會被防火牆擋下。
以下列出實務上該掌握的重點。
- 可以使用訊息模式。 指定
PipeTransmissionMode.Message後,一次寫入就會以一則訊息為單位切分並送達。這讓 OS 代勞了 TCP 中必須自己實作的 length 前置長度等訊框化工作,是實務上很大的優點(接收端需要用IsMessageComplete讀完一則訊息。參見第 5 章)。 - 預設存取權限出乎意料地寬鬆。 管道的預設安全性描述元會給 LocalSystem・管理員・建立者完整控制權,但同時也允許 Everyone 與匿名帳戶讀取。6 如果是同一使用者的行程之間,只要加上
PipeOptions.CurrentUserOnly就能強制「只和同一使用者建立的對象連線」,建議把這個當作預設作法。7 不同帳戶之間(例如和服務通訊)則要用PipeSecurity明確指定 ACL。 - 管道名稱位於所有人都看得到的命名空間中。 管道名稱放在
\\.\pipe\底下單一的命名空間裡,同一台電腦上其他使用者的行程也能看到。這裡要注意的是名稱佔用(squatting)。如果惡意行程先用相同名稱建立伺服器並等待連線,用戶端就會連到那邊去。伺服器端可以指定PipeOptions.FirstPipeInstance,讓已有同名管道時直接失敗;用戶端則可以在連線後驗證管道的擁有者帳戶。在有不同權限使用者共用同一台電腦時,應該加入這類對策。8 - 可以進行跨越權限邊界的通訊。 作為 UAC 分離出的「標準權限 UI + 管理員權限中介(broker)」之間的通訊路徑,也是常見做法。不過這種架構下無法使用
CurrentUserOnly(因為即使是同一使用者,也要求提升層級必須一致7)。需要明確設計 ACL,具體做法已在《管理員權限的分離(broker)》中詳述。
弱點是實務上不太適合跨機器通訊(規格上可行,但運維上限制很多),也不容易用於和非 Windows 系統的互連。如果看得到這類需求,就選 TCP / gRPC。
2.3 本機 TCP ── 跨語言・跨 OS 的通用性
對 localhost 的 TCP 連線,幾乎能和任何語言・執行環境・OS 溝通,是最通用的 IPC。像「Linux 上的分析引擎與 Windows 的 UI」這種混合架構,首先會考慮 TCP(或架在其上的 HTTP/gRPC)。
要注意的地方有 3 點。
- 它是位元組串流。 TCP 沒有「按送出的單位到達」這種性質。
Send送出的 3 則訊息在一次Receive中被串接送達,或是一則訊息被拆成好幾次送達,都是正常現象。必須在應用層自行設計 length 前置長度之類的訊框化,省略這一步的程式碼只是「碰巧能動」而已。詳細內容請參考《以為 TCP 的 Send 單位可以對應 Receive 是一種誤解》。 - 收斂待接聽的公開範圍。 即使原本只打算做同一台電腦內的通訊,只要用
0.0.0.0待接聽,網路上其他電腦就能連進來。原則上本機 IPC 應該綁定在127.0.0.1(loopback)上。即便如此,同一台電腦上的其他使用者依然能連線,所以如果需要確認對方身分,就要在應用層加上驗證。這一點和管道那種與 OS 整合的存取控制有明顯差異。 - 連接埠的維運會一直跟著你。 固定連接埠可能和其他軟體衝突,防火牆產品也可能把它偵測為「可疑的待接聽」。應該從一開始就把變更連接埠號碼的手段納入設計。
2.4 gRPC ── 買的是結構定義和程式碼產生
和純 socket + 自訂協定相比,gRPC 提供的與其說是通訊本身,不如說是開發的框架。只要用 proto 檔案定義服務與訊息,序列化・訊框化・用戶端/伺服器程式碼就會全部產生出來,雙向串流(伺服器主動推播通知)也能像語言內建功能一樣撰寫。當你進入「每次訊息增加就要修改自訂協定的 switch 陳述式和文件」的階段,這個框架就能發揮作用。
.NET 8 以後,ASP.NET Core(Kestrel)不只支援 TCP,還直接支援 Unix 網域 socket 與具名管道作為傳輸層。8 伺服器端只需要呼叫 ListenNamedPipe,也能設定 PipeSecurity 的存取控制。2 「通訊路徑維持管道、免連接埠・整合 ACL,協定層則用 gRPC」這種組合,在 UI + 服務分離(第 4 章)中是很有力的選項。
另一方面,對於小規模的工具間通訊,這往往會過度設計。建立 gRPC 伺服器意味著要扛起 ASP.NET Core 的主機基礎設施,會增加部署物、依賴關係與啟動成本。如果指令只有 2〜3 種的工具之間,具名管道 + JSON(第 5 章)的總成本會更低,請保有這條分界線。等到「proto 裡要寫的訊息超過 10 個」「本質上需要串流通知」「對方不是 .NET」三者之一成立後再導入也不遲。
2.5 共享記憶體(記憶體對應檔案) ── 最快,但同步全部要自己來
同一台電腦內最快的 IPC 是共享記憶體。在 .NET 中可以用 MemoryMappedFile.CreateNew 建立具名的共享記憶體,讓多個行程直接讀寫同一段位元組序列。3 因為不涉及複製或序列化,對影像畫面或波形資料這類「又大又快」的資料,幾乎可以說是唯一的選擇,效能表現也確實出色。
不過,共享記憶體並不是「快速的管道」。看到的只是相同的位元組序列,完全不提供任何同步機制。 不讀取寫入中資料的機制、偵測對方存活狀態、一方異常結束後的復原──全部都要自己設計。連環形緩衝區架構與版面配置版本管理都包含在內的設計論,整理在《共享記憶體的陷阱與實務最佳實踐》這篇文章中。
實務上的使用場合很明確,就是只把資料面(data plane)放上共享記憶體,控制面(control plane,如開始・停止・變更設定)則交給具名管道等其他通道的架構(第 4 章的架構 3)。一旦連開始・停止・變更設定之類的控制訊息都想硬塞進共享記憶體,就是該重新檢視設計的訊號。
2.6 COM(跨行程) ── 不是新設計的首選,但仍有現役的場合
COM 的跨行程伺服器(EXE 伺服器),是 Windows 很早就有的「像呼叫本機函式一樣呼叫另一個行程裡的物件」機制。4 在新的應用程式間通訊中,已經不會把它當作首選,但在下列情境中依然實用。
- 32bit/64bit 橋接:32bit 應用程式無法把 64bit 的 DLL(或反過來)載入同一個行程,但跨行程 COM 可以跨越這個邊界。COM 基礎架構會代勞封送處理,呼叫端幾乎不用改程式碼。實際案例寫在《32bit→64bit COM 橋接實例》中。
- VBA・其他語言的使用:想讓 Excel VBA 之類的舊執行環境呼叫 .NET 的功能時,把它以 COM 公開,現在依然是最直接的路徑。
- 與既有 COM 資產的協作:如果對方只能用 COM 溝通,這邊也用 COM 溝通是最快的做法(COM 本身的說明請見《COM・ActiveX・OCX 是什麼》)。
讓人猶豫是否新採用的原因,是伴隨登錄檔登錄的部署麻煩、介面設計與參考計數的學習成本,以及發生問題時調查的困難度。請先和「如果目的是 32/64 橋接,就把 64bit 端做成單純的輔助行程,用具名管道溝通」這個替代方案(第 4 章的架構 2)比較之後再決定。
2.7 古典的手段 ── 新設計不會選
Windows 還有 WM_COPYDATA(用視窗訊息傳資料)、剪貼簿、DDE、mailslot 這類 IPC 手段,官方的 IPC 概觀頁面現在依然列著它們。4 只是這些手段大多以視窗存在為前提,或是正在走向淘汰(遠端 mailslot 正處於淘汰程序中),新設計幾乎沒有理由選擇它們。維護既有應用程式時遇到能認出來就夠了。
3. 判斷表
| 觀點 | 檔案 | 具名管道 | 本機 TCP | gRPC | 共享記憶體 | COM |
|---|---|---|---|---|---|---|
| 通訊範圍 | 用共享資料夾可跨機器 | 同一台電腦是實用範圍 | 可跨機器 | 可跨機器 | 限同一台電腦 | 同一台電腦是實用範圍 |
| 通訊模型 | 檔案交接(非同步) | 串流+訊息模式 | 位元組串流 | RPC+串流 | 共享狀態 | 方法呼叫 |
| 伺服器→用戶端通知 | ✕(輪詢) | ○ | ○ | ◎(雙向串流) | △(需搭配事件) | △(能做但複雜) |
| 跨越權限邊界(UAC・服務) | ○(資料夾 ACL) | ◎(PipeSecurity) | △(驗證要自己做) | △〜○(用管道傳輸則為◎) | ○(可設 ACL,但設計難) | ○ |
| 32/64・語言混用 | ◎ | ○ | ◎ | ◎(從 proto 產生各語言) | △(必須設計 ABI) | ○(擅長橋接) |
| 實作成本 | 低 | 低〜中 | 中(訊框化要自己做) | 中(要導入基礎設施) | 高 | 高(對新設計而言) |
| 除錯難易度 | ◎(內容留存在檔案中) | ○ | ○(可擷取封包) | △(HTTP/2+二進位) | △(症狀較劇烈) | △ |
| 吞吐量/延遲 | 低 | 中〜高 | 中 | 中 | ◎ | 中 |
補充一點。正確的用法不是選「◎ 最多的手段」,而是只看影響到需求的那幾行,用排除法收斂範圍。例如一旦決定「每秒 30 個畫面的影像」,資料面就只能選共享記憶體。
4. 典型架構範例
把判斷表套用到個別需求上,實務中常常會收斂到下面 3 種架構。
架構 1:UI 應用程式 + Windows 服務的分離。 把常駐處理或需要特權的處理放在服務中,UI 則是普通的使用者行程(服務端的做法請參考同日公開的《Windows 服務文章》)。通訊以具名管道為本命,協定從 JSON 訊息+訊息模式(或位元組模式+length 前置長度)開始是穩健的做法,等呼叫種類增多後,也有移轉到 gRPC 的具名管道傳輸層的路可以走。2 因為服務是用不同帳戶(如 LocalService)執行,無法使用 CurrentUserOnly,重點是要用 PipeSecurity 明確指定「允許 Users 讀寫、拒絕遠端」之類的 ACL。
架構 2:32bit 應用程式 → 64bit 功能的橋接。 想從 32bit 的既有應用程式使用只能在 64bit 上執行的 DLL・驅動程式 SDK 時,要把 64bit 端切到別的行程。實作方式有兩種:(a) 建立 64bit 輔助行程並用具名管道溝通,(b) 做成 64bit 的跨行程 COM 伺服器。如果呼叫端是 VBA 或舊語言,(b) 比較自然;如果雙方都是 C#,(a) 在部署與除錯上都更輕鬆。採用 (a) 時,輔助行程的啟動・監控・親子連動結束,請直接套用《安全地處理子行程》中提到的 Job Object 模式。
架構 3:量測引擎 → 顯示用的高頻率資料。 把量測・影像處理引擎拆成別的行程,再把資料流向 UI 的架構中,控制(開始・停止・設定)用具名管道,資料(畫面・波形)用共享記憶體+具名事件的雙通道架構是常見做法。控制每秒只有幾次,管道就夠用;資料則用共享記憶體逼近零複製。把通道分開後,就能把資料端的設計(環形緩衝區等)和控制端的考量分開,各自最佳化。引擎或外部裝置的狀態顯示,也可以參考《外部裝置的狀態顯示》。
5. 實作範例 ── 具名管道的非同步伺服器與用戶端
以下展示作為具名管道實作基礎的、.NET 8 下的最小架構,包含訊息模式・支援多用戶端・斷線處理。協定設計成「UTF-8 的 JSON、一則訊息一封」,並且一定要帶 version 欄位。下面的範例以同一使用者行程間通訊為前提,使用了 CurrentUserOnly。如果像架構 1 那樣要和服務(不同帳戶)通訊,請如稍後所述,移除 CurrentUserOnly 並用 PipeSecurity 明確指定 ACL。
先看伺服器端。每次接受連線都會重新建立新的伺服器串流,並把每個用戶端的處理分離到各自的工作。
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeServer(string pipeName)
{
// camelCase・不區分大小寫的 Web 預設值。System.Text.Json 的預設值
// 會區分大小寫,如果不共用這個設定,"version" 就不會
// 綁定到 Request.Version,導致正常的請求也落入 unknown_type
internal static readonly JsonSerializerOptions JsonOptions =
new(JsonSerializerDefaults.Web);
public async Task RunAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var pipe = new NamedPipeServerStream(
pipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message, // 寫入一次 = 一則訊息
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
await pipe.WaitForConnectionAsync(ct);
_ = Task.Run(() => HandleClientAsync(pipe, ct), ct); // 每個連線分開處理
}
}
// 單則訊息的上限。即使對方送來巨大的訊息,
// 也要保護服務端的記憶體(IPC 的對象也算外部輸入。第6章)
private const int MaxMessageBytes = 1024 * 1024;
private static async Task HandleClientAsync(
NamedPipeServerStream pipe, CancellationToken ct)
{
await using (pipe)
{
var buffer = new byte[64 * 1024];
try
{
while (!ct.IsCancellationRequested)
{
// 即使是訊息模式,也不保證一次 Read 就能讀完
// 整則訊息。要讀到 IsMessageComplete 為止
using var ms = new MemoryStream();
do
{
int n = await pipe.ReadAsync(buffer, ct);
if (n == 0) return; // 用戶端已斷線
ms.Write(buffer, 0, n);
if (ms.Length > MaxMessageBytes) return; // 超過上限,斷線
} while (!pipe.IsMessageComplete);
byte[] response = Dispatch(ms.ToArray());
await pipe.WriteAsync(response, ct);
}
}
catch (IOException)
{
// 只是這個用戶端的通訊路徑壞了。
// 不停掉整個伺服器,繼續處理其他連線
}
}
}
private static byte[] Dispatch(byte[] payload)
{
Request? req;
try { req = JsonSerializer.Deserialize<Request>(payload, JsonOptions); }
catch (JsonException) { req = null; }
// 格式錯誤・未知版本・未知 type 都在這裡檢查並拒絕
object result = req switch
{
null => new { version = 1, error = "bad_request" },
// version 未指定(會綁定成 0)或未知版本,都在這裡拒絕
{ Version: not 1 } => new { version = 1, error = "version_unsupported" },
{ Type: "getStatus" } => new { version = 1, running = true },
{ Type: "startJob" } => new { version = 1, jobId = StartJob(req) },
_ => new { version = 1, error = "unknown_type" },
};
return JsonSerializer.SerializeToUtf8Bytes(result, JsonOptions);
}
}
public sealed record Request(int Version, string Type, JsonElement? Body);
用戶端則把「連線 → 請求 → 回應」封裝在一個方法裡,並從一開始就加入逾時與重試。
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeClient(string pipeName)
{
// idempotent: 只有呼叫端明確宣告「這個請求即使送達兩次也安全」
// 時才會重試。預設是「不重試」
public async Task<TResponse?> RequestAsync<TResponse>(
object request, CancellationToken ct, bool idempotent = false)
{
for (int attempt = 1; ; attempt++)
{
// 不只是連線,整個請求→回應的交換過程都設上限時間。
// 避免伺服器只接受連線卻沒寫回應就卡死時,
// 造成無限等待
using var deadline =
CancellationTokenSource.CreateLinkedTokenSource(ct);
deadline.CancelAfter(TimeSpan.FromSeconds(10));
try
{
using var pipe = new NamedPipeClientStream(
".", pipeName, PipeDirection.InOut,
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
// 不無限等待。如果伺服器沒有啟動,就會丟出例外
await pipe.ConnectAsync(timeout: 3000, deadline.Token);
pipe.ReadMode = PipeTransmissionMode.Message;
await pipe.WriteAsync(
JsonSerializer.SerializeToUtf8Bytes(
request, PipeServer.JsonOptions),
deadline.Token);
using var ms = new MemoryStream();
var buffer = new byte[64 * 1024];
do
{
int n = await pipe.ReadAsync(buffer, deadline.Token);
if (n == 0) throw new IOException("伺服器已斷線。");
ms.Write(buffer, 0, n);
} while (!pipe.IsMessageComplete);
return JsonSerializer.Deserialize<TResponse>(
ms.ToArray(), PipeServer.JsonOptions);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw; // 呼叫端主動取消,不重試
}
catch (Exception ex) when (
ex is IOException or TimeoutException or OperationCanceledException
&& idempotent && attempt < 3)
{
// 針對伺服器重啟中之類的暫時性失敗進行重試。
// 「送出後但還沒讀到回應就斷線」的情況下,請求有可能
// 已經執行過了,因此像 startJob 這種有副作用的請求,
// 依預設不重試;只有在設計成可用請求 ID 擋掉重複之後,
// 才傳入 idempotent: true(第6章)
await Task.Delay(500 * attempt, ct);
}
}
}
}
針對這段程式碼的設計判斷,補充 3 點。
- 從第一個版本就加入
version。 UI 和服務一定會遇到分別更新(只有一邊變新)的時刻。只要回應端做到「明確拒絕不認識的版本」,原本會悄悄跑出怪異行為的事故,就會變成「看得懂的錯誤」。 - 決定連線要用一次性,還是常駐連線。 上面的範例是每次請求都重新連線的一次性做法,不需要管理斷線・重新連線的狀態,但不適合高頻率的呼叫。如果需要常駐連線+伺服器主動推播通知,那種複雜度就是換成 gRPC 雙向串流的判斷依據。
- 和服務通訊時,要移除
CurrentUserOnly,並設計PipeSecurity。 上面的範例假設是同一使用者的行程之間。如果對方是不同帳戶的服務,請用NamedPipeServerStreamAcl.Create建立帶 ACL 的伺服器串流,並明確指定允許連線的群組。6
6. 協定設計的共通注意事項 ── 選定手段之後的工作
不管選哪一種 IPC,通訊內容的設計都有共通的課題。其實比起選手段,這裡才是事故的源頭。
- 訊息的分界(訊框化):除了像管道的訊息模式那樣由 OS 負責分界的情況之外,「從哪裡到哪裡算一則訊息」是應用程式自己的責任。共享記憶體的記錄邊界也是同樣的問題。請以 length 前置長度方式為基本做法。
- 版本控制:在訊息中包含格式版本,接收端要「明確拒絕不認識的版本」。即使用同一個安裝程式發布,也不保證兩個行程永遠會同時更新。
- 逾時:把「對方不回應」當成一定會發生的事來設計。為連線・請求・回應各自設定上限時間,不要留下無限等待的程式碼。在 UI 執行緒上同步等待更是不可行。
- 重新連線與冪等性:重試意味著會製造出同一個請求送達兩次的可能性。要依請求種類分類「執行兩次也安全的請求」,對於不安全的請求(如投遞工作)則加上請求 ID 來擋掉重複。
- 把對方也當成外部輸入來驗證:如果是同一台電腦內的通訊,很容易因為「對方是自己的應用程式」就省略驗證,但只要存在權限邊界,IPC 的對象就等同於來自網路的輸入,是同樣「不可信任的輸入」。管理員權限的中介或服務,不應該把標準權限用戶端送來的路徑或指令原封不動拿來執行。連同管道名稱佔用(2.2 節)一起,「懷疑對方是誰」「懷疑要做什麼」兩者並重,才是跨越權限邊界的 IPC 該有的作法。
- 保留觀測手段:只要能輸出通訊記錄(至少要有訊息類型與對象・結果),「連不上」「沒有回應」這類問題的調查時間就會差好幾個量級。
7. 結語
行程間通訊的選擇,按照下面的順序思考幾乎不會迷路。首先確認是否可以限定在同一台電腦內。如果可以限定,請求-回應用具名管道、大量高頻率資料只用共享記憶體、鬆散耦合的批次處理用檔案協作。如果看得到跨機器或跨語言混用,就用本機 TCP 或 gRPC;當自訂協定的維護規模變成負擔時,就選 gRPC。COM 不當作新設計的首選,而是保留作為 32/64 橋接與 VBA 協作的工具──這篇文章的判斷表,就是把這個流程整理成表格。
而且一旦決定了手段,就要把第 6 章的共通設計(訊框化・版本・逾時・重新連線・驗證對方)放進第一個版本裡。從實務上的體感來說,IPC 的問題比起「選錯手段」,更多是因為「省略了協定設計」而發生,而且差距相當大。行程拆分或通訊方式的重新檢視,建議先從盤點「哪個行程・用誰的權限・做什麼・多常互動」開始。
相關文章
- 共享記憶體的陷阱與實務最佳實踐
- 以為 TCP 的 Send 單位可以對應 Receive 是一種誤解 ── 把 TCP 當成位元組串流處理的接收設計
- 管理員權限的分離(broker) ── 跨越 UAC 邊界的設計
- Windows 服務的做法 ── Worker Service 指南
相關諮詢領域
小村軟體有限公司承接行程拆分・通訊方式的設計檢視、包含 32bit/64bit 橋接的既有資產協作設計,以及「連不上・沒有回應」這類通訊問題的原因調查。
參考連結
-
Microsoft Learn,How to: Use Named Pipes for Network Interprocess Communication。關於用 NamedPipeServerStream / NamedPipeClientStream 實作支援多用戶端的伺服器・用戶端範例。 ↩
-
Microsoft Learn,Inter-process communication with gRPC and Named pipes。關於 .NET 8 以後透過 Kestrel 的 ListenNamedPipe,讓 gRPC 跑在具名管道上的架構,以及 PipeSecurity 的存取控制。 ↩ ↩2 ↩3
-
Microsoft Learn,MemoryMappedFile Class。關於記憶體對應檔案的 .NET API,以及用 CreateNew 建立、不綁定檔案的共享記憶體適合用於 IPC 的說明。 ↩ ↩2
-
Microsoft Learn,Interprocess communications。關於 Windows 提供的 IPC 機制(剪貼簿、COM、WM_COPYDATA、DDE、檔案對應、mailslot、管道、RPC、Windows socket)清單與選用準則。 ↩ ↩2 ↩3
-
Microsoft Learn,NamedPipeServerStream Class。關於具名管道的伺服器端串流,以及指定 PipeTransmissionMode 或 PipeSecurity 的建構函式。 ↩
-
Microsoft Learn,Named Pipe Security and Access Rights。關於具名管道的預設安全性描述元允許 Everyone 與匿名帳戶讀取,以及存取權限的組成。 ↩ ↩2
-
Microsoft Learn,PipeOptions Enum。關於 CurrentUserOnly 只允許與「同一使用者建立的對象」連線,在 Windows 上除了使用者帳戶之外,還會驗證到提升層級。 ↩ ↩2
-
Microsoft Learn,Inter-process communication with gRPC。關於把 gRPC 用於 IPC 時的傳輸層(Unix 網域 socket、具名管道),以及伺服器擁有者驗證・偽裝(impersonation)對策等安全性考量。 ↩ ↩2
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows應用程式資料儲存位置怎麼選 ── SQLite / JSON / 登錄檔 / Access 判斷表
Windows桌面應用程式的資料該存在哪裡、用什麼格式儲存?本文整理AppData/ProgramData的使用區分,以及SQLite、JSON檔案、登錄檔、Access(.accdb)各自的優勢與陷阱,並附判斷表,從實務角度說明防止資料損毀與位元數問題等注意事項。
如何理解 Windows 的工作階段隔離 ── Session 0・RDP・多使用者同時執行
整理 Windows 應用程式開發者容易混淆的「工作階段」概念。說明服務無法顯示 UI 的 Session 0 隔離原因、RDP 連線時工作階段的行為、具名物件的工作階段隔離,以及共用 PC・RDS 環境常見的設計失誤,從實務角度解說。
防止 Windows 應用程式重複啟動 ── 具名 Mutex 與重複執行時的視窗前置
整理業務用 Windows 應用程式的常見需求「不讓同一個應用程式啟動兩次」,如何用具名 Mutex 實作。涵蓋 Global\ 與 Local\ 命名空間差異在 RDP 環境中的陷阱、擁有執行緒限制與 AbandonedMutexException、考量 SetForeg...
Windows 服務的建立與維運 ── 從工作排程器的取捨到 BackgroundService 服務化
常駐處理該做成 Windows 服務,還是工作排程器就足夠?本文整理判斷表,以及用 .NET Worker Service(UseWindowsService)建立服務的方法、執行帳戶、復原選項、事件記錄、安全停止處理,從實務角度梳理上線維運所需的設計。
在 WinForms/WPF 應用程式中導入 Entra ID 驗證 ── MSAL.NET 與 WAM 代理的實務架構
從實務角度整理在 WinForms/WPF 桌面應用程式中導入 Entra ID(原 Azure AD)驗證的做法。內容涵蓋公用客戶端的概念、ROPC 廢除現況、應用程式註冊、MSAL.NET 的 AcquireTokenSilent 模式、WAM 代理、權杖快取持久化,以...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
技術諮詢 & 設計審查
協助釐清設計方向、架構邊界、生命週期責任,以及既有 Windows 資產的處理方式。
常見問題
整理諮詢這個主題時常見的問題。
- Windows 的行程間通訊該以什麼為首選?
- 同一台電腦內的請求-回應(送出指令並取得結果)的話,具名管道是首選。它是 OS 標準功能,不需要管理連接埠,也不會被防火牆擋下,並與 Windows 的存取控制(PipeSecurity)整合,在 .NET 中可以用 System.IO.Pipes 直接寫出來。如果未來有可能跨越網路,一開始就選本機 TCP 或 gRPC;只有大量・高頻率的資料(影像畫面、波形)才用共享記憶體;鬆散耦合・非同步且想留下稽核記錄的協作則用檔案協作──用這種排除法來收斂範圍才是正確的選擇方式。
- gRPC 適合在什麼時候用於行程間通訊?
- 當服務或呼叫的種類增加,自訂協定的 switch 陳述式與文件維護開始變成負擔時。透過 proto 檔案定義結構並產生程式碼、取得雙向串流,而且 .NET 8 以後 ASP.NET Core(Kestrel)可以直接把具名管道當作傳輸層。另一方面,對於只有 2〜3 種指令的工具之間,扛著 ASP.NET Core 的主機基礎設施會是過度設計,具名管道+JSON 的總成本會更低。等到「proto 裡要寫的訊息超過 10 個」「本質上需要串流通知」「對方不是 .NET」這三者之一成立後再導入也不遲。
- 從 32bit 應用程式使用 64bit DLL 該怎麼做?
- 32bit 行程無法把 64bit 的 DLL 載入同一個行程,因此要把 64bit 那一側切到別的行程。實作方式有兩種:(a) 建立 64bit 的輔助行程並用具名管道通訊,(b) 做成 64bit 的跨行程 COM 伺服器。如果呼叫端是 VBA 或舊語言,(b) 比較自然,因為 COM 基礎架構會代勞封送處理,呼叫端幾乎不用改程式碼。如果雙方都是 C#,(a) 在部署與除錯上都更輕鬆。採用(a)時,要用 Job Object 設計輔助行程的親子連動結束。
- 共享記憶體適合在什麼場合使用?
- 只限定在影像畫面或波形資料這類大量・高頻率的資料上使用。它是同一台電腦內最快的 IPC,不涉及複製或序列化,但完全不提供任何同步機制,因此「不讀取寫入中的資料」的機制、偵測對方存活狀態、異常結束後的復原,全部都要自己設計。實務上常見的做法是只把資料面(data plane)放在共享記憶體上,開始・停止・變更設定等控制面(control plane)則交給具名管道之類的另一個通道,形成雙通道架構。一旦連控制訊息都想硬塞進共享記憶體,就是該重新檢視設計的訊號。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。