Inter-process Communication with WitRPC
Working on engineering systems that often involve legacy components, I frequently face challenges in integrating outdated elements with modern platforms. For example, you may encounter scenarios requiring support for obsolete file formats that can only be opened by a 32-bit component. This limitation poses a dilemma: either remain on an outdated platform, foregoing modern framework advantages like 64-bit addressing, or isolate the legacy component�s logic into a separate process with which the main application communicates.
There are numerous scenarios for inter-process communication (IPC) on the same machine, including:
- Supporting components built for a different platform than the main system.
- Running multiple parallel, independent processes to overcome system limitations.
- Enabling data exchange between several concurrently running applications.
- Many others.
Why WitRPC?
For years, I relied on Windows Communication Foundation (WCF) for its flexibility, particularly its support for optimized transports like named pipes for local interactions. However, WCF was no longer fully available when I migrated to .NET Core. Recent .NET versions have reintroduced some WCF support, but its current status remains uncertain.
One of WCF�s standout features was the ServiceContract, which allows defining a service interface. This approach simplifies development: a client integrating a WCF service generates the required wrapper code automatically, eliminating guesswork about function names or parameters. Unfortunately, alternatives like SignalR and gRPC typically require method calls using plain text method names, complicating code maintenance.
Introducing WitRPC
A few years ago, I discovered the IpcServiceFramework, which uses interfaces to define services and leverages reflection to unpack and invoke methods on the service side. While promising, the project was no longer maintained. Inspired by its core concept, I developed my own implementation, which evolved into WitRPC.
WitRPC is a modern, high-performance successor to WCF, designed specifically for .NET. It enables developers to define service interfaces, choose a transport, and establish full-duplex communication with minimal effort. Supported transports include Named Pipes, TCP, WebSocket, and Memory-Mapped Files (MMF). MMF is particularly useful for ultra-fast, low-latency duplex communication on a local machine, ideal for transferring large volumes of data with maximum efficiency.
Example: Inter-process Communication with WitRPC
You can find an example implementation in the WitRPC GitHub repository.
Step 1: Define the Service Contract
public interface IExampleService
{
event ExampleServiceEventHandler ProcessingStarted;
event ExampleServiceProgressEventHandler ProgressChanged;
event ExampleServiceProcessingEventHandler ProcessingCompleted;
Task<bool> StartProcessing();
Task StopProcessing();
}
public delegate void ExampleServiceEventHandler();
public delegate void ExampleServiceProgressEventHandler(double progress);
public delegate void ExampleServiceProcessingEventHandler(ProcessingStatus status);
WitRPC supports full-duplex communication natively using event callbacks, with delegates like PropertyChangedEventHandler fully supported.
Step 2: Implement the Service in the Agent
The agent process hosts the service:
public class ExampleService : IExampleService
{
public event ExampleServiceEventHandler ProcessingStarted = delegate { };
public event ExampleServiceProgressEventHandler ProgressChanged = delegate { };
public event ExampleServiceProcessingEventHandler ProcessingCompleted = delegate { };
private CancellationTokenSource? CancellationTokenSource { get; set; }
public Task<bool> StartProcessing()
{
if (CancellationTokenSource != null) return Task.FromResult(false);
CancellationTokenSource = new CancellationTokenSource();
Task.Run(Process);
ProcessingStarted();
return Task.FromResult(true);
}
public Task StopProcessing()
{
CancellationTokenSource?.Cancel();
return Task.CompletedTask;
}
private void Process()
{
ProcessingStatus status = ProcessingStatus.Success;
for (int i = 1; i <= 100; i++)
{
if (CancellationTokenSource?.IsCancellationRequested == true)
{
status = ProcessingStatus.Interrupted;
break;
}
ProgressChanged(i);
Thread.Sleep(100);
}
ProcessingCompleted(status);
CancellationTokenSource = null;
}
}
Step 3: Configure and Launch the WitRPC Server in the Agent
var server = WitServerBuilder.Build(options =>
{
options.WithService(new ExampleService());
options.WithMemoryMappedFile("ExampleMemoryMap");
options.WithEncryption();
options.WithJson();
options.WithAccessToken("SecureAccessToken");
options.WithTimeout(TimeSpan.FromSeconds(1));
});
server.StartWaitingForConnection();
Step 4: Create the Client in the Host
The host process connects to the agent:
var client = WitClientBuilder.Build(options =>
{
options.WithMemoryMappedFile("ExampleMemoryMap");
options.WithEncryption();
options.WithJson();
options.WithAccessToken("SecureAccessToken");
options.WithTimeout(TimeSpan.FromSeconds(1));
});
if (!await client.ConnectAsync(TimeSpan.FromSeconds(5), CancellationToken.None))
{
Console.WriteLine("Failed to connect.");
return;
}
var service = client.GetService<IExampleService>();
service.ProcessingStarted += () => Console.WriteLine("Processing started!");
service.ProgressChanged += progress => Console.WriteLine($"Progress: {progress}%");
service.ProcessingCompleted += status => Console.WriteLine($"Processing completed: {status}");