Named Pipe Messaging
In computer science, a named pipe is an extension of the traditional pipe concept on Unix and Unix-like systems and one of the methods of interprocess communication.
Learn more
Overview
- For communication between Windows applications
- The classes are written very dynamically, so that any length of data can be sent/received
NamedPipeSender
- You just have to execute
SendAsync()to send data - The data is sent as a string, but you can also send other types of data (e.g.
int,bool, etc.) by converting them to a string. - The data is sent in two steps:
- First the length of the data is sent
- Then the data itself is sent
Class: NamedPipeClientStream
using CommunityToolkit.WinUI;using Microsoft.UI.Dispatching;using System.Diagnostics;using System.IO.Pipes;using System.Text;
namespace App.NamedPipe;
public class NamedPipeSender{
#region Fields
private readonly string pipeName; private bool isSending;
#endregion
public NamedPipeSender(string pipeName) { this.pipeName = pipeName; }
#region Methods
public async Task<bool> SendAsync(string data) { if (isSending) return false; isSending = true;
try { await using var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.Out);
await pipeClient.ConnectAsync(2000);
// Send data byte length var dataBytesSendLenght = Encoding.UTF8.GetBytes(Encoding.UTF8.GetBytes(data).Length.ToString()); await pipeClient.WriteAsync(dataBytesSendLenght);
// Send data var dataBytes = Encoding.UTF8.GetBytes(data); await pipeClient.WriteAsync(dataBytes);
await pipeClient.FlushAsync(); pipeClient.Close(); await pipeClient.DisposeAsync();
return true; } catch (Exception e) { // Logging error here Trace.WriteLine($"NamedPipeSender Exception: {e.Message}"); return false; } finally { isSending = false; } }
#endregion
}NamedPipeReceiver
- You have to execute
StartAsync()to start the application (or when you want to start the pipe). - You can then subscribe to the
DataReceivedevent and when data is intercepted, this event will return the data. - The data is received in two steps:
- First the length of the data is received
- Then the data itself is received
Class: NamedPipeServerStream
using System.Diagnostics;using System.IO.Pipes;using System.Text;using CommunityToolkit.WinUI;using Microsoft.UI.Dispatching;
namespace App.NamedPipe;
public sealed class NamedPipeReceiver{
#region Fields
private readonly string pipeName; private bool isRunning; private CancellationTokenSource cancelTokenSource = new();
#endregion
public NamedPipeReceiver(string pipeName) { this.pipeName = pipeName; }
#region Methods
public event EventHandler<string>? DataReceived;
public async Task StartAsync() { if (isRunning) { return; }
cancelTokenSource = new CancellationTokenSource(); isRunning = true;
await Task.Run(async () => { try { while (!cancelTokenSource.Token.IsCancellationRequested) { await using var pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.In);
await pipeServer.WaitForConnectionAsync(cancelTokenSource.Token);
try { // First the length of the data is received var dataReadLenght = new byte[10]; var bytesRead = await pipeServer.ReadAsync(dataReadLenght, cancelTokenSource.Token);
if (!int.TryParse(Encoding.UTF8.GetString(dataReadLenght, 0, bytesRead), out var lenghtBytes)) { return; }
// Receive data var data = new byte[lenghtBytes + 1]; bytesRead = await pipeServer.ReadAsync(data, cancelTokenSource.Token); var message = Encoding.UTF8.GetString(data, 0, bytesRead);
await pipeServer.FlushAsync(cancelTokenSource.Token);
OnDataReceived(message); } catch (Exception e) { // Logging error here Trace.WriteLine($"NamedPipeReceiver Exception: {e.Message}"); } finally { pipeServer.Disconnect(); await pipeServer.DisposeAsync(); } } } catch (Exception e) { // Logging error here Trace.WriteLine($"NamedPipeReceiver Exception: {e.Message}"); } }, cancelTokenSource.Token); }
public void Stop() { if (!isRunning) { return; }
cancelTokenSource.Cancel(); isRunning = false; }
private void OnDataReceived(string data) => DataReceived?.Invoke(this, data);
#endregion
}NamedPipeReceiver.DataReceived += NamedPipeReceiverOnDataReceived;
private void NamedPipeReceiverOnDataReceived(object? sender, string e){ Trace.WriteLine($"NamedPipeReceiverOnDataReceived: {e}");}Best practices
- Choose descriptive pipe names (e.g., include your app or company name) to avoid conflicts with other software on the system.
- Use cancellation tokens generously so the server loop can exit quickly during app shutdown or when the user disables the feature.
- Consider adding message framing metadata, such as version numbers, when you need to evolve the payload format without breaking clients.
Security considerations
- Named pipes on Windows are subject to access control lists. Lock down permissions with
PipeSecuritywhen the communication should stay private to administrators or the current user. - Validate and sanitize any content received over the pipe before acting on it to avoid command injection or denial-of-service issues.
- Log failures and unexpected payload sizes so you can diagnose issues from production environments.