處理序之間通訊 (IPC)

本主題說明了 Windows 應用程式 SDK 桌面應用程式與其他 Win32 應用程式之間執行程序間通訊(IPC)的各種方式。 由於 Windows 應用程式 SDK 桌面應用程式以完全信任的 Win32 程序運行,因此它們能直接存取所有作業系統層級的 IPC 機制。 如下文所述,當應用程式與 MSIX 一起包裝時,某些機制會有額外要求。

應用程式服務

應用程式服務可讓應用程式在背景中公開可接受及傳回原始型別屬性包(ValueSet)的服務。 複合物件只要經過序列化即可傳遞。

應用程式服務可以在處理序外作為背景工作執行,也可以在前景應用程式的處理序內執行。

Note

應用程式服務需要一個帶有套件身份的打包應用程式。 它們適用於使用 MSIX 包裝的 Windows 應用程式 SDK 應用程式。

應用程式服務最適合用於分享少量資料,且不需要接近即時延遲。

COM

COM 是一個分散式物件導向系統,用於建立能互動與溝通的二進位軟體元件。 作為開發者,你會使用 COM 來建立可重複使用的軟體元件和自動化層。 COM元件可以是進行中或非進行中,且可透過 客戶端與伺服器 模型進行通訊。 程序外的 COM 伺服器長期以來被用作 物件間通訊的手段。

具有 runFullTrust 功能的封裝應用程式可透過 套件資訊清單註冊供 IPC 使用的處理序外 COM 伺服器。 這稱作 封裝式 COM

Windows 應用程式 SDK 桌面應用程式以全信任程序形式執行,因此它們也能像傳統 Win32 應用程式一樣,透過 Windows 登錄檔直接註冊並使用 COM 伺服器。

Filesystem

BroadFileSystemAccess

封裝應用程式可藉由宣告 broadFileSystemAccess 受限能力,使用廣域檔案系統執行 IPC。 這項功能可讓 Windows.Storage API 和 Win32 FromApp API 存取廣泛的檔案系統。

預設情況下,透過封裝應用程式的檔案系統執行 IPC 僅限於本節所述的其他機制。

出版者快取資料夾

PublisherCacheFolder 允許封裝應用程式在其清單中宣告資料夾,並可由同一發佈者與其他套件共享。

共享儲存資料夾有以下要求與限制:

  • 共用儲存資料夾中的資料不會備份,也不會漫遊。
  • 使用者可以清除共用儲存資料夾的內容。
  • 你不能用共用儲存資料夾來在不同出版商的應用程式間分享資料。
  • 你不能用共享儲存資料夾在不同使用者間分享資料。
  • 共用儲存資料夾沒有版本管理。

如果你發佈多個應用程式,並且想找一個簡單的方式在它們之間分享資料,那麼 PublisherCacheFolder 是一個簡單的檔案系統解決方案。

管道

管道 使管道伺服器與一個或多個管道用戶端之間能夠簡單通訊。

支援匿名管線命名管線,並有以下限制條件:

  • 預設情況下,封裝應用程式中的命名管線僅支援同一套件內的處理程序之間使用,除非某個處理程序具有完全信任權限。
  • 命名管線可依照 命名物件共享指引在套件間共享。
  • 在打包的應用程式中,命名管道應該包含 LOCAL\ 在管道名稱中(例如, \\.\pipe\LOCAL\<pipename>)。 該 LOCAL\ 區段會將管線範圍延伸至呼叫者的登入會話,且對 MSIX 封裝應用程式是必要的。 使用 .NET API(例如 NamedPipeServerStream)時,請只傳遞 LOCAL\<pipename> 部分——\\.\pipe\ 前綴會在內部處理。

Windows 應用程式 SDK 桌面應用程式以全信任程序執行,因此可以建立並使用命名管道,且不受相同套件限制。 然而,如果你是與另一個未獲得完整信任的封裝應用程式進行通訊,則上述限制仍然適用。

命名管路範例(C#)

以下範例展示了一個完整的命名管道伺服器與用戶端,作為兩個主控台應用程式。 由於 Windows 應用程式 SDK 桌面應用程式是全信任程序,命名管線 IPC 無需特殊功能或清單條目即可運作。

若要試用此範例,請建立兩個主控台應用程式專案(目標設為 net8.0-windows,並啟用 ImplicitUsings),先執行伺服器,再於另一個終端機中執行用戶端。

管道伺服器 — 建立一個命名管道並將訊息回傳給用戶端:

using System.Text;

string PipeName = @"LOCAL\WinAppSdkIpcDemo";

Console.WriteLine("Named Pipe Server");
Console.WriteLine($"  Process ID: {Environment.ProcessId}");
Console.WriteLine($"  User:       {Environment.UserName}");
Console.WriteLine($"  Pipe:       \\\\.\\pipe\\{PipeName}");
Console.WriteLine();

using var server = new System.IO.Pipes.NamedPipeServerStream(
    PipeName,
    System.IO.Pipes.PipeDirection.InOut,
    maxNumberOfServerInstances: 1,
    System.IO.Pipes.PipeTransmissionMode.Message,
    System.IO.Pipes.PipeOptions.Asynchronous);

Console.WriteLine("Waiting for client...");
await server.WaitForConnectionAsync();
Console.WriteLine("Client connected!");

byte[] buffer = new byte[4096];

while (server.IsConnected)
{
    try
    {
        int bytesRead = await server.ReadAsync(buffer);
        if (bytesRead == 0) break;

        string received = Encoding.UTF8.GetString(buffer, 0, bytesRead);
        Console.WriteLine($"  Received: \"{received}\"");

        if (received.Equals("QUIT", StringComparison.OrdinalIgnoreCase))
            break;

        // Echo the message back with the server's process ID
        string reply = $"Echo from PID {Environment.ProcessId}: {received}";
        await server.WriteAsync(Encoding.UTF8.GetBytes(reply));
        await server.FlushAsync();
    }
    catch (IOException)
    {
        break;
    }
}

Console.WriteLine("Done.");

管道用戶端 — 連接伺服器並傳送使用者輸入:

using System.Text;

string PipeName = @"LOCAL\WinAppSdkIpcDemo";

Console.WriteLine("Named Pipe Client");
Console.WriteLine($"  Process ID: {Environment.ProcessId}");
Console.WriteLine();

using var client = new System.IO.Pipes.NamedPipeClientStream(
    serverName: ".",
    pipeName: PipeName,
    System.IO.Pipes.PipeDirection.InOut,
    System.IO.Pipes.PipeOptions.Asynchronous);

Console.WriteLine("Connecting...");
await client.ConnectAsync(timeout: 5000);
client.ReadMode = System.IO.Pipes.PipeTransmissionMode.Message;
Console.WriteLine("Connected! Type messages (or QUIT to exit):");

byte[] buffer = new byte[4096];

while (true)
{
    Console.Write("> ");
    string? input = Console.ReadLine();
    if (string.IsNullOrEmpty(input)) continue;

    await client.WriteAsync(Encoding.UTF8.GetBytes(input));
    await client.FlushAsync();

    if (input.Equals("QUIT", StringComparison.OrdinalIgnoreCase))
        break;

    int bytesRead = await client.ReadAsync(buffer);
    string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    Console.WriteLine($"  <- {response}");
}

Console.WriteLine("Done.");

當你同時執行兩個應用程式時,用戶端會發送訊息,伺服器會回傳訊息,確認兩個全信任桌面程序間的跨程序通訊,無需特殊設定。

登記處

一般不建議使用 IPC 登錄檔,但現有程式碼仍支援使用。 打包應用程式只能存取其有權限存取的登錄檔金鑰。

打包桌面應用程式(參見 從你的程式碼建構 MSIX 套件)利用 登錄檔虛擬化 ,使全域登錄檔寫入被限制在 MSIX 套件中的私人蜂巢中。 這能在降低全域登錄檔影響的同時,實現原始碼相容性,並可用於同一套件中程序間的 IPC。 如果必須使用登錄檔,這種模式比操作全域登錄檔更為可取。

RPC

RPC 可用來將已封裝的應用程式連接到 Win32 RPC 端點,前提是該封裝後的應用程式具備與 RPC 端點 ACL 相符的正確能力。

自訂能力讓 OEM 與 IHV 能 定義任意能力,將 RPC 端點納入 ACL,然後 將這些能力授予授權的客戶端應用程式。 完整範例應用請參見 CustomCapability 範例。

RPC 端點也可以被 ACL 綁定到特定封裝應用程式,限制只能存取這些應用程式,而無需自訂功能的管理負擔。 你可以使用 DeriveAppContainerSidFromAppContainerName API,從套件系列名稱推導出 SID,然後如 CustomCapability 範例所示,使用該 SID 對 RPC 端點設定 ACL。

共用記憶體

檔案映射 可用於在兩個或多個程序間共享檔案或記憶體,並有以下限制:

  • 預設情況下,封裝應用程式中的檔案映射僅支援同一套件內的程序間,除非該程序是完全信任的。
  • 檔案映射可依照 命名物件共享指引在套件間共享。

Windows 應用程式 SDK 桌面應用程式以全信任程序運作,因此可以無限制地建立並使用共享記憶體檔案映射。 當與其他沒有完全信任的封裝應用程式通訊時,請採用 共享命名物件中所述的 ACL 方法。

建議使用共享記憶體以有效分享與操作大量資料。

Loopback

迴圈是指與在本地主機(迴圈位址)上監聽的網路伺服器進行通訊的過程。

為了維持安全性與網路隔離,封裝應用程式預設會阻擋 IPC 的迴圈連線。 你可以利用能力和清單屬性,在受信任的封裝應用程式間啟用迴圈連線。

  • 所有參與迴路連線的封裝應用程式都必須在其privateNetworkClientServer中宣告該能力。
  • 兩個封裝應用程式可以透過迴圈通訊,方法是在其套件清單中宣告 LoopbackAccessRules
    • 每個應用程式都必須在各自的 LoopbackAccessRules 中列出對方。 用戶端會為伺服器宣告「出」規則,伺服器則為其支援的客戶端宣告「入」規則。

Note

在這些規則中識別應用程式所需的套件族名稱,可在開發期間透過 Visual Studio 的套件清單編輯器找到,Microsoft Store 發佈的應用程式則可透過 Partner Center 查詢,或已安裝的應用程式可透過 Get-AppxPackage PowerShell 指令查詢。

未封裝的應用程式和服務沒有套件身份,因此無法在 LoopbackAccessRules 中宣告。 你可以設定已封裝的應用程式透過迴圈方式與未封裝的應用程式和服務 CheckNetIsolation.exe連接,但這僅適用於側載或除錯情境,且你擁有本地存取權且擁有管理員權限。

  • 如果封裝應用程式要連線至未封裝應用程式或服務,請執行 CheckNetIsolation.exe LoopbackExempt -a -n=<PACKAGEFAMILYNAME>,以新增該封裝應用程式的回送豁免。
  • 如果未封裝的應用程式或服務正在連線到已封裝的應用程式,請執行 CheckNetIsolation.exe LoopbackExempt -is -n=<PACKAGEFAMILYNAME>,以啟用該已封裝的應用程式接收傳入的回送連線。

Note

-n 旗標所需的套件族名稱可在開發期間透過 Visual Studio 的套件清單編輯器找到,或透過 Microsoft Store 發佈的應用程式透過合作夥伴中心,或透過已安裝的應用程式使用 Get-AppxPackage PowerShell 指令找到。

選擇IPC機制

下表總結了 IPC 機制及其最佳使用情境:

機制 最適合用於 Requirements
應用程式服務 與財產袋的小規模資料交換 具有套件識別的已封裝應用程式
COM 可重複使用元件、自動化層 無(Win32)或套件資訊清單(已封裝 COM)
具名管道 基於串流的雙向通訊 完全信任應用程式則沒有
共用記憶體 大量資料,高效能 完全信任的應用程式不適用
RPC 分散式客戶端/伺服器程式 ACL 必須允許存取
登記處 舊有程式碼相容性 不建議用於新程式碼
Loopback 基於網路的協定(TCP/UDP) privateNetworkClientServer 套件應用程式的功能