Library / SDK
justinstenning/SharedMemory avatar
justinstenning/SharedMemory

SharedMemory gives .NET processes a ring buffer and an RPC channel over mapped files

C# shared memory classes for sharing data between processes (Array, Buffer and Circular Buffer)

606 stars116 forksC#NOASSERTION

At a glance

What is it?
SharedMemory is a C# class library that puts memory mapped files behind six classes, from a generic shared array to a lock-free circular buffer and a bi-directional RPC channel built on top of it. It targets a specific shape of problem, fast low-level inter-process communication, and the RPC buffer's throughput depends heavily on how the buffer capacity relates to the message size.
Who is it for?
Adopt SharedMemory if you are passing high volume data between .NET processes on one machine and you want the copy to be a memory write rather than a pipe or a serialiser, since the circular buffer and the RPC channel are lock-free and the measured bandwidth in the README is in the tens of gigabytes per second.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the library is for, and the framework story behind it

SharedMemory is described as C# shared memory classes for sharing data between processes, covering an Array, a Buffer, a Circular Buffer and RPC, and the class library provides a set of C# classes that utilise memory mapped files for fast low-level inter-process communication. Originally only for sharing data between processes, the README says, but now also with a simple RPC implementation.

The framework story is stated in one sentence and it explains a lot about the code. The library uses the .NET MemoryMappedFile class in .NET 4.0 and later, and implements its own wrapper class for .NET 3.5.

That dual implementation is the reason the class list looks the way it does. A wrapper for .NET 3.5, where MemoryMappedFile did not exist, and the framework class everywhere else, gives the library a target range that most modern .NET packages would not bother with. The cost is that a 2022 release, v2.3.2, sits in the history, followed by a long gap, then v2.3.3-rc.1 and v2.3.3 in October 2026, so the current release is a jump of several years over the previous stable one.

The package is on NuGet as SharedMemory, the licence is a custom one that GitHub cannot classify, and the last push was on 2026-10-20 with the v2.3.3 release on the same day.

The audience is narrow and specific: developers writing two or more .NET processes on the same Windows machine that need to move a lot of bytes between them quickly. The README's own description of the project, fast low-level inter-process communication, is the right filter for deciding whether it applies to you.

The class hierarchy, from a header to an RPC channel

Six classes make up the library, and they stack on top of each other, so reading the list in order tells you the whole design.

SharedMemory.SharedBuffer is an abstract base class that wraps a memory mapped file, exposing the read and write operations and implementing a small header. That header is the important part: it lets a client open the shared buffer without knowing the size beforehand, which is what makes a buffer usable by two processes that were not configured together.

SharedMemory.BufferWithLocks extends SharedBuffer to provide simple read and write locking support through the use of EventWaitHandles.

SharedMemory.SharedArray is a simple generic array implementation utilising a shared memory buffer, inheriting from BufferWithLocks to provide support for thread synchronisation. This is the type parameterised view of the buffer, where T is the element type.

SharedMemory.BufferReadWrite provides read and write access to a shared memory buffer, with various overloads to support reading and writing structures and copying to and from IntPtr. It also inherits from BufferWithLocks.

SharedMemory.CircularBuffer is the interesting one, a lock-free FIFO circular buffer implementation, also known as a ring buffer. It supports two or more nodes and supports multiple readers and writers, and the lock-free approach is implemented using Interlocked.Exchange and EventWaitHandles.

SharedMemory.RpcBuffer sits on top and is a simple bi-directional RPC channel using SharedMemory.CircularBuffer, supporting a master and slave pair per channel. It is only available in .NET 4.5 and later, or .NET Standard 2.0, which is a stricter floor than the rest of the library.

So the progression is: a mapped file with a header, optional locking, typed and untyped accessors on top of that, a lock-free ring buffer, and an RPC protocol on the ring buffer. Each layer only depends on the one below it.

The shared array, and the producer/consumer pattern it assumes

The usage examples in the README are all the same shape, and that shape is the pattern the library is designed for: a producer and a consumer opening the same named buffer, one of them writing and the other reading.

The shared array example is the simplest:

csharp
    Console.WriteLine("SharedMemory.SharedArray:");
    using (var producer = new SharedMemory.SharedArray<int>("MySharedArray", 10))
    using (var consumer = new SharedMemory.SharedArray<int>("MySharedArray"))
    {
        producer[0] = 123;
        producer[producer.Length - 1] = 456;

Notice the asymmetry in the constructors. The producer passes a length of 10 because it creates the buffer, and the consumer passes no length at all because the header carries the size. That is the header doing its job, and it is the reason the two processes do not have to agree on a size out of band.

The consumer then reads the two values that were written, and the README shows the expected output of 123 and 456, matching what the other three examples produce.

The BufferReadWrite example is the same idea for raw bytes, with overloads that read and write structures and support copying to and from IntPtr. It shows two ways of reading: a plain read into an out parameter, and a read at a given offset, which is how you would treat the buffer as a byte array with a layout of your own.

The CircularBuffer example is the one with a rule you have to respect, stated in a comment in the source: nodeCount must be one larger than the number of writes that must fit in the buffer at any one time. Get that wrong and the reader consumes a message that is still being written, which is the kind of bug that shows up as occasional corrupted data rather than an exception.

The RPC channel and how a request crosses the buffer

The RPC example is the most compact of the four, and it shows the shape of a request and reply over shared memory.

csharp
    Console.WriteLine("SharedMemory.RpcBuffer:");
    // Ensure a unique channel name
    var rpcName = "RpcTest" + Guid.NewGuid().ToString();
    var rpcMaster = new RpcBuffer(rpcName);
    var rpcSlave = new RpcBuffer(rpcName, (msgId, payload) =>
    {
        // Add the two bytes together
        return BitConverter.GetBytes((payload[0] + payload[1]));
    });

The master and the slave are constructed with the same channel name, and the slave is given a handler that takes a message id and a payload and returns a byte array. The master then makes the call and blocks for the result:

csharp
    // Call the remote handler to add 123 and 10
    var result = rpcMaster.RemoteRequest(new byte[] { 123, 10 });
    Console.WriteLine(result); // outputs 133

The channel name is built from a Guid, with the comment saying to ensure a unique channel name, which matters because the name is what identifies the shared buffer. Two unrelated RPC conversations on the same machine that pick the same name will talk to each other, and the failure will look like a corrupted response rather than a collision error.

Underneath, the RPC buffer is the circular buffer, and the Performance section explains the protocol. When an RpcBuffer is created, a buffer capacity can be specified along with the number of nodes in the underlying CircularBuffer instances. A message is sent within one or more packets, where a single packet is made up of a packet header, 64 bytes for RpcProtocol.V1, and the message payload. Ideally there should be enough room in the buffer to hold at least one message, preferably a few, expressed as bufferCapacity times numberOfNodes greater than maxMessageSize.

If the message payload exceeds bufferCapacity minus packetHeaderSize, the message is split into multiple packets. That single sentence explains most of the performance characteristics.

The throughput numbers, and the buffer size trap inside them

The Performance section gives concrete figures, and they are more informative than a headline number would be because the author explains the relationship behind them.

For the RPC buffer: a message size of 512KB that fits in a single packet, which needs a bufferCapacity of at least 1024 times 500 plus 64, might achieve a throughput of around 2k messages per second, whereas with a buffer capacity of only 1KB it achieves around 500 messages per second. And the counterintuitive part: larger buffer capacities do not necessarily mean greater message throughput, since with the 512KB message size example, using a smaller buffer capacity of 256KB actually slightly improves performance. A 1KB message can be sent as a single packet approximately 10k times per second.

The reason is in the sentence before: the throughput depends not only on the message size but on how many packets are required for a single message. A capacity that fits the message exactly leaves no slack, so the buffer fills and the producer waits. A capacity that is a comfortable multiple of the message allows several messages in flight, and the split penalty disappears. A capacity that is a fraction of the message forces every message to be split, which is the worst case.

The circular buffer figures are given separately: the maximum bandwidth achieved was approximately 20GB per second, using 20 nodes of 1MB each with one reader and one writer. The .NET 3.5 implementation is markedly slower at around 14GB per second, probably due to framework level performance improvements.

The chart in the README compares configurations from 2 to 50 nodes with varying numbers of readers and writers, for 1KB against 1MB node buffer sizes, on .NET 3.5 and .NET 4. All the results are from a machine running Windows 10 64-bit with an Intel Core i7-3770K at 3.50GHz and 16GB of DDR3 memory on an ASUS P8Z77-V motherboard, with data selected randomly from an array of 256 buffers populated with random bytes.

Where SharedMemory is the wrong tool

Three limits, and the first one is the most fundamental thing about shared memory.

The first is that it is local to a machine. A memory mapped file is visible to processes on the same computer, and nothing more. The moment the two sides of your data flow live on different hosts, this library has nothing to offer, and a socket, a queue or a message broker is the only option. That is not a defect in the library, it is what shared memory is, but it does mean the question to ask first is whether your processes are co-located.

The second is the single-writer assumption baked into the examples. The producer and consumer pattern with one producer and one consumer is what the documentation demonstrates, and while the circular buffer is explicitly described as supporting multiple readers and writers, the correctness of the overall protocol when several processes write to the same logical channel is not something the README goes into. The CircularBuffer class supports two or more nodes and multiple readers and writers, and the lock-free approach uses Interlocked.Exchange and EventWaitHandles, but the coordination rules for several independent writers are left to the reader of the code.

The third is the RPC design itself. A shared memory RPC channel is a good fit for bulk transfer and for a request that expects a large reply, and a poor fit for a small request where the round trip latency is dominated by the buffer handshake rather than the work. The throughput figures make that explicit: a 1KB message goes about 10k per second, and a 512KB message about 2k per second, so the per-message cost grows with size in a way that a socket does not.

The alternative approach, and it is the obvious one, is to use whatever your runtime already gives you. .NET ships pipes and named pipes, and sockets, and a message queue if you need one. The difference in approach is that those copy bytes through kernel buffers and give you a clean, well-understood contract, while shared memory gives you the lowest possible copy count and a protocol you have to get right yourself. The right answer for most .NET services is the boring one, and this library earns its place in the specific case of moving very large buffers between two processes on one machine.

Editorial conclusion

Adopt SharedMemory if you are passing high volume data between .NET processes on one machine and you want the copy to be a memory write rather than a pipe or a serialiser, since the circular buffer and the RPC channel are lock-free and the measured bandwidth in the README is in the tens of gigabytes per second. Do not adopt it for a distributed system, since shared memory is local to a machine by construction, and do not adopt it for request and response traffic where the RPC round trip is the point and the payload is small, because the README's own figures show throughput falling sharply when the buffer capacity is close to the message size. Verify first that your target framework is .NET 4.5 or later or .NET Standard 2.0, since the RPC buffer is not available on .NET 3.5, that the two processes agree on the node count and buffer size, because a mismatch produces a read of the wrong data rather than an error, and that the licence file permits your use, since GitHub cannot classify it.

Frequently asked questions

What does the SharedMemory NuGet package provide?

A set of C# classes that use memory mapped files for fast low-level inter-process communication, covering a shared array, a raw read and write buffer, a lock-free circular buffer, and a simple bi-directional RPC channel built on the circular buffer. The library uses the .NET MemoryMappedFile class in .NET 4.0 and later, with its own wrapper for .NET 3.5.

How do I install SharedMemory and which frameworks does it support?

The package is published on NuGet as SharedMemory. The library targets .NET 3.5 through current .NET, with the RpcBuffer class available only on .NET 4.5 and later or .NET Standard 2.0, and the current release is v2.3.3 with a v2.3.3-rc.1 release candidate preceding it.

What is the nodeCount rule for SharedMemory.CircularBuffer?

The example in the README states it directly: nodeCount must be one larger than the number of writes that must fit in the buffer at any one time. The constructor takes a name, a nodeCount and a nodeBufferSize, while the consumer opens the same buffer by name only, because the buffer header carries the size.

How fast is the SharedMemory RPC buffer?

The README reports that a 512KB message fitting in a single packet might achieve around 2k messages per second, while a buffer capacity of only 1KB gives around 500 messages per second, and that a 1KB message can be sent as a single packet approximately 10k times per second. Throughput depends on the relationship between buffer capacity and message size rather than on capacity alone.

Is SharedMemory suitable for processes on different machines?

No. The library is built on memory mapped files, which are local to a machine, so it applies only to processes running on the same computer. For data crossing host boundaries you need a network transport such as sockets, pipes or a message broker instead.

Official sources

  1. Issues
  2. justinstenning/SharedMemory on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/justinstenning-sharedmemory.svg)](https://hysenlabs.com/projects/justinstenning-sharedmemory)

Was this useful?