Liftoff.Ipc
0.5.0
dotnet add package Liftoff.Ipc --version 0.5.0
NuGet\Install-Package Liftoff.Ipc -Version 0.5.0
<PackageReference Include="Liftoff.Ipc" Version="0.5.0" />
<PackageVersion Include="Liftoff.Ipc" Version="0.5.0" />
<PackageReference Include="Liftoff.Ipc" />
paket add Liftoff.Ipc --version 0.5.0
#r "nuget: Liftoff.Ipc, 0.5.0"
#:package Liftoff.Ipc@0.5.0
#addin nuget:?package=Liftoff.Ipc&version=0.5.0
#tool nuget:?package=Liftoff.Ipc&version=0.5.0
Liftoff.Ipc
Liftoff.Ipc is a small library for typed request/response and server-published events between local .NET processes over Windows named pipes. The demo models a parent application hosting the server and a distributed child executable connecting as its client.
The dependency-free NuGet package targets .NET Framework 4.8, .NET 8, and .NET 10. Endpoints may use different target frameworks: for example, a Revit 2024 add-in on .NET Framework 4.8 can communicate with a child process on .NET 8 while both reference the corresponding build of the same contracts package.
Release maintainers should follow the release runbook. Packaging and publishing are intentionally owned by GitHub Actions rather than the library project.
The library keeps correlation IDs, acknowledgement timeouts, framing, serialization, heartbeats, cancellation, subscriptions, and pipe lifecycle behind one public namespace:
using Liftoff.Ipc;
Parent/child applications (primary use case)
Liftoff.Ipc is designed first for applications where a host process owns the server and launches a child executable as its client. A Revit add-in is a typical example: Revit hosts the server in-process, while an executable distributed with the add-in provides the client UI or performs isolated work.
The recommended lifecycle is:
- The parent creates an
IpcChildProcessHostfor its child executable. - The host creates an authenticated session and starts the server before launching the child.
- The child reads the inherited session and connects without pipe names, keys, ports, or configuration files supplied by the user.
- Stopping the host requests a graceful child exit and forces termination after the configured timeout.
A session uses an unpredictable pipe name, a 256-bit key, current-user pipe isolation, and a mutual HMAC-SHA256 handshake. The key is never sent over the pipe, and application messages are not accepted until both endpoints authenticate.
await using var host = new IpcChildProcessHost(
"Child.exe",
options => options.ShutdownTimeout = TimeSpan.FromSeconds(2));
await host.StartAsync(server =>
server.RegisterHandlersFromAssemblyContaining<AnalyzeModelHandler>());
await host.PublishAsync(new ModelChanged(42));
The child reads the session from its inherited environment and connects automatically:
await using var client = await IpcClient.ConnectFromEnvironmentAsync();
StartAsync rejects an already-running host; RestartAsync intentionally stops and replaces it. PublishBestEffortAsync is available for transient UI or selection events that may race with shutdown. Unexpected serialization and programming errors remain observable.
The lower-level IpcSession, IpcServer, and IpcClient APIs remain available when an application needs a custom launcher, a Windows Job Object, automatic recovery, or another supervision policy.
Fixed-name overloads remain available for independently configured applications. They restrict access to the current Windows user by default but do not authenticate same-user peers; authenticated IpcSession overloads are recommended whenever one process launches the other. See the parent/child application guide for a complete lifecycle, deployment guidance, Revit integration notes, and shutdown considerations.
Shared contracts
Parent and child reference the same small contract assembly. Requests declare their response type, while events use a marker interface:
using System.Runtime.Serialization;
[DataContract]
public sealed record AnalyzeModel(
[property: DataMember(Order = 1)] string ModelName)
: IIpcRequest<AnalysisResult>;
[DataContract]
public sealed record AnalysisResult(
[property: DataMember(Order = 1)] int ElementsAnalyzed);
[DataContract]
public sealed record ModelChanged(
[property: DataMember(Order = 1)] int ElementId)
: IIpcEvent;
Explicit data contracts keep the binary wire representation stable between .NET Framework and modern .NET without bringing serializer packages into a Revit process.
No manual message names or contract versions are required. The library derives an internal identity from the shared CLR type's assembly name and full name. This fits applications where the parent, child, contracts, and IPC library are distributed as one aligned unit.
Server
Handlers follow a mediator-style interface and may report progress:
public sealed class AnalyzeModelHandler
: IIpcRequestHandler<AnalyzeModel, AnalysisResult>
{
public async Task<AnalysisResult> HandleAsync(
AnalyzeModel request,
IpcRequestContext context,
CancellationToken cancellationToken)
{
await context.ReportProgressAsync(50, "Halfway", cancellationToken);
return new AnalysisResult(1_000);
}
}
Create the server, discover handlers, and start listening:
await using var server = IpcServer.Create("my-product.pipe");
server.RegisterHandlersFromAssemblyContaining<AnalyzeModelHandler>();
await server.StartAsync(cancellationToken);
Handler discovery scans only concrete implementations of IIpcRequestHandler<TRequest,TResponse>. Explicit instance and delegate registration are also available. A handler factory can integrate any dependency-injection container without making the library depend on one.
The server accepts multiple clients simultaneously. Each connection has its own request queue, cancellation state, and event subscriptions, while registered handlers are shared across all connections.
Client
await using var client = await IpcClient.ConnectAsync(
"my-product.pipe",
cancellationToken);
var progress = new Progress<IpcProgress>(update =>
Console.WriteLine($"{update.Percent}%: {update.Message}"));
var result = await client.RequestAsync(
new AnalyzeModel("Office Model"),
progress,
cancellationToken);
The acknowledgement timeout answers only whether the server received and queued a request. Completion has no arbitrary transport timeout; callers provide an operation-specific cancellation token when appropriate.
Events
Clients explicitly subscribe to an event type:
await using var subscription =
await client.SubscribeAsync<ModelChanged>(cancellationToken);
await foreach (var changed in subscription.WithCancellation(cancellationToken))
{
Console.WriteLine(changed.ElementId);
}
Server/domain code publishes the shared event type:
var recipients = await server.PublishAsync(
new ModelChanged(elementId),
cancellationToken);
recipients is zero when no connected client subscribed. Disposing a subscription sends an unsubscribe message, waits for acknowledgement, and completes its local async stream. Subscriptions belong to one connection and must be recreated after reconnecting.
Solution boundaries
Liftoff.Ipccontains the reusable public API and all internal named-pipe/protocol machinery.IpcDemo.Contractsis an example application contract assembly shared by the demo parent and child.IpcDemo.Servercontains application handlers and server startup only.IpcDemo.Clientcontains client application code only.IpcDemo.Wpf.ServerandIpcDemo.Wpf.Clientare visual learning tools built with native WPF controls and CommunityToolkit.Mvvm. Their shared presentation models live inIpcDemo.Wpf.Shared.
The server's pipe reader never runs application handlers. It acknowledges and queues requests so transport processing remains responsive. A single queue consumer invokes handlers; a Revit application can make its handler delegate into ExternalEvent without introducing a Revit dependency into this library.
Run the demo
In one terminal:
dotnet run --project IpcDemo.Server --framework net10.0
In another:
dotnet run --project IpcDemo.Client --framework net10.0
Additional scenarios:
dotnet run --project IpcDemo.Client --framework net10.0 -- --fail
dotnet run --project IpcDemo.Client --framework net10.0 -- --cancel-after=1200
dotnet run --project IpcDemo.Client --framework net10.0 -- --events
For the visual demo, run the server station and click Start + launch client. The server creates an authenticated IPC session, launches the client as a tracked child process, and the client connects automatically. Stopping or closing the server also closes the launched client.
dotnet run --project IpcDemo.Wpf.Server
The original manual workflow remains available: click Start listening, run dotnet run --project IpcDemo.Wpf.Client in another terminal, and click Connect.
The client exposes progress, cancellation, remote failure, heartbeats, and typed event subscriptions. The server shows handler work, event publication, and subscriber delivery counts. Changing the server theme publishes a ThemeChanged event so connected clients follow it; clients also request a theme snapshot when connecting so reconnects cannot miss earlier changes. Both apps use native light and dark themes; no commercial or third-party WPF control suite is required.
Tests
dotnet test IpcDemo.slnx
IpcDemo.Tests.Integrationruns on .NET Framework 4.8, .NET 8, and .NET 10 and tests only the public library API through real operating-system named pipes. Test-owned CLR contracts and handlers exercise reflection discovery, requests, progress, errors, cancellation, reconnection, event subscription, publication, and acknowledged unsubscription.IpcDemo.Tests.Unitcovers internal framing and request coordination in memory. These focused implementation tests are deliberately separate from the stable public behavior suite.
Deliberate boundary
The library does not automatically replay an in-flight request after a broken connection. If a connection dies after a destructive command was sent but before its result arrives, the client cannot know whether it executed. Safe retry requires an application-specific idempotency policy.
Likely future production concerns include additional named-pipe access policies, bounded queues/backpressure, and structured diagnostics. IpcChildProcessHost provides a standard lifecycle policy; stronger guarantees such as crash-coupled Windows Job Objects and automatic restart remain application concerns.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Framework | net48 is compatible. net481 was computed. |
-
.NETFramework 4.8
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.