Revision history (2 updates, last updated Sep 7, 2026)
A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.
- Corrected the search description to refer to .NET's named-pipe stream classes instead of a nonexistent NamedPipeStream type. The article body is unchanged. Read the version before this update (DOI: 10.5281/zenodo.22615650)
- Retranslated as a full translation of the current Japanese original. The previous English version was an abridgement that dropped subsections, tables, diagrams, and paragraphs; all of them have been restored to match the Japanese article, and the knowledge map section has been added where the Japanese article has one. The technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.22170909)
- First published
Cite this article(DOI: 10.5281/zenodo.22170908)
This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.
Go Komura (2026). Named Pipes in Practice — Windows' Standard IPC from Design to Security. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22170908 https://comcomponent.com/en/blog/windows-named-pipes-practical-guide/
- DOI (latest version)
- 10.5281/zenodo.22170908
- DOI (this version)
- 10.5281/zenodo.22626863
“I want to send commands from a settings screen to a resident service.” “I want to split off only the work that needs administrator privileges into a separate process.” When you build this kind of inter-process communication (IPC) on Windows, the first thing to consider is a named pipe.
The reason it is the first candidate for communication within the same PC is not only the ease of reading and writing. The big advantage is that you can narrow who connects with Windows ACLs and, when needed, check the peer’s Windows account and do the work with its privileges. Creating a pipe does not make it secure on its own, though; the connection and the permissions have to be configured.123
This article organizes the design in the order shape of the connection, message boundaries, server structure, security, and behavior on failure. It is for developers writing business applications and services on Windows. It takes the “first candidate for same-machine IPC” from the article on the IPC decision table and drills down to the decisions you make at implementation time.
1. The Bottom Line First: Five Design Decisions
Before choosing an API, decide the communication peer, the unit of data, concurrent connections, privileges, and how failures are handled.
| Decision | Basic rule of thumb | Read more |
|---|---|---|
| Who to communicate with | For Windows processes on the same PC, make a named pipe the first candidate. If remote deployment, other OSes, or reuse of an existing protocol matters, consider a TCP-based option | Chapter 2 |
| What counts as one item | If one write is treated as one item, message mode. If you already have framing, byte mode | Chapter 3 |
| How to accept multiple clients | Provide several instances of the same name. For a new .NET implementation, asynchronous I/O and async/await are the straightforward choice | Chapter 4 |
| Who may connect and borrow privileges | Design remote rejection, the ACL, the first-instance guarantee, and the client’s impersonation level as a set | Chapters 5 and 6 |
| What to do when communication fails | Build startup waits, reconnection, a message size limit, and response confirmation into the protocol | Chapter 7 |
With a named pipe, connection control through ACLs and client impersonation are available as Windows mechanisms. With localhost TCP, you have to design separate authentication to establish who the peer is. On the other hand, if you want to extend to remote communication later, communicate with other OSes, or use existing assets such as gRPC, a TCP-based option is advantageous.23
The most important point is never to execute a request whose impersonation failed. Ignore the failure and processing continues with the server’s own privileges rather than the client’s. If you are building a privileged service, read through Chapters 5 and 6, not just the code samples.4
2. How Connections Work: One Shared Name, One Channel per Client
2.1 Separate the Roles of Creating, Connecting, and Reading/Writing
A named pipe is a one-way or bidirectional channel identified by a name such as \\.\pipe\MyCompany.MyApp.Control. The server and the client start with different APIs.1
| Stage | Server side | Client side |
|---|---|---|
| Prepare the channel | Create an instance with CreateNamedPipe |
Use the pipe name the server prepared |
| Connect | Wait for a client with ConnectNamedPipe |
Open the same name with CreateFile |
| Exchange data | Read and write with ReadFile / WriteFile |
Read and write with ReadFile / WriteFile |
After connecting, both sides handle it the same way as file I/O. Beyond specifying the name, grasping the following ideas of instances and direction makes multiple connections and connection errors easier to understand.
2.2 One Instance Handles One Client
Multiple pipes with the same name can be created, and one instance becomes the channel to one client. Sharing a name does not mean that every client shares a single channel. The first CreateNamedPipe specifies the maximum number of instances. PIPE_UNLIMITED_INSTANCES is also available as the upper limit.15
flowchart TB
accTitle: Basic structure of a named pipe
accDescr: The server creates multiple pipe instances of the same name and waits for connections with ConnectNamedPipe; each client opens the name with CreateFile and has a one-to-one bidirectional channel with one instance
s["Server"] --> i1["Instance 1"]
s --> i2["Instance 2"]
s --> i3["Instance 3"]
c1["Client A"] <--> i1
c2["Client B"] <--> i2
c3["Client C"] <--> i3
Figure 1: An example of a bidirectional pipe. By providing several instances of the same name, the server communicates one-to-one with each of several clients.
2.3 “Direction” Is Named from the Server’s Point of View
The client’s access specification must match the direction of the pipe the server created. If they disagree, CreateFile fails.6
| Direction the server creates | Server behavior | Access the client specifies |
|---|---|---|
| Bidirectional | Reads and writes | Can be opened for read, for write, or for both |
| Outbound | Writes only | Open read-only |
| Inbound | Reads only | Open write-only |
If every instance is in use, the client’s CreateFile returns ERROR_PIPE_BUSY. Wait for a free instance with WaitNamedPipe, then call CreateFile again. This is a different failure from the server not having created the pipe yet, so Section 7.1 covers it together with startup-order races.6
2.4 Even for Local Use, Decide How Remote Connections Are Handled
A named pipe can also be used for remote connections over SMB, in the form \\server\pipe\name. In a modern design, however, there is almost no reason to use this path actively, and for local IPC the important thing is not to leave a path you do not use open. Refuse it explicitly with PIPE_REJECT_REMOTE_CLIENTS in Section 5.1.17
3. Choosing a Mode: Decide “How Much Is One Item”
3.1 Message Mode for Request/Response, Byte Mode If You Already Have Boundaries
The difference between the transfer modes is whether the pipe preserves the boundaries of writes.5
| Mode | What the receiver sees | Suited to |
|---|---|---|
Byte mode (PIPE_TYPE_BYTE) |
An unbroken byte stream, the same as TCP | Data that carries its own framing such as a length prefix, or stream transfers |
Message mode (PIPE_TYPE_MESSAGE with message read mode) |
Units in which one write is one message | One-at-a-time requests and responses |
flowchart TB
accTitle: Difference between byte mode and message mode
accDescr: In byte mode three writes become an unbroken byte stream that the receiver has to split on its own, whereas in message mode the unit of each write is preserved and reaches the receiver as-is
bw["Byte mode: write AAA, BB, CCCC"] --> br["Received as the byte stream AAABBCCCC"]
br --> bf["Boundaries (framing) designed on your own"]
mw["Message mode: the same three writes"] --> mr["Received as three items: AAA, BB, CCCC"]
mr --> mf["The unit of each write is preserved"]
Figure 2: In byte mode the receiver manages the boundaries. Message mode can preserve the unit of each write, but a single read does not necessarily receive the whole message.
If you do not yet have your own boundaries and want to treat one write as one request, message mode is convenient. If you already use something like a length-prefixed serialization format, byte mode is fine. In .NET, PipeTransmissionMode.Message is the specification for message type.8
For a message-type bidirectional pipe there is also TransactNamedPipe, which sends a request and receives the response in a single call.9
3.2 Message Type and Read Mode Are Separate Settings
The read mode is set per handle. Specifying PIPE_READMODE_MESSAGE in CreateNamedPipe is a server-side setting. A handle the client opened with CreateFile defaults to byte read mode.6
| Implementation | Server side | Client side |
|---|---|---|
| Win32 | Specify PIPE_TYPE_MESSAGE and PIPE_READMODE_MESSAGE |
After connecting, specify PIPE_READMODE_MESSAGE with SetNamedPipeHandleState |
| .NET | Specify PipeTransmissionMode.Message |
After connecting, set NamedPipeClientStream.ReadMode to Message |
The point is not to assume that making only the server message-typed lets the client read in the same units.
3.3 Even in Message Mode, You Need to Read the Remainder
Preserving message boundaries and fitting the whole message into the receive buffer are two different things. If the buffer is small, ReadFile returns ERROR_MORE_DATA and the message is split. You need a loop that keeps the part received so far, reads the remainder, and concatenates them.56
| Read result | Handling |
|---|---|
| Success | Process the message as complete |
ERROR_MORE_DATA |
There is more, so read the remainder and concatenate |
| Any other error | Do not continue processing the request; treat it as a communication failure such as a disconnect |
If small requests go through but only large requests break, check this remainder-reading logic. Also, unlimited concatenation is not acceptable. Decide a maximum size for one message as part of the protocol, and adopt a policy of disconnecting when the limit is exceeded. This prevents memory waste caused by huge messages.
4. Server Structure: Separate Accepting from Client Handling
4.1 Compare the Synchronous-Thread Design and the Overlapped Design
The server’s basic operation is the cycle create an instance, wait for a connection, read and write, disconnect and move on to the next. To accept several clients at once, run this flow on multiple instances.
| Design | Mechanism | Advantages and caveats |
|---|---|---|
| Synchronous-thread design | Assign one thread per instance and process with synchronous I/O | The code is straightforward. However, it consumes one thread per client, and you need a way to break out of blocking I/O on shutdown |
| Overlapped (asynchronous) design | Create with FILE_FLAG_OVERLAPPED and issue connect, read, and write asynchronously |
A small number of threads can handle completions for multiple instances |
Microsoft’s official sample puts each instance’s event in an array, waits with WaitForMultipleObjects, and processes multiple connections on a single thread. It decouples the number of clients from the number of threads. At larger scale, you can also choose to attach IOCP or thread-pool I/O.10
For how asynchronous I/O itself works, see the article on synchronous and asynchronous I/O.
4.2 For a New .NET Implementation, async/await Is the Straightforward Choice
In .NET you can use NamedPipeServerStream’s WaitForConnectionAsync / ReadAsync / WriteAsync with async/await. Unless there is a particular reason otherwise, I recommend this asynchronous design for new implementations. When the accept loop receives a connection, it hands it off to per-client handling and returns to accepting on the next instance.8
The following is the skeleton of that accept loop. As an example for use between processes of the same user, it specifies PipeOptions.Asynchronous and PipeOptions.CurrentUserOnly. On Windows, CurrentUserOnly restricts connections to the same user and the same elevation level. This is not an example of connecting a service and a UI under different accounts. That case needs the ACL design in Section 5.2.11
// C#: skeleton of a server that accepts multiple clients
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(); // dispose it yourself when leaving before a connection
throw;
}
_ = HandleClientAsync(server, token); // after connecting, ownership passes to the handler
}
If an exception occurs before a connection, the accepting side disposes the stream; after a connection, HandleClientAsync takes over ownership of the stream. That ownership includes not only reading and writing but also disposing it at the end.
This code is a skeleton that omits the communication protocol and the handler body. In production, manage the exceptions and completion of the detached tasks, and combine it with the remainder reading and size limit from Chapter 3, the security from Chapters 5 and 6, and the disconnect handling and response confirmation from Chapter 7. CurrentUserOnly is a convenient option for limiting connectors without writing an ACL yourself, but it alone does not complete a design for a privileged service.
5. Security: Three Server-Side Points and One Client-Side Point
The security advantages of named pipes are obtained only when they are configured correctly. In particular, in the broker design of “an administrator-privileged service plus a low-privileged UI app”, the pipe is the privilege boundary.
5.1 Server Side: Refuse Unnecessary Remote Connections
If you use the pipe as local IPC, specify PIPE_REJECT_REMOTE_CLIENTS in CreateNamedPipe. Remote clients are refused automatically, which closes the path where a pipe you meant for apps on the same PC can also be opened from the network.7
5.2 Server Side: Narrow Connectors and Rights with an ACL
Pass a security descriptor in SECURITY_ATTRIBUTES and make explicit which users and groups may connect. The default ACL is not necessarily strict enough for your use.2
What is easy to overlook here is the write permission you grant clients. If the ACL grants generic write (GENERIC_WRITE / FILE_GENERIC_WRITE), it includes the right equivalent to FILE_CREATE_PIPE_INSTANCE. An authorized client can then create a server instance of the same name itself and intercept subsequent connections.2
Grant reading and writing as the individual rights needed, and do not hand clients the right to create instances. ACL design covers not only “who is allowed” but also “what is allowed”.
5.3 Server Side: Check That the First Instance Has Not Been Claimed Ahead of You
If a malicious process creates a pipe of the same name first, clients connect to that fake server. The server specifies FILE_FLAG_FIRST_PIPE_INSTANCE only when creating the first instance, guaranteeing that it is first. If that fails, suspect that the name was claimed ahead of you and stop.5
This flag is only for the first instance, the one that claims the name. Putting it on the second and later instances too makes creation fail. In an accept loop for multiple clients, do not repeat the same specification on every creation.5
Note, however, that this is a mechanism for the legitimate server to detect an anomaly at startup, not authentication of the connection target by the client. If the legitimate service is absent, an attacker still has room to create a pipe of the same name first and lie in wait.
5.4 Client Side: Decide How Far the Server May Borrow Your Privileges
As a defense against a fake server, the client keeps the impersonation level to the minimum needed. If the design permits only identity verification, specify SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION in CreateFile. The server can then check the account but can no longer borrow its privileges to perform real access.3
| What you want the server to do | Client-side policy |
|---|---|
| Only verify the connector’s identity | Narrow to identification level (SECURITY_IDENTIFICATION) |
| Perform real access, such as opening files with the client’s privileges | Impersonation level (SECURITY_IMPERSONATION) must be permitted. Pair it with verifying that you are connected to the legitimate server |
At identification level, broker processing that performs real access with the client’s privileges is not possible. Conversely, permitting impersonation without verifying the connection target risks having your privileges borrowed by a fake server.
The principle is to permit the impersonation you need only when you can confirm the legitimate connection target, through a guaranteed service start or mutual authentication after connecting. Do not assume that the client-side check is unnecessary because of the claim-ahead detection in Section 5.3.
6. Using Impersonation: Never Execute a Request Whose Impersonation Failed
6.1 Impersonate After Reading the Request, Not Just After Connecting
The API for the server to verify the client’s identity and process with its privileges is ImpersonateNamedPipeClient. Its subject is the security context of the sender of the last message read from that pipe. Read the request first, then call it.4
Impersonation applies to the calling thread. If you open a file in that state, whether access is allowed is judged against the client’s privileges. Even for a privileged service, this is the mechanism for “executing the requested operation with the requester’s privileges”.3
6.2 Make Reading, Confirming Success, and Reverting One Unit
The required order is read the request, attempt impersonation, confirm success, operate with the client’s privileges, and return to the original context.
sequenceDiagram
accTitle: Flow of request handling that uses impersonation
accDescr: The server reads a request from the pipe, confirms that ImpersonateNamedPipeClient succeeded, then performs the operation with the client's privileges and returns to its own context with RevertToSelf. If impersonation fails, it refuses the request without executing it
participant C as Client
participant S as Server
C->>S: Send a request
S->>S: Read the request
S->>S: ImpersonateNamedPipeClient
Note over S: On failure, refuse without executing the request
S->>S: Perform the operation with the client's privileges
S->>S: Return to the original context with RevertToSelf
S->>C: Reply with the result
Figure 3: If impersonation fails, do not execute the request. Even on success, the sequence is not finished until RevertToSelf restores the original context after the work.
Never proceed to processing the request without checking the return value of ImpersonateNamedPipeClient. On failure, the thread has not switched to the client’s privileges, and the server process’s own privileges are used. If the server runs under a privileged account, it lets through operations the client is not allowed to perform. Microsoft’s documentation also states explicitly that on failure the client’s request must not be executed.4
When the work is done, return reliably to the original context with RevertToSelf. Have both the success check and the cleanup in place. Details such as tokens, impersonation levels, and SeImpersonatePrivilege are covered in the article on Windows impersonation tokens.
7. Practical Pitfalls: Design on the Assumption That Communication Will Break
7.1 Treat Waiting for Startup and Waiting for a Free Instance as Different Failures
When you cannot connect, distinguish between the server not having created the pipe yet and every created instance being in use.69
| State | Client action |
|---|---|
| The pipe does not exist | Allow for the server still starting up; wait briefly and retry |
ERROR_PIPE_BUSY |
Wait for a free instance with WaitNamedPipe and retry CreateFile |
| Connected | Proceed to communication. If needed, also set the read mode from Section 3.2 |
flowchart TB
accTitle: Client connection retry flow
accDescr: Open the pipe with CreateFile; if the pipe does not exist, wait briefly and retry; if every instance is in use and ERROR_PIPE_BUSY is returned, wait for a free instance with WaitNamedPipe and then retry; on success, start communicating
cf["Open with CreateFile"] --> ok{"Result?"}
ok -->|"Success"| go["Start communicating"]
ok -->|"Pipe does not exist"| wait1["Wait briefly (server not started)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["Wait for a free instance with WaitNamedPipe"]
wait1 --> cf
wnp --> cf
Figure 4: “Does not exist” and “full” are different states. In both cases, wait as the state requires, then retry the connection.
On the server side, the principle is to start waiting with ConnectNamedPipe before the client starts. Startup-order races can still happen, so build retries into the client side as well.9
7.2 Make Disconnection a Normal Code Path, Not an Emergency
When the peer process exits, reads and writes fail with ERROR_BROKEN_PIPE and the like. Treat this as everyday communication.
When the server detects a disconnect, it detaches the instance with DisconnectNamedPipe and prepares for the next connection. The client reconnects. The idea of “idempotent reconnection”, which does not corrupt state no matter how often it repeats, explained in the article on resuming from sleep, is effective here too.
7.3 Large Messages Need Both “Reading the Remainder” and “a Limit”
The ERROR_MORE_DATA handling in Section 3.3 is the logic for reading a message in pieces. The maximum message size, on the other hand, is a rule for limiting the amount of data you accept. Do not confuse the two.
If a malicious or buggy peer sends a huge message, an implementation that reads the remainder without limit wastes memory. Adopt a policy of defining a limit in the protocol and disconnecting when it is exceeded.
7.4 Distinguish a Successful Write from the Peer Finishing Its Processing
Success of WriteFile does not mean the peer’s app has processed the data. For operations where you need to know that they were actually executed, confirm with a response message.
Also, so that each response can be matched to the request it answers, include the relationship between requests and responses in the protocol. Separating “it was sent” from “the processing finished” is what leads to confirmation of completion in business terms.
8. Summary: Decide the Channel, the Data, and the Privileges Separately
A named pipe combines the ease of use of file I/O with the Windows security model of ACLs and impersonation, and is the first candidate for same-machine IPC. Creating a channel, however, is a different matter from creating a secure protocol that is hard to break.
In designing the connection and the data, start from the fact that one instance handles one client. Choose message mode for request/response and byte mode if you already have framing, and set message read mode on the client side as well. Handling split reads and a maximum size are also needed. For a new .NET implementation that accepts multiple connections, asynchronous I/O and async/await are the straightforward choice.
In designing the privileges, treat remote rejection, an explicit ACL, the first-instance guarantee, and minimizing the client-side impersonation level as a set. If you use impersonation, call it after reading the request, do not execute on failure, and revert with RevertToSelf after the work.
Finally, write out startup order, disconnection, the message size limit, and response confirmation as your own protocol. The practical point is to decide, before you start implementing, on a shape that can recover when communication breaks without exceeding the peer’s privileges, rather than assuming “it is on the same PC, so it will not fail”.
Related Articles
- Choosing Windows Inter-Process Communication — A Decision Table for Named Pipes / TCP / gRPC / Shared Memory / COM
- How to Isolate “Only the Operations That Need Administrator Privileges” in a Windows App, Concretely
- Handling Windows Impersonation Tokens Correctly — Borrowing Privileges per Thread and Reverting Safely
- The Depths of Windows I/O (Part 2) — Synchronous and Asynchronous I/O: What OVERLAPPED Really Means
- Shared Memory Pitfalls and Practical Best Practices
Related Consulting Areas
KomuraSoft LLC handles the design and implementation of systems that involve inter-process communication, such as separating a service from its UI app and isolating administrator privileges; replacing existing IPC (shared memory, homegrown sockets, COM, and so on) with named pipes; and security reviews of a privileged service’s pipe communication. Feel free to get in touch even if you just want to talk through a protocol design.
- Windows Application Development
- Technical Consulting and Design Review
- Bug Investigation and Root-Cause Analysis
- Contact Us
References
-
Microsoft Learn, Named Pipes. On a named pipe being a one-way or bidirectional channel between a pipe server and one or more pipe clients; on every instance sharing the same name while having independent buffers and handles; and on being usable from local and remote processes. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Security and Access Rights. On the composition of named-pipe access rights; on GENERIC_WRITE including FILE_CREATE_PIPE_INSTANCE, so that granting a client generic write also permits creating a server instance; and on reading and writing data being granted as individual access rights. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Impersonating a Named Pipe Client. On impersonation letting the server thread operate within the client’s privileges; on the default impersonation level being SecurityImpersonation; and on the client being able to control the impersonation level with the SECURITY_SQOS_PRESENT flag at CreateFile time (SECURITY_IDENTIFICATION permits identification only). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). On a server-side thread beginning impersonation in the security context of the client of the last message read from the pipe; on returning with RevertToSelf after completion; and on continuing after impersonation fails causing execution in the server process’s own (privileged) context, so the return value must always be checked and on failure the client’s request must not be executed. ↩ ↩2 ↩3
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). On pipe direction (inbound, outbound, bidirectional), byte type and message type (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) and read mode (PIPE_READMODE_MESSAGE), the maximum instance count (PIPE_UNLIMITED_INSTANCES), asynchronous mode via FILE_FLAG_OVERLAPPED, the first-instance guarantee via FILE_FLAG_FIRST_PIPE_INSTANCE, and the default timeout used by WaitNamedPipe. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Named Pipe Client. On the client opening the pipe with CreateFile; on ERROR_PIPE_BUSY when every instance is in use and waiting for a free one with WaitNamedPipe; and on the opened handle defaulting to byte read, blocking, and non-overlapped, with SetNamedPipeHandleState able to change it to message read mode. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). On the two remote-client modes, PIPE_ACCEPT_REMOTE_CLIENTS (accept remote connections and check them against the security descriptor) and PIPE_REJECT_REMOTE_CLIENTS (automatically refuse connections from remote clients). ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). On connecting and reading/writing with NamedPipeServerStream / NamedPipeClientStream, message-unit transfer via PipeTransmissionMode.Message, and handling multiple clients with asynchronous methods. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. On overlapped operations via ReadFileEx / WriteFileEx, a non-consuming read via PeekNamedPipe, TransactNamedPipe sending a request and receiving the response in one call on a message-type bidirectional pipe, and a blocking read before the client starts being able to cause a race. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. On the official sample of a single-threaded server handling simultaneous connections with multiple clients through overlapped operations. On the structure that waits on each instance’s OVERLAPPED structure and event with WaitForMultipleObjects and advances the completed instance’s state machine, and on confirming completion of pending I/O with GetOverlappedResult. ↩
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). On enabling asynchronous I/O with Asynchronous, and on CurrentUserOnly being able to permit connections only with processes of the same user (and the same elevation level). ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Why Arguments Break — The Rules of Windows Command-Line Arguments
Windows passes CreateProcess a single string that the receiver splits. Covers the CommandLineToArgvW, CRT, and .NET rules, ArgumentList, ...
What Is Left After the Parent Dies — Keeping Child Processes in a Job Object
Why do SDK helpers outlive a killed UI and keep the camera or COM port? Designing child process lifetime with Job Objects, KillOnJobClose...
The Win32 Thread Pool API — Concurrency That Does Not Create Threads, with CreateThreadpoolWork
Calling CreateThread everywhere in native code? A primary-source guide to the Win32 thread pool API: the work, timer, wait, and io object...
DllMain and the Loader Lock — The Real Reason You Are Told to "Do Nothing in DLL Initialization"
Why DllMain must not call LoadLibrary or wait on threads: the loader lock serializes DLL notifications, typical deadlocks, deferred initi...
Spurious Wakeups — Why a Condition Variable Wakes "Without a Notification" and How to Wait Correctly on Windows
Condition variable waits can wake with no notification (spurious wakeups). Why Windows allows it, and the correct while-and-predicate wai...
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.
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.
Frequently Asked Questions
Common questions about the topic of this article.
- How should I choose between named pipes and TCP (a localhost socket)?
- For inter-process communication on the same machine, a named pipe is the first candidate. The reason is the security model. A pipe lets you control who may connect at the OS level with a Windows security descriptor (ACL), and the server can check and borrow the connecting peer's Windows account with ImpersonateNamedPipeClient. That contrasts with a localhost TCP port, which anyone can connect to, so you have to establish who the peer is with your own authentication. On the other hand, a TCP-based option is advantageous when the communication is likely to grow into remote communication later, when you also talk to processes on other OSes, or when you want to use existing protocol assets such as gRPC. This decision is also laid out in the article on the IPC decision table.
- Should I use byte mode or message mode?
- If you want to treat one write as one unit of meaning, message mode (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) is convenient. The receiver can read in the units the sender wrote, so you do not have to manage the boundaries yourself. Byte mode is an unbroken byte stream, the same as TCP, and you have to design the framing (a length prefix, for example) yourself. If you are carrying a protocol that already has framing (for example a length-prefixed serialization format), byte mode is fine. One caveat: even in message mode, a split read (ERROR_MORE_DATA) occurs when the receive buffer is smaller than the message, so you still need to handle that. Also, the read mode is a per-handle setting, and CreateNamedPipe sets it only on the server side. The client must specify PIPE_READMODE_MESSAGE with SetNamedPipeHandleState after CreateFile. In .NET the server specifies PipeTransmissionMode.Message, and the client sets NamedPipeClientStream.ReadMode to Message after connecting.
- How do I build a server that talks to several clients at once?
- A named pipe can have multiple instances created under the same name, and one instance handles one client. There are two designs. One is a synchronous design that assigns a thread per client; the implementation is straightforward, but it consumes one thread per client. The other uses asynchronous I/O with FILE_FLAG_OVERLAPPED and has a small number of threads handle ConnectNamedPipe, ReadFile, and WriteFile for every instance; Microsoft's official sample also shows an implementation that processes multiple instances on a single thread. In .NET, NamedPipeServerStream's WaitForConnectionAsync and async/await let you write the asynchronous design about as straightforwardly as the synchronous one. Unless you have a specific reason otherwise, I recommend the .NET asynchronous design for new implementations.
- What is the minimum I should do for named-pipe security?
- There are four points. First, if remote connections are not needed, specify PIPE_REJECT_REMOTE_CLIENTS and explicitly refuse connections over the network. Second, set an appropriate ACL with SECURITY_ATTRIBUTES and narrow the users and groups that may connect (the default ACL is too loose for some uses). Third, specify FILE_FLAG_FIRST_PIPE_INSTANCE when creating the first instance, so you detect name hijacking, where a pipe of the same name is created before yours (do not put this flag on the second and later instances). Fourth, a client that only wants the server to verify its identity should specify SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION on CreateFile, so a fake server cannot borrow (impersonate) its privileges. In a broker design where the server performs real access under the client's privileges, impersonation has to be permitted, so whether you apply this restriction depends on whether the design lets the server borrow privileges.
- Are there caveats when using ImpersonateNamedPipeClient?
- The most important is checking the return value. If you continue after impersonation fails, subsequent operations run with the server process's own (often high) privileges, and operations that should not have been allowed to the client go through. The official documentation also states explicitly that on failure you must not execute the client's request. Also, because impersonation is performed in the context of the last message read from the pipe, you must read something before calling it, and once the work is done you must reliably return to the original context with RevertToSelf. The machinery around impersonation (tokens, impersonation levels, SeImpersonatePrivilege) is explained in detail in the article on impersonation tokens.