Choosing Windows Inter-Process Communication ── A Decision Table for Named Pipes / TCP / gRPC / Shared Memory / COM
· Go Komura · Inter-Process Communication, Named Pipes, Windows, .NET, C#, gRPC, Shared Memory, COM, Architecture, Decision Table, Technical Consulting
“If we split the UI and the service, how should the two sides talk to each other?” “I want a 32-bit app to use functionality from a 64-bit DLL.” “I want to stream data from a measurement engine running in a separate process to the screen.” Splitting an application into multiple processes is a common, pragmatic answer to robustness, privilege separation, and bitness mismatches — but the moment you split it, you inevitably face the question of how the pieces should communicate.
On this blog we’ve already written about individual communication mechanisms: shared memory pitfalls, TCP framing, handling child processes safely, and file-based integration and locking. What was still missing was the bigger picture — which mechanism should you choose in the first place? This article organizes the main options for Windows inter-process communication (IPC) — file-based integration, named pipes, local TCP, gRPC, shared memory, and COM — by their strengths and pitfalls, in the decision-table format this blog favors.
1. The bottom line first
- For request/response on the same machine (send a command, get a result back), named pipes are the first candidate. They’re an OS-native primitive, need no port, integrate with Windows access control, and are straightforward to use from .NET via
System.IO.Pipes.1 - If there’s any chance the communication will need to cross the network someday, go with local TCP or gRPC from the start. Migrating from a pipe to a socket later tends to be more than a “small tweak” — it’s a real redesign. Note that raw TCP is just a byte stream, so designing a framing scheme is mandatory.
- When the number of services or call types grows and maintaining a custom protocol becomes a burden, reach for gRPC. You get schema definitions and code generation via proto files, plus bidirectional streaming, and since .NET 8, ASP.NET Core (Kestrel) can use named pipes as the transport.2
- Use shared memory only for large-volume, high-frequency data (image frames, waveform data). It’s the fastest option, but you design all the synchronization yourself — the golden rule is: never put control messages on shared memory too.3
- For loosely coupled, asynchronous integration where you want an audit trail, file-based integration is still a strong choice. Success or failure, though, hinges entirely on how you design mutual exclusion.
- COM (out-of-process) is not the first choice for new designs, but it’s still an active tool in the context of consumption from VBA/other languages, or as a 32-bit/64-bit bridge.4
- Whichever mechanism you pick, you can’t escape the shared design challenges of message boundaries, version numbers, timeouts, and reconnection (Chapter 6). Spend as much time on these as you do on picking the mechanism itself.
2. Profile of each option
2.1 File-based integration ── loosely coupled, asynchronous, auditable
This is the classic pattern: “A drops a file into an output folder, B picks it up and processes it.” Both sides don’t need to be running at the same time, the exchange leaves a paper trail as files, and when something goes wrong a human can look directly at the file, fix it, and resubmit it. For batch-style integration that doesn’t demand real-time responsiveness, this is still a powerful approach.
The pitfall boils down to almost one thing: mutual exclusion. “Reading a file that’s still being written” and “two processes fighting over the same file” are the classic accidents, and you need to follow standard practices such as writing to a temp file name and then renaming it. The details are covered in “File Integration and Locking Best Practices” — be sure to reference it if you choose this approach. It’s not suited for interactive communication that wants a response, or exchanges that happen dozens of times per second.
2.2 Named pipes ── the go-to for same-machine IPC
Named pipes are a bidirectional communication channel provided by the Windows kernel, and they’re the go-to for client/server-style IPC within the same machine. In .NET they’re handled via NamedPipeServerStream / NamedPipeClientStream, and multiple clients can connect to a single pipe name.5 Unlike TCP, there’s no port number to manage, and they don’t get blocked by firewalls.
Here are the points that matter in practice.
- Message mode is available. Specifying
PipeTransmissionMode.Messagemeans each write is delivered as a discrete message boundary. This is a significant practical advantage — the OS handles the framing that’s mandatory with TCP (length prefixes, etc.) for you (the receiving side still needs to fully drain a message usingIsMessageComplete; see Chapter 5). - The default access rights are surprisingly permissive. The default security descriptor for a pipe grants full control to LocalSystem, administrators, and the creator, but it also grants read access to Everyone and anonymous accounts.6 For inter-process communication within the same user, simply adding
PipeOptions.CurrentUserOnlyenforces “only connect to a peer created by the same user” — I’d recommend making this your default practice.7 For cross-account communication (e.g., talking to a service), explicitly define an ACL withPipeSecurity. - Pipe names live in a namespace visible to everyone. Pipe names sit in a single namespace under
\\.\pipe\, visible to other users’ processes on the same machine as well. What you need to watch out for here is name squatting. If a malicious process sets up a server under the same name first, a client will connect to that one instead. On machines shared by users with different privilege levels, you should have countermeasures in place: on the server side, specifyPipeOptions.FirstPipeInstanceto fail if a pipe of the same name already exists; on the client side, verify the pipe’s owner account after connecting.8 - You can communicate across a privilege boundary. This is a standard channel between a “standard-privilege UI + an elevated broker” separated by UAC. Note, though, that
CurrentUserOnlycan’t be used in this configuration (it requires the elevation level to match too, even for the same user7). You need to design the ACL explicitly — the details are covered in “Separating Administrator Privileges (Broker).”
The weaknesses are that it’s effectively unsuited for cross-machine communication (technically possible, but with plenty of operational constraints), and it’s not easy to use for interoperating with non-Windows systems. If those requirements are on the horizon, choose TCP / gRPC instead.
2.3 Local TCP ── cross-language, cross-OS versatility
A TCP connection to localhost is about the most universal IPC mechanism there is — it can be spoken from just about any language, runtime, or OS. In a mixed configuration like “an analysis engine on Linux plus a UI on Windows,” TCP (or HTTP/gRPC on top of it) is the first candidate.
Three points to watch:
- It’s a byte stream. TCP doesn’t guarantee “arrives in the same units you sent.” It’s perfectly normal for three messages sent via
Sendto arrive concatenated in a singleReceive, or for a single message to arrive split across multiple reads. You have to design framing (such as a length prefix) at the application layer yourself — code that skips this is merely “happening to work.” See “The Misconception That You Can Receive One Unit Per Send Over TCP” for details. - Restrict the scope of what’s exposed by your listener. Even if you intend same-machine-only communication, listening on
0.0.0.0allows other machines on the network to connect. For local IPC, the rule is to bind to127.0.0.1(loopback). Even then, another user on the same machine can still connect, so add application-layer authentication if you need to verify the peer. This is a clear gap compared to pipes, which have OS-integrated access control. - Port management comes along for the ride. A fixed port can collide with other software, and firewall products may flag it as a “suspicious listener.” Build in a way to change the port number from the start.
2.4 gRPC ── buying schema and code generation
Compared to a raw socket plus a homegrown protocol, what gRPC provides is less the communication itself and more a development framework. Define services and messages in a proto file, and serialization, framing, and client/server code are all generated for you; bidirectional streaming (server-push notifications) can be written like a native language feature. Once you hit the phase where “every time a message is added, we have to touch a homegrown protocol’s switch statement and its documentation,” this framework starts to pay off.
Since .NET 8, ASP.NET Core (Kestrel) directly supports Unix domain sockets and named pipes as transports, in addition to TCP.8 On the server side, you just call ListenNamedPipe, and you can also configure access control via PipeSecurity.2 The combination of “keep the transport as a portless, ACL-integrated pipe, and put the protocol layer on gRPC” is a strong option for UI/service separation (Chapter 4).
On the other hand, it tends to be overkill for small-scale tool-to-tool communication. Standing up a gRPC server means carrying the ASP.NET Core hosting stack with you, which increases your deliverables, dependencies, and startup cost. For a pair of tools that only exchange two or three kinds of commands, named pipes + JSON (Chapter 5) will often be cheaper overall — keep that line firmly in mind. It’s not too late to switch once one of these is true: “the number of messages we want in the proto has passed ten,” “streaming notifications are genuinely needed,” or “the other side is not .NET.”
2.5 Shared memory (memory-mapped files) ── fastest, but you own all the synchronization
The fastest IPC mechanism within a single machine is shared memory. In .NET, MemoryMappedFile.CreateNew creates a named block of shared memory that multiple processes can read and write directly as the same byte sequence.3 With no copying or serialization in the way, it delivers performance that’s essentially the only sensible choice for “big and fast” data like image frames or waveform data.
That said, shared memory is not “a faster pipe.” All you get is the same bytes visible on both sides — not a single byte of synchronization is provided. A mechanism to avoid reading data that’s mid-write, detecting whether the other side is alive, and recovering after one side crashes abnormally — all of that is on you to design. The full design discussion, including ring-buffer layouts and versioning the memory layout, is collected in “Shared Memory Pitfalls and Practical Best Practices.”
The practical use case is clear: put only the data plane on shared memory, and route the control plane (start/stop/configuration changes) through a separate channel like named pipes (Configuration 3 in Chapter 4). If you find yourself trying to push control messages — start, stop, configuration changes — over shared memory too, that’s a sign it’s time to revisit the design.
2.6 COM (out-of-process) ── not the first choice for new work, but still active in some settings
COM’s out-of-process servers (EXE servers) are Windows’ long-standing mechanism for “calling an object in another process as if it were a local function.”4 It’s no longer the first candidate for new inter-application communication, but it’s still practical in the following contexts:
- 32-bit/64-bit bridging: You can’t load a 64-bit DLL into a 32-bit app’s process (or vice versa), but out-of-process COM can cross that boundary. Because the COM infrastructure handles marshaling for you, you can leave the calling code almost unchanged. See a real-world example in “A 32-bit → 64-bit COM Bridge Case Study.”
- Use from VBA or other languages: When you want an older runtime, like Excel VBA, to call into .NET functionality, exposing it as COM is still the most straightforward path today.
- Interop with existing COM assets: If your counterpart can only speak COM, speaking COM yourself is the shortest route (for a primer on COM itself, see “What Are COM, ActiveX, and OCX?”).
The reasons to hesitate about adopting it for new work are the hassle of distribution involving registry registration, the learning cost of interface design and reference counting, and how hard it is to investigate when something goes wrong. Before you commit, compare it against the alternative in Chapter 4, Configuration 2: “if the goal is a 32/64 bridge, just make the 64-bit side a plain helper process and talk to it over a named pipe.”
2.7 Classic mechanisms ── not a new-design choice
Windows has several other IPC mechanisms — WM_COPYDATA (sending data via a window message), the clipboard, DDE, and mailslots — and they still appear on the official IPC overview page.4 However, they either presuppose the existence of a window, or are being phased out (remote mailslots are on their way to deprecation), so there’s essentially no reason to pick them for a new design. It’s enough to recognize them when you run into them while maintaining an existing application.
3. Decision table
| Aspect | File | Named pipe | Local TCP | gRPC | Shared memory | COM |
|---|---|---|---|---|---|---|
| Communication range | Can cross machines via a shared folder | Practically same-machine only | Can cross machines | Can cross machines | Same machine only | Practically same-machine only |
| Communication model | File handoff (asynchronous) | Stream + message mode | Byte stream | RPC + streaming | Shared state | Method calls |
| Server → client notification | ✕ (polling) | ○ | ○ | ◎ (bidirectional streaming) | △ (needs a separate event mechanism) | △ (possible but complex) |
| Crossing privilege boundaries (UAC, services) | ○ (folder ACL) | ◎ (PipeSecurity) | △ (auth is your own responsibility) | △–○ (◎ with the pipe transport) | ○ (ACL possible, hard to design) | ○ |
| 32/64-bit and mixed languages | ◎ | ○ | ◎ | ◎ (proto generates code for every language) | △ (ABI design required) | ○ (bridging is a strength) |
| Implementation cost | Low | Low–medium | Medium (framing is your own) | Medium (framework to adopt) | High | High (for new designs) |
| Ease of debugging | ◎ (contents persist as files) | ○ | ○ (can be captured) | △ (HTTP/2 + binary) | △ (failures tend to be dramatic) | △ |
| Throughput/latency | Low | Medium–high | Medium | Medium | ◎ | Medium |
One caveat: rather than choosing “whichever option has the most ◎s,” the correct approach is to look only at the rows that matter for your requirements and narrow down by elimination. For example, the moment you know you have “30 image frames per second,” the data plane is a one-way choice — shared memory.
4. Examples of standard configurations
When you map the decision table onto individual requirements, three configurations come up repeatedly in practice.
Configuration 1: Separating a UI app and a Windows service. Put resident processing or work that needs elevated privileges into the service, and keep the UI a normal user process (for how to build the service side, see the companion article published the same day, “Windows Services Article”). Named pipes are the go-to communication channel; starting the protocol as JSON messages plus message mode (or byte mode plus a length prefix) is a solid default, and there’s a path to migrate to gRPC’s named-pipe transport as the variety of calls grows.2 Because the service runs under a different account (such as LocalService), CurrentUserOnly can’t be used — the key is to explicitly define an ACL via PipeSecurity, such as “allow Users to read/write, deny remote.”
Configuration 2: Bridging a 32-bit app to 64-bit functionality. When you want to use a DLL or driver SDK that only runs in 64-bit from an existing 32-bit app, split the 64-bit side out into a separate process. There are two implementation paths: (a) stand up a 64-bit helper process and talk to it over a named pipe, or (b) make it a 64-bit out-of-process COM server. If the calling side is VBA or some older language, (b) is the natural choice; if it’s C# on both ends, (a) is easier both to distribute and to investigate. For (a), reuse the Job Object pattern described in “Handling Child Processes Safely” for launching, monitoring, and coordinating parent/child exit for the helper process.
Configuration 3: High-frequency data from a measurement engine to a display. In a configuration where a measurement or image-processing engine is split into its own process and streams data to the UI, the standard pattern is a two-channel design: control (start/stop/configuration) over a named pipe, and data (frames/waveforms) over shared memory plus a named event. Control happens only a few times a second, so a pipe is plenty; data gets close to zero-copy via shared memory. Splitting the channels lets you optimize the data side’s design (such as a ring buffer) independently of control concerns. See also “Displaying the Status of External Devices” for how engine or external-device status is displayed.
5. Implementation example ── an asynchronous named-pipe server and client
Here’s a minimal .NET 8 implementation that serves as the foundation for a named-pipe setup. It includes message mode, support for multiple clients, and disconnect handling. The protocol is “one UTF-8 JSON message per write,” and it always carries a version field. The example below assumes same-user, same-account communication and uses CurrentUserOnly accordingly. If you’re talking to a service under a different account, as in Configuration 1, drop CurrentUserOnly and explicitly define an ACL with PipeSecurity, as covered next.
First, the server side. It rebuilds a new server stream for every connection accepted, and hands off per-client processing to its own task.
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeServer(string pipeName)
{
// Web defaults: camelCase and case-insensitive. System.Text.Json's own
// defaults are case-sensitive, so without sharing this, "version" would
// fail to bind to Request.Version and a valid request would fall through
// to 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, // one write = one message
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
await pipe.WaitForConnectionAsync(ct);
_ = Task.Run(() => HandleClientAsync(pipe, ct), ct); // isolate per connection
}
}
// Upper bound on a single message. Protects the service's memory even if
// the peer sends an oversized message (the IPC peer is external input too;
// see Chapter 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)
{
// Even in message mode, a single Read is not guaranteed to
// read a whole message. Keep reading until IsMessageComplete
using var ms = new MemoryStream();
do
{
int n = await pipe.ReadAsync(buffer, ct);
if (n == 0) return; // the client disconnected
ms.Write(buffer, 0, n);
if (ms.Length > MaxMessageBytes) return; // over the limit: disconnect
} while (!pipe.IsMessageComplete);
byte[] response = Dispatch(ms.ToArray());
await pipe.WriteAsync(response, ct);
}
}
catch (IOException)
{
// Only this client's channel broke.
// Don't stop the whole server — keep serving other connections
}
}
}
private static byte[] Dispatch(byte[] payload)
{
Request? req;
try { req = JsonSerializer.Deserialize<Request>(payload, JsonOptions); }
catch (JsonException) { req = null; }
// Reject malformed requests, unknown versions, and unknown types here
object result = req switch
{
null => new { version = 1, error = "bad_request" },
// An unspecified version (binds to 0) or an unknown version is rejected here
{ 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);
The client side keeps “connect → request → response” self-contained in a single method, with a timeout and retry built in from the start.
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeClient(string pipeName)
{
// idempotent: only retry when the caller has declared that this request is
// safe to receive twice. The default is "do not retry"
public async Task<TResponse?> RequestAsync<TResponse>(
object request, CancellationToken ct, bool idempotent = false)
{
for (int attempt = 1; ; attempt++)
{
// Set an upper bound not just on the connection but on the whole
// request-response exchange. This prevents an infinite wait if the
// server accepts the connection but hangs without writing a response
using var deadline =
CancellationTokenSource.CreateLinkedTokenSource(ct);
deadline.CancelAfter(TimeSpan.FromSeconds(10));
try
{
using var pipe = new NamedPipeClientStream(
".", pipeName, PipeDirection.InOut,
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
// Don't wait forever. If the server isn't running, this throws
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("The server disconnected.");
ms.Write(buffer, 0, n);
} while (!pipe.IsMessageComplete);
return JsonSerializer.Deserialize<TResponse>(
ms.ToArray(), PipeServer.JsonOptions);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw; // the caller canceled; do not retry
}
catch (Exception ex) when (
ex is IOException or TimeoutException or OperationCanceledException
&& idempotent && attempt < 3)
{
// Retry transient failures such as the server restarting.
// In the "sent the request but the connection dropped before we
// could read the response" case, the request may already have
// been executed, so requests with side effects (like startJob)
// are not retried by default — design them to reject duplicates
// by request ID first, and only then pass idempotent: true
// (see Chapter 6)
await Task.Delay(500 * attempt, ct);
}
}
}
}
Three design decisions in this code deserve a note:
- Include
versionfrom the very first release. There will always come a moment when the UI and the service get updated separately (one side is newer than the other). Having the receiving side “explicitly reject any version it doesn’t know” turns a silent misbehavior into an error you can actually understand. - Decide whether connections are disposable or long-lived. The example above uses the disposable style — connect for every request — which avoids the need to manage disconnect/reconnect state, at the cost of being unsuited to high-frequency calls. Once you need a persistent connection plus server-initiated push notifications, that added complexity is a good signal to move to gRPC’s bidirectional streaming.
- When talking to a service, drop
CurrentUserOnlyand designPipeSecurityinstead. The example above assumes communication between processes owned by the same user. If the peer is a service running under a different account, create the server stream with an ACL viaNamedPipeServerStreamAcl.Create, and explicitly specify which group is allowed to connect.6
6. Common protocol-design concerns ── the work that remains after you’ve picked a mechanism
Whichever IPC mechanism you choose, there are shared challenges in designing the contents of the communication. In fact, these are a bigger source of incidents than the choice of mechanism itself.
- Message boundaries (framing): Except when the OS delineates them for you, as with a pipe’s message mode, “where does one message start and end” is the application’s responsibility. Record boundaries in shared memory are the same problem. Default to a length-prefix scheme.
- Versioning: Include a format version in every message, and have the receiving side “explicitly reject unknown versions.” There’s no guarantee both processes are always updated in lockstep, even if they ship in the same installer.
- Timeouts: Design on the assumption that “the peer not responding” will definitely happen. Set an upper bound on each of connect, request, and response, and don’t leave any code waiting indefinitely. Waiting synchronously on the UI thread is completely out of the question.
- Reconnection and idempotency: Retrying means creating the possibility that the same request arrives twice. Classify each kind of request by whether it’s “safe to execute twice,” and for the ones that aren’t (like submitting a job), attach a request ID to reject duplicates.
- Treat the peer as external input too: For same-machine communication, it’s tempting to skip validation because “the peer is our own app” — but when there’s a privilege boundary involved, the IPC peer is untrusted input, just like something arriving over the network. An elevated broker or a service must never blindly execute a path or command that arrives from a standard-privilege client. Suspecting both “who” and “what” — including pipe-name squatting (Section 2.2) — is the discipline of IPC that crosses a privilege boundary.
- Leave a way to observe what’s happening: Being able to emit communication logs (at minimum, message type, peer, and result) changes the time it takes to investigate “can’t connect” or “no response” issues by an order of magnitude.
7. Summary
You’ll rarely go wrong if you think through IPC choice in this order. First, ask whether same-machine-only is acceptable. If it is: named pipes for request/response, shared memory only for large-volume high-frequency data, and file-based integration for loosely coupled batch work. If crossing machines or mixed languages are on the horizon, go with local TCP or gRPC, and choose gRPC once maintaining a homegrown protocol becomes a burden at scale. Keep COM in reserve as a tool for 32/64 bridging and VBA interop rather than the first candidate for new work — the decision table in this article is simply this flow laid out in table form.
And once you’ve picked a mechanism, make sure the shared design concerns from Chapter 6 — framing, versioning, timeouts, reconnection, and validating the peer — make it into the first release. In practice, IPC trouble is overwhelmingly more often caused by “skipping protocol design” than by “picking the wrong mechanism.” When revisiting process splits or a communication scheme, I’d recommend starting by taking stock of which processes, under whose privileges, exchange what, and how often.
Related articles
- Shared Memory Pitfalls and Practical Best Practices
- The Misconception That You Can Receive One Unit Per Send Over TCP ── Designing Receive Logic That Treats It as a Byte Stream
- Separating Administrator Privileges (Broker) ── Designing Across the UAC Boundary
- How to Build a Windows Service ── A Worker Service Guide
Related consulting areas
Komura Soft LLC handles design reviews for process splitting and communication schemes, integration design with existing assets including 32-bit/64-bit bridging, and root-cause investigation of “can’t connect / no response” communication issues.
- Technical Consulting & Design Review
- Windows App Development
- Legacy Asset Reuse & Migration Support
- Contact
Reference links
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication. An implementation example of a multi-client-capable server and client using NamedPipeServerStream / NamedPipeClientStream. ↩
-
Microsoft Learn, Inter-process communication with gRPC and Named pipes. On running gRPC over named pipes via Kestrel’s ListenNamedPipe since .NET 8, and on access control via PipeSecurity. ↩ ↩2 ↩3
-
Microsoft Learn, MemoryMappedFile Class. The .NET API for memory-mapped files, and on how shared memory created via CreateNew, unbound to any file, is well suited for IPC use. ↩ ↩2
-
Microsoft Learn, Interprocess communications. A list of the IPC mechanisms Windows provides (clipboard, COM, WM_COPYDATA, DDE, file mapping, mailslots, pipes, RPC, Windows Sockets) and guidance on when to use each. ↩ ↩2 ↩3
-
Microsoft Learn, NamedPipeServerStream Class. The server-side stream for named pipes, and the constructors for specifying PipeTransmissionMode and PipeSecurity. ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. On how the default security descriptor for a named pipe grants read access to Everyone and anonymous accounts, and on how access rights are structured. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum. On how CurrentUserOnly permits connections only to a peer created by the same user, and how on Windows it also verifies the elevation level in addition to the user account. ↩ ↩2
-
Microsoft Learn, Inter-process communication with gRPC. Transports for using gRPC as IPC (Unix domain sockets, named pipes), along with security considerations such as verifying the server’s owner and guarding against impersonation. ↩ ↩2
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
How to Choose Where a Windows App Stores Local Data — A Decision Table for SQLite / JSON / Registry / Access
Where — and in what format — should a Windows desktop app store its data? This article organizes the choice between AppData and ProgramDa...
How to Think About Windows Session Isolation — Session 0, RDP, and Running Multiple Users Concurrently
This article untangles the concept of a Windows "session," a topic that consistently confuses Windows app developers. It covers why Sessi...
Preventing Multiple Instances of a Windows App — Named Mutexes and Activating the Existing Window on a Second Launch
This article organizes the classic requirement for business Windows apps — 'don't let the same app launch twice' — around a named Mutex. ...
How to Build and Operate Windows Services ── From Choosing Between Task Scheduler and Services to Turning a BackgroundService into a Windows Service
Should a background process become a Windows service, or is Task Scheduler enough? This guide organizes the practical design work for put...
Integrating Entra ID Authentication into WinForms/WPF Apps — A Practical Architecture with MSAL.NET and the WAM Broker
A practical, hands-on look at integrating Entra ID (formerly Azure AD) authentication into WinForms/WPF desktop apps: the public client m...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Technical Consulting & Design Review
We help clarify design direction, architectural boundaries, lifetime ownership, and how to handle legacy Windows assets.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links