Repository: justinstenning/SharedMemory Branch: main Commit: d80348e07a2e Files: 44 Total size: 407.7 KB Directory structure: gitextract_lcci0mpz/ ├── .gitignore ├── Examples/ │ ├── ClientTest/ │ │ ├── ClientTest.csproj │ │ └── Program.cs │ ├── RpcTest/ │ │ ├── Program.cs │ │ └── RpcTest.csproj │ ├── ServerTest/ │ │ ├── Program.cs │ │ └── ServerTest.csproj │ └── SingleProcess/ │ ├── CommandLineParser.cs │ ├── Program.cs │ └── SingleProcess.csproj ├── LICENSE.md ├── README.md ├── SharedMemory/ │ ├── BufferReadWrite.cs │ ├── BufferWithLocks.cs │ ├── CircularBuffer.cs │ ├── FastStructure.cs │ ├── MemoryMappedFiles/ │ │ ├── MemoryMappedFile.cs │ │ ├── MemoryMappedFileAccess.cs │ │ ├── MemoryMappedFileRights.cs │ │ ├── MemoryMappedView.cs │ │ ├── MemoryMappedViewAccessor.cs │ │ ├── SafeMemoryMappedFileHandle.cs │ │ └── SafeMemoryMappedViewHandle.cs │ ├── RpcBuffer.cs │ ├── SharedArray.cs │ ├── SharedBuffer.cs │ ├── SharedHeader.cs │ ├── SharedMemory.csproj │ ├── SharedMemory.licenseheader │ ├── UnsafeNativeMethods.cs │ └── Utilities/ │ ├── ArraySlice.cs │ └── ExpandingArray.cs ├── SharedMemory.Tests/ │ ├── ArraySliceTests.cs │ ├── ArrayTests.cs │ ├── BufferReadWriteTests.cs │ ├── CircularBufferTests.cs │ ├── ExpandingArrayTests.cs │ ├── FastStructureTests.cs │ ├── RpcBufferTests.cs │ └── SharedMemory.Tests.csproj ├── SharedMemory.nuspec ├── SharedMemory.sln ├── appveyor-develop.yml └── appveyor.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ #ignore thumbnails created by windows Thumbs.db #Ignore files build by Visual Studio *.user *.aps *.pch *.vspscc *_i.c *_p.c *.ncb *.suo *.bak *.cache *.ilk *.log [Bb]in [Dd]ebug*/ *.sbr obj/ [Rr]elease*/ _ReSharper*/ TestResults*/ *.nupkg /.vs/SharedMemory /nuget.exe /.vs/ProjectSettings.json /.vs/slnx.sqlite /.vs/VSWorkspaceState.json # JetBrains Rider .idea/ *.sln.iml ================================================ FILE: Examples/ClientTest/ClientTest.csproj ================================================  Exe netcoreapp3.0;netcoreapp2.0;net47;net46;net45;net4;net35 ================================================ FILE: Examples/ClientTest/Program.cs ================================================ // SharedMemory (File: ClientTest\Program.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using SharedMemory; #if NET40Plus using System.Threading.Tasks; #endif namespace ClientTest { class Program { static void Main(string[] args) { Console.WriteLine("Press to start client"); Console.ReadLine(); Console.WriteLine("Open existing shared memory circular buffer"); using (SharedMemory.CircularBuffer theClient = new SharedMemory.CircularBuffer("TEST")) { Console.WriteLine("Buffer {0} opened, NodeBufferSize: {1}, NodeCount: {2}", theClient.Name, theClient.NodeBufferSize, theClient.NodeCount); long bufferSize = theClient.NodeBufferSize; byte[] writeDataProof; byte[] writeData = new byte[bufferSize]; List dataList = new List(); // Generate data for integrity check for (var j = 0; j < 256; j++) { var data = new byte[bufferSize]; for (var i = 0; i < data.Length; i++) { data[i] = (byte)((i + j) % 255); } dataList.Add(data); } int skipCount = 0; long iterations = 0; long totalBytes = 0; long lastTick = 0; Stopwatch sw = Stopwatch.StartNew(); int threadCount = 0; Action reader = () => { int myThreadIndex = Interlocked.Increment(ref threadCount); int linesOut = 0; bool finalLine = false; for (; ; ) { int amount = theClient.Read(writeData, 100); //int amount = theClient.Read(writeData, 100); if (amount == 0) { Interlocked.Increment(ref skipCount); } else { // Only check data integrity for first thread if (threadCount == 1) { bool mismatch = false; writeDataProof = dataList[((int)Interlocked.Read(ref iterations)) % 255]; for (var i = 0; i < writeDataProof.Length; i++) { if (writeData[i] != writeDataProof[i]) { mismatch = true; Console.WriteLine("Buffers don't match!"); break; } } if (mismatch) break; } Interlocked.Add(ref totalBytes, amount); Interlocked.Increment(ref iterations); } if (threadCount == 1 && Interlocked.Read(ref iterations) > 500) finalLine = true; if (myThreadIndex < 3 && (finalLine || sw.ElapsedTicks - lastTick > 1000000)) { lastTick = sw.ElapsedTicks; Console.WriteLine("Read: {0}, Wait: {1}, {2}MB/s", ((double)totalBytes / 1048576.0).ToString("F0"), skipCount, (((totalBytes / 1048576.0) / sw.ElapsedMilliseconds) * 1000).ToString("F2")); linesOut++; if (finalLine || (myThreadIndex > 1 && linesOut > 10)) { Console.WriteLine("Completed."); break; } } } }; Console.WriteLine("Testing data integrity (high CPU, low bandwidth)..."); reader(); Console.WriteLine(""); skipCount = 0; iterations = 0; totalBytes = 0; lastTick = 0; sw.Reset(); sw.Start(); Console.WriteLine("Testing data throughput (low CPU, high bandwidth)..."); #if NET40Plus Task c1 = Task.Factory.StartNew(reader); Task c2 = Task.Factory.StartNew(reader); Task c3 = Task.Factory.StartNew(reader); //Task c4 = Task.Factory.StartNew(reader); #else ThreadPool.QueueUserWorkItem((o) => { reader(); }); ThreadPool.QueueUserWorkItem((o) => { reader(); }); ThreadPool.QueueUserWorkItem((o) => { reader(); }); #endif Console.ReadLine(); } } } } ================================================ FILE: Examples/RpcTest/Program.cs ================================================ // SharedMemory (File: RpcTest\Program.cs) // Copyright (c) 2020 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using SharedMemory; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace RpcTest { class Program { static void Main(string[] args) { long completed = 0; long count = 0; byte[][] dataList; int loopCount = 2000; int bufSize = 1024 * 500; int bufferCapacity = bufSize + 64; // buf size + enough room for protocol header int threadCount = 1; int dataListCount = 256; // Generate random data to be written Random random = new Random(); dataList = new byte[dataListCount][]; for (var j = 0; j < dataListCount; j++) { var data = new byte[bufSize]; random.NextBytes(data); dataList[j] = data; } Console.WriteLine($"Thread count: {threadCount}"); Console.WriteLine($"Buffer size: {bufferCapacity}"); Console.WriteLine($"Message size: {bufSize}"); Console.WriteLine("Running..."); Stopwatch watch = Stopwatch.StartNew(); for (var i = 0; i < threadCount; i++) { new Task(async () => { RpcBuffer ipcMaster = null; RpcBuffer ipcSlave = null; var name = $"MasterSlaveTest{Guid.NewGuid()}"; ipcMaster = new RpcBuffer(name, bufferCapacity: bufferCapacity); ipcSlave = new RpcBuffer(name, (msgId, payload) => { Interlocked.Increment(ref count); return (byte[])null; //return new byte[] { (byte)(payload[0] * payload[1]) }; }); var rnd = new Random(); var watchLine = Stopwatch.StartNew(); for (var j = 0; j < loopCount; j++) { var result = await ipcMaster.RemoteRequestAsync(dataList[rnd.Next(0, dataList.Length)]); if (!result.Success) { Console.WriteLine("Failed"); return; } } Interlocked.Increment(ref completed); }).Start(); } while(Interlocked.Read(ref completed) < threadCount) { Thread.Sleep(0); } watch.Stop(); Console.WriteLine($"{count} in {watch.Elapsed}, {(int)(count / watch.Elapsed.TotalSeconds)} requests / sec"); Console.ReadLine(); } } } ================================================ FILE: Examples/RpcTest/RpcTest.csproj ================================================  Exe netcoreapp3.0;netcoreapp2.0;net47;net46;net45 ================================================ FILE: Examples/ServerTest/Program.cs ================================================ // SharedMemory (File: ServerTest\Program.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; #if NET40Plus using System.Threading.Tasks; #endif namespace ServerTest { class Program { static void Main(string[] args) { int bufferSize = 1048576; byte[] readData; int size = sizeof(byte) * bufferSize; int count = 50; // Generate data to be written byte[][] dataList = new byte[256][]; for (var j = 0; j < 256; j++) { var data = new byte[bufferSize]; for (var i = 0; i < data.Length; i++) { data[i] = (byte)((i + j) % 255); } dataList[j] = data; } Console.WriteLine("Press to start Server"); Console.ReadLine(); Console.WriteLine("Create shared memory circular buffer"); using (var theServer = new SharedMemory.CircularBuffer("TEST", count, size)) { Console.WriteLine("Circular buffer producer created."); Console.WriteLine("Ready for client..."); Thread.Sleep(1000); int skipCount = 0; long iterations = 0; long totalBytes = 0; long lastTick = 0; Stopwatch sw = Stopwatch.StartNew(); int threadCount = 0; Action writer = () => { int myThreadIndex = Interlocked.Increment(ref threadCount); int linesOut = 0; bool finalLine = false; for (; ; ) { readData = dataList[iterations % 255]; int amount = theServer.Write(readData, 100); //int amount = theServer.Write(readData, 100); if (amount == 0) { Interlocked.Increment(ref skipCount); } else { Interlocked.Add(ref totalBytes, amount); Interlocked.Increment(ref iterations); } if (threadCount == 1 && Interlocked.Read(ref iterations) > 500) finalLine = true; if (myThreadIndex < 3 && (finalLine || sw.ElapsedTicks - lastTick > 1000000)) { lastTick = sw.ElapsedTicks; Console.WriteLine("Write: {0}, Wait: {1}, {2}MB/s", ((double)totalBytes / 1048576.0).ToString("F0"), skipCount, (((totalBytes / 1048576.0) / sw.ElapsedMilliseconds) * 1000).ToString("F2")); linesOut++; if (finalLine || (myThreadIndex > 1 && linesOut > 10)) { Console.WriteLine("Completed."); break; } } } }; writer(); Console.WriteLine(""); skipCount = 0; iterations = 0; totalBytes = 0; lastTick = 0; sw.Reset(); sw.Start(); Console.WriteLine("Testing throughput..."); #if NET40Plus Task s1 = Task.Factory.StartNew(writer); //Task s2 = Task.Factory.StartNew(writer); //Task s3 = Task.Factory.StartNew(writer); #else ThreadPool.QueueUserWorkItem((o) => { writer(); }); #endif Console.ReadLine(); } } } } ================================================ FILE: Examples/ServerTest/ServerTest.csproj ================================================  Exe netcoreapp3.0;netcoreapp2.0;net47;net46;net45;net4;net35 ================================================ FILE: Examples/SingleProcess/CommandLineParser.cs ================================================ ////////////////////////////////////////////////////////////////////////////// // Command Line Argument Parser // ---------------------------- // // Author: hotweird@hotmail.com // // Microsoft Public License (Ms-PL) // // This license governs use of the accompanying software. If you use the software, you // accept this license. If you do not accept the license, do not use the software. // // 1. Definitions // // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the // same meaning here as under U.S. copyright law. // // A "contribution" is the original software, or any additions or changes to the software. // // A "contributor" is any person that distributes its contribution under this license. // // "Licensed patents" are a contributor's patent claims that read directly on its contribution. // // 2. Grant of Rights // // (A) Copyright Grant- Subject to the terms of this license, including the license conditions // and limitations in section 3, each contributor grants you a non-exclusive, worldwide, // royalty-free copyright license to reproduce its contribution, prepare derivative works // of its contribution, and distribute its contribution or any derivative works that you create. // // (B) Patent Grant- Subject to the terms of this license, including the license conditions and // limitations in section 3, each contributor grants you a non-exclusive, worldwide, // royalty-free license under its licensed patents to make, have made, use, sell, offer for // sale, import, and/or otherwise dispose of its contribution in the software or derivative // works of the contribution in the software. // // 3. Conditions and Limitations // // (A) No Trademark License- This license does not grant you rights to use any contributors' // name, logo, or trademarks. // // (B) If you bring a patent claim against any contributor over patents that you claim are // infringed by the software, your patent license from such contributor to the software ends // automatically. // // (C) If you distribute any portion of the software, you must retain all copyright, patent, // trademark, and attribution notices that are present in the software. // // (D) If you distribute any portion of the software in source code form, you may do so only under // this license by including a complete copy of this license with your distribution. If you // distribute any portion of the software in compiled or object code form, you may only do so // under a license that complies with this license. // // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no // express warranties, guarantees or conditions. You may have additional consumer rights under // your local laws which this license cannot change. To the extent permitted under your local // laws, the contributors exclude the implied warranties of merchantability, fitness for a // particular purpose and non-infringement. // // Usage // ----- // // Parsing command line arguments to a console application is a common problem. // This library handles the common task of reading arguments from a command line // and filling in the values in a type. // // To use this library, define a class whose fields represent the data that your // application wants to receive from arguments on the command line. Then call // CommandLine.ParseArguments() to fill the object with the data // from the command line. Each field in the class defines a command line argument. // The type of the field is used to validate the data read from the command line. // The name of the field defines the name of the command line option. // // The parser can handle fields of the following types: // // - string // - int // - uint // - bool // - enum // - array of the above type // // For example, suppose you want to read in the argument list for wc (word count). // wc takes three optional boolean arguments: -l, -w, and -c and a list of files. // // You could parse these arguments using the following code: // // class WCArguments // { // public bool lines; // public bool words; // public bool chars; // public string[] files; // } // // class WC // { // static void Main(string[] args) // { // if (CommandLine.ParseArgumentsWithUsage(args, parsedArgs)) // { // // insert application code here // } // } // } // // So you could call this aplication with the following command line to count // lines in the foo and bar files: // // wc.exe /lines /files:foo /files:bar // // The program will display the following usage message when bad command line // arguments are used: // // wc.exe -x // // Unrecognized command line argument '-x' // /lines[+|-] short form /l // /words[+|-] short form /w // /chars[+|-] short form /c // /files: short form /f // @ Read response file for more options // // That was pretty easy. However, you realy want to omit the "/files:" for the // list of files. The details of field parsing can be controled using custom // attributes. The attributes which control parsing behaviour are: // // ArgumentAttribute // - controls short name, long name, required, allow duplicates, default value // and help text // DefaultArgumentAttribute // - allows omition of the "/name". // - This attribute is allowed on only one field in the argument class. // // So for the wc.exe program we want this: // // using System; // using Utilities; // // class WCArguments // { // [Argument(ArgumentType.AtMostOnce, HelpText="Count number of lines in the input text.")] // public bool lines; // [Argument(ArgumentType.AtMostOnce, HelpText="Count number of words in the input text.")] // public bool words; // [Argument(ArgumentType.AtMostOnce, HelpText="Count number of chars in the input text.")] // public bool chars; // [DefaultArgument(ArgumentType.MultipleUnique, HelpText="Input files to count.")] // public string[] files; // } // // class WC // { // static void Main(string[] args) // { // WCArguments parsedArgs = new WCArguments(); // if (CommandLine.ParseArgumentsWithUsage(args, parsedArgs)) // { // // insert application code here // } // } // } // // // // So now we have the command line we want: // // wc.exe /lines foo bar // // This will set lines to true and will set files to an array containing the // strings "foo" and "bar". // // The new usage message becomes: // // wc.exe -x // // Unrecognized command line argument '-x' // /lines[+|-] Count number of lines in the input text. (short form /l) // /words[+|-] Count number of words in the input text. (short form /w) // /chars[+|-] Count number of chars in the input text. (short form /c) // @ Read response file for more options // Input files to count. (short form /f) // // If you want more control over how error messages are reported, how /help is // dealt with, etc you can instantiate the CommandLine.Parser class. // // // // Cheers, // Peter Hallam // C# Compiler Developer // Microsoft Corp. // // // // // Release Notes // ------------- // // 10/02/2002 Initial Release // 10/14/2002 Bug Fix // 01/08/2003 Bug Fix in @ include files // 10/23/2004 Added user specified help text, formatting of help text to // screen width. Added ParseHelp for /?. // 11/23/2004 Added support for default values. // 02/23/2005 Fix bug with short name and default arguments. ////////////////////////////////////////////////////////////////////////////// namespace CommandLine { using System; using System.Diagnostics; using System.Reflection; using System.Collections; using System.IO; using System.Text; using System.Runtime.InteropServices; /// /// Used to control parsing of command line arguments. /// [Flags] public enum ArgumentType { /// /// Indicates that this field is required. An error will be displayed /// if it is not present when parsing arguments. /// Required = 0x01, /// /// Only valid in conjunction with Multiple. /// Duplicate values will result in an error. /// Unique = 0x02, /// /// Inidicates that the argument may be specified more than once. /// Only valid if the argument is a collection /// Multiple = 0x04, /// /// The default type for non-collection arguments. /// The argument is not required, but an error will be reported if it is specified more than once. /// AtMostOnce = 0x00, /// /// For non-collection arguments, when the argument is specified more than /// once no error is reported and the value of the argument is the last /// value which occurs in the argument list. /// LastOccurenceWins = Multiple, /// /// The default type for collection arguments. /// The argument is permitted to occur multiple times, but duplicate /// values will cause an error to be reported. /// MultipleUnique = Multiple | Unique, } /// /// Allows control of command line parsing. /// Attach this attribute to instance fields of types used /// as the destination of command line argument parsing. /// [AttributeUsage(AttributeTargets.Field)] public class ArgumentAttribute : Attribute { /// /// Allows control of command line parsing. /// /// Specifies the error checking to be done on the argument. public ArgumentAttribute(ArgumentType type) { this.type = type; } /// /// The error checking to be done on the argument. /// public ArgumentType Type { get { return this.type; } } /// /// Returns true if the argument did not have an explicit short name specified. /// public bool DefaultShortName { get { return null == this.shortName; } } /// /// The short name of the argument. /// Set to null means use the default short name if it does not /// conflict with any other parameter name. /// Set to String.Empty for no short name. /// This property should not be set for DefaultArgumentAttributes. /// public string ShortName { get { return this.shortName; } set { Debug.Assert(value == null || !(this is DefaultArgumentAttribute)); this.shortName = value; } } /// /// Returns true if the argument did not have an explicit long name specified. /// public bool DefaultLongName { get { return null == this.longName; } } /// /// The long name of the argument. /// Set to null means use the default long name. /// The long name for every argument must be unique. /// It is an error to specify a long name of String.Empty. /// public string LongName { get { Debug.Assert(!this.DefaultLongName); return this.longName; } set { Debug.Assert(value != ""); this.longName = value; } } /// /// The default value of the argument. /// public object DefaultValue { get { return this.defaultValue; } set { this.defaultValue = value; } } /// /// Returns true if the argument has a default value. /// public bool HasDefaultValue { get { return null != this.defaultValue; } } /// /// Returns true if the argument has help text specified. /// public bool HasHelpText { get { return null != this.helpText; } } /// /// The help text for the argument. /// public string HelpText { get { return this.helpText; } set { this.helpText = value; } } private string shortName; private string longName; private string helpText; private object defaultValue; private ArgumentType type; } /// /// Indicates that this argument is the default argument. /// '/' or '-' prefix only the argument value is specified. /// The ShortName property should not be set for DefaultArgumentAttribute /// instances. The LongName property is used for usage text only and /// does not affect the usage of the argument. /// [AttributeUsage(AttributeTargets.Field)] public class DefaultArgumentAttribute : ArgumentAttribute { /// /// Indicates that this argument is the default argument. /// /// Specifies the error checking to be done on the argument. public DefaultArgumentAttribute(ArgumentType type) : base (type) { } } /// /// A delegate used in error reporting. /// public delegate void ErrorReporter(string message); /// /// Parser for command line arguments. /// /// The parser specification is infered from the instance fields of the object /// specified as the destination of the parse. /// Valid argument types are: int, uint, string, bool, enums /// Also argument types of Array of the above types are also valid. /// /// Error checking options can be controlled by adding a ArgumentAttribute /// to the instance fields of the destination object. /// /// At most one field may be marked with the DefaultArgumentAttribute /// indicating that arguments without a '-' or '/' prefix will be parsed as that argument. /// /// If not specified then the parser will infer default options for parsing each /// instance field. The default long name of the argument is the field name. The /// default short name is the first character of the long name. Long names and explicitly /// specified short names must be unique. Default short names will be used provided that /// the default short name does not conflict with a long name or an explicitly /// specified short name. /// /// Arguments which are array types are collection arguments. Collection /// arguments can be specified multiple times. /// public sealed class Parser { /// /// The System Defined new line string. /// public const string NewLine = "\r\n"; /// /// Don't ever call this. /// private Parser() { } /// /// Parses Command Line Arguments. Displays usage message to Console.Out /// if /?, /help or invalid arguments are encounterd. /// Errors are output on Console.Error. /// Use ArgumentAttributes to control parsing behaviour. /// /// The actual arguments. /// The resulting parsed arguments. /// true if no errors were detected. public static bool ParseArgumentsWithUsage(string [] arguments, object destination) { if (Parser.ParseHelp(arguments) || !Parser.ParseArguments(arguments, destination)) { // error encountered in arguments. Display usage message System.Console.Write(Parser.ArgumentsUsage(destination.GetType())); return false; } return true; } /// /// Parses Command Line Arguments. /// Errors are output on Console.Error. /// Use ArgumentAttributes to control parsing behaviour. /// /// The actual arguments. /// The resulting parsed arguments. /// true if no errors were detected. public static bool ParseArguments(string [] arguments, object destination) { return Parser.ParseArguments(arguments, destination, new ErrorReporter(Console.Error.WriteLine)); } /// /// Parses Command Line Arguments. /// Use ArgumentAttributes to control parsing behaviour. /// /// The actual arguments. /// The resulting parsed arguments. /// The destination for parse errors. /// true if no errors were detected. public static bool ParseArguments(string[] arguments, object destination, ErrorReporter reporter) { Parser parser = new Parser(destination.GetType(), reporter); return parser.Parse(arguments, destination); } private static void NullErrorReporter(string message) { } private class HelpArgument { [ArgumentAttribute(ArgumentType.AtMostOnce, ShortName="?")] public bool help = false; } /// /// Checks if a set of arguments asks for help. /// /// Args to check for help. /// Returns true if args contains /? or /help. public static bool ParseHelp(string[] args) { Parser helpParser = new Parser(typeof(HelpArgument), new ErrorReporter(NullErrorReporter)); HelpArgument helpArgument = new HelpArgument(); helpParser.Parse(args, helpArgument); return helpArgument.help; } /// /// Returns a Usage string for command line argument parsing. /// Use ArgumentAttributes to control parsing behaviour. /// Formats the output to the width of the current console window. /// /// The type of the arguments to display usage for. /// Printable string containing a user friendly description of command line arguments. public static string ArgumentsUsage(Type argumentType) { int screenWidth = Parser.GetConsoleWindowWidth(); if (screenWidth == 0) screenWidth = 80; return ArgumentsUsage(argumentType, screenWidth); } /// /// Returns a Usage string for command line argument parsing. /// Use ArgumentAttributes to control parsing behaviour. /// /// The type of the arguments to display usage for. /// The number of columns to format the output to. /// Printable string containing a user friendly description of command line arguments. public static string ArgumentsUsage(Type argumentType, int columns) { return (new Parser(argumentType, null)).GetUsageString(columns); } private const int STD_OUTPUT_HANDLE = -11; private struct COORD { internal Int16 x; internal Int16 y; } private struct SMALL_RECT { internal Int16 Left; internal Int16 Top; internal Int16 Right; internal Int16 Bottom; } private struct CONSOLE_SCREEN_BUFFER_INFO { internal COORD dwSize; internal COORD dwCursorPosition; internal Int16 wAttributes; internal SMALL_RECT srWindow; internal COORD dwMaximumWindowSize; } [DllImport("kernel32.dll", EntryPoint="GetStdHandle", SetLastError=true, CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)] private static extern int GetStdHandle(int nStdHandle); [DllImport("kernel32.dll", EntryPoint="GetConsoleScreenBufferInfo", SetLastError=true, CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)] private static extern int GetConsoleScreenBufferInfo(int hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); /// /// Returns the number of columns in the current console window /// /// Returns the number of columns in the current console window public static int GetConsoleWindowWidth() { int screenWidth; CONSOLE_SCREEN_BUFFER_INFO csbi = new CONSOLE_SCREEN_BUFFER_INFO(); int rc; rc = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), ref csbi); screenWidth = csbi.dwSize.x; return screenWidth; } /// /// Searches a StringBuilder for a character /// /// The text to search. /// The character value to search for. /// The index to stat searching at. /// The index of the first occurence of value or -1 if it is not found. public static int IndexOf(StringBuilder text, char value, int startIndex) { for (int index = startIndex; index < text.Length; index++) { if (text[index] == value) return index; } return -1; } /// /// Searches a StringBuilder for a character in reverse /// /// The text to search. /// The character to search for. /// The index to start the search at. /// The index of the last occurence of value in text or -1 if it is not found. public static int LastIndexOf(StringBuilder text, char value, int startIndex) { for (int index = Math.Min(startIndex, text.Length - 1); index >= 0; index --) { if (text[index] == value) return index; } return -1; } private const int spaceBeforeParam = 2; /// /// Creates a new command line argument parser. /// /// The type of object to parse. /// The destination for parse errors. public Parser(Type argumentSpecification, ErrorReporter reporter) { this.reporter = reporter; this.arguments = new ArrayList(); this.argumentMap = new Hashtable(); foreach (FieldInfo field in argumentSpecification.GetFields()) { if (!field.IsStatic && !field.IsInitOnly && !field.IsLiteral) { ArgumentAttribute attribute = GetAttribute(field); if (attribute is DefaultArgumentAttribute) { Debug.Assert(this.defaultArgument == null); this.defaultArgument = new Argument(attribute, field, reporter); } else { this.arguments.Add(new Argument(attribute, field, reporter)); } } } // add explicit names to map foreach (Argument argument in this.arguments) { Debug.Assert(!argumentMap.ContainsKey(argument.LongName)); this.argumentMap[argument.LongName] = argument; if (argument.ExplicitShortName) { if (argument.ShortName != null && argument.ShortName.Length > 0) { Debug.Assert(!argumentMap.ContainsKey(argument.ShortName)); this.argumentMap[argument.ShortName] = argument; } else { argument.ClearShortName(); } } } // add implicit names which don't collide to map foreach (Argument argument in this.arguments) { if (!argument.ExplicitShortName) { if (argument.ShortName != null && argument.ShortName.Length > 0 && !argumentMap.ContainsKey(argument.ShortName)) this.argumentMap[argument.ShortName] = argument; else argument.ClearShortName(); } } } private static ArgumentAttribute GetAttribute(FieldInfo field) { object[] attributes = field.GetCustomAttributes(typeof(ArgumentAttribute), false); if (attributes.Length == 1) return (ArgumentAttribute) attributes[0]; Debug.Assert(attributes.Length == 0); return null; } private void ReportUnrecognizedArgument(string argument) { this.reporter(string.Format("Unrecognized command line argument '{0}'", argument)); } /// /// Parses an argument list into an object /// /// /// /// true if an error occurred private bool ParseArgumentList(string[] args, object destination) { bool hadError = false; if (args != null) { foreach (string argument in args) { if (argument.Length > 0) { switch (argument[0]) { case '-': case '/': int endIndex = argument.IndexOfAny(new char[] {':', '+', '-'}, 1); string option = argument.Substring(1, endIndex == -1 ? argument.Length - 1 : endIndex - 1); string optionArgument; if (option.Length + 1 == argument.Length) { optionArgument = null; } else if (argument.Length > 1 + option.Length && argument[1 + option.Length] == ':') { optionArgument = argument.Substring(option.Length + 2); } else { optionArgument = argument.Substring(option.Length + 1); } Argument arg = (Argument) this.argumentMap[option]; if (arg == null) { ReportUnrecognizedArgument(argument); hadError = true; } else { hadError |= !arg.SetValue(optionArgument, destination); } break; case '@': string[] nestedArguments; hadError |= LexFileArguments(argument.Substring(1), out nestedArguments); hadError |= ParseArgumentList(nestedArguments, destination); break; default: if (this.defaultArgument != null) { hadError |= !this.defaultArgument.SetValue(argument, destination); } else { ReportUnrecognizedArgument(argument); hadError = true; } break; } } } } return hadError; } /// /// Parses an argument list. /// /// The arguments to parse. /// The destination of the parsed arguments. /// true if no parse errors were encountered. public bool Parse(string[] args, object destination) { bool hadError = ParseArgumentList(args, destination); // check for missing required arguments foreach (Argument arg in this.arguments) { hadError |= arg.Finish(destination); } if (this.defaultArgument != null) { hadError |= this.defaultArgument.Finish(destination); } return !hadError; } private struct ArgumentHelpStrings { public ArgumentHelpStrings(string syntax, string help) { this.syntax = syntax; this.help = help; } public string syntax; public string help; } /// /// A user firendly usage string describing the command line argument syntax. /// public string GetUsageString(int screenWidth) { ArgumentHelpStrings[] strings = GetAllHelpStrings(); int maxParamLen = 0; foreach (ArgumentHelpStrings helpString in strings) { maxParamLen = Math.Max(maxParamLen, helpString.syntax.Length); } const int minimumNumberOfCharsForHelpText = 10; const int minimumHelpTextColumn = 5; const int minimumScreenWidth = minimumHelpTextColumn + minimumNumberOfCharsForHelpText; int helpTextColumn; int idealMinimumHelpTextColumn = maxParamLen + spaceBeforeParam; screenWidth = Math.Max(screenWidth, minimumScreenWidth); if (screenWidth < (idealMinimumHelpTextColumn + minimumNumberOfCharsForHelpText)) helpTextColumn = minimumHelpTextColumn; else helpTextColumn = idealMinimumHelpTextColumn; const string newLine = "\n"; StringBuilder builder = new StringBuilder(); foreach (ArgumentHelpStrings helpStrings in strings) { // add syntax string int syntaxLength = helpStrings.syntax.Length; builder.Append(helpStrings.syntax); // start help text on new line if syntax string is too long int currentColumn = syntaxLength; if (syntaxLength >= helpTextColumn) { builder.Append(newLine); currentColumn = 0; } // add help text broken on spaces int charsPerLine = screenWidth - helpTextColumn; int index = 0; while (index < helpStrings.help.Length) { // tab to start column builder.Append(' ', helpTextColumn - currentColumn); currentColumn = helpTextColumn; // find number of chars to display on this line int endIndex = index + charsPerLine; if (endIndex >= helpStrings.help.Length) { // rest of text fits on this line endIndex = helpStrings.help.Length; } else { endIndex = helpStrings.help.LastIndexOf(' ', endIndex - 1, Math.Min(endIndex - index, charsPerLine)); if (endIndex <= index) { // no spaces on this line, append full set of chars endIndex = index + charsPerLine; } } // add chars builder.Append(helpStrings.help, index, endIndex - index); index = endIndex; // do new line AddNewLine(newLine, builder, ref currentColumn); // don't start a new line with spaces while (index < helpStrings.help.Length && helpStrings.help[index] == ' ') index ++; } // add newline if there's no help text if (helpStrings.help.Length == 0) { builder.Append(newLine); } } return builder.ToString(); } private static void AddNewLine(string newLine, StringBuilder builder, ref int currentColumn) { builder.Append(newLine); currentColumn = 0; } private ArgumentHelpStrings[] GetAllHelpStrings() { ArgumentHelpStrings[] strings = new ArgumentHelpStrings[NumberOfParametersToDisplay()]; int index = 0; foreach (Argument arg in this.arguments) { strings[index] = GetHelpStrings(arg); index++; } strings[index++] = new ArgumentHelpStrings("@", "Read response file for more options"); if (this.defaultArgument != null) strings[index++] = GetHelpStrings(this.defaultArgument); return strings; } private static ArgumentHelpStrings GetHelpStrings(Argument arg) { return new ArgumentHelpStrings(arg.SyntaxHelp, arg.FullHelpText); } private int NumberOfParametersToDisplay() { int numberOfParameters = this.arguments.Count + 1; if (HasDefaultArgument) numberOfParameters += 1; return numberOfParameters; } /// /// Does this parser have a default argument. /// /// Does this parser have a default argument. public bool HasDefaultArgument { get { return this.defaultArgument != null; } } private bool LexFileArguments(string fileName, out string[] arguments) { string args = null; try { using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { args = (new StreamReader(file)).ReadToEnd(); } } catch (Exception e) { this.reporter(string.Format("Error: Can't open command line argument file '{0}' : '{1}'", fileName, e.Message)); arguments = null; return false; } bool hadError = false; ArrayList argArray = new ArrayList(); StringBuilder currentArg = new StringBuilder(); bool inQuotes = false; int index = 0; // while (index < args.Length) try { while (true) { // skip whitespace while (char.IsWhiteSpace(args[index])) { index += 1; } // # - comment to end of line if (args[index] == '#') { index += 1; while (args[index] != '\n') { index += 1; } continue; } // do one argument do { if (args[index] == '\\') { int cSlashes = 1; index += 1; while (index == args.Length && args[index] == '\\') { cSlashes += 1; } if (index == args.Length || args[index] != '"') { currentArg.Append('\\', cSlashes); } else { currentArg.Append('\\', (cSlashes >> 1)); if (0 != (cSlashes & 1)) { currentArg.Append('"'); } else { inQuotes = !inQuotes; } } } else if (args[index] == '"') { inQuotes = !inQuotes; index += 1; } else { currentArg.Append(args[index]); index += 1; } } while (!char.IsWhiteSpace(args[index]) || inQuotes); argArray.Add(currentArg.ToString()); currentArg.Length = 0; } } catch (System.IndexOutOfRangeException) { // got EOF if (inQuotes) { this.reporter(string.Format("Error: Unbalanced '\"' in command line argument file '{0}'", fileName)); hadError = true; } else if (currentArg.Length > 0) { // valid argument can be terminated by EOF argArray.Add(currentArg.ToString()); } } arguments = (string[]) argArray.ToArray(typeof (string)); return hadError; } private static string LongName(ArgumentAttribute attribute, FieldInfo field) { return (attribute == null || attribute.DefaultLongName) ? field.Name : attribute.LongName; } private static string ShortName(ArgumentAttribute attribute, FieldInfo field) { if (attribute is DefaultArgumentAttribute) return null; if (!ExplicitShortName(attribute)) return LongName(attribute, field).Substring(0,1); return attribute.ShortName; } private static string HelpText(ArgumentAttribute attribute, FieldInfo field) { if (attribute == null) return null; else return attribute.HelpText; } private static bool HasHelpText(ArgumentAttribute attribute) { return (attribute != null && attribute.HasHelpText); } private static bool ExplicitShortName(ArgumentAttribute attribute) { return (attribute != null && !attribute.DefaultShortName); } private static object DefaultValue(ArgumentAttribute attribute, FieldInfo field) { return (attribute == null || !attribute.HasDefaultValue) ? null : attribute.DefaultValue; } private static Type ElementType(FieldInfo field) { if (IsCollectionType(field.FieldType)) return field.FieldType.GetElementType(); else return null; } private static ArgumentType Flags(ArgumentAttribute attribute, FieldInfo field) { if (attribute != null) return attribute.Type; else if (IsCollectionType(field.FieldType)) return ArgumentType.MultipleUnique; else return ArgumentType.AtMostOnce; } private static bool IsCollectionType(Type type) { return type.IsArray; } private static bool IsValidElementType(Type type) { return type != null && ( type == typeof(int) || type == typeof(uint) || type == typeof(string) || type == typeof(bool) || type.IsEnum); } [System.Diagnostics.DebuggerDisplay("Name = {LongName}")] private class Argument { public Argument(ArgumentAttribute attribute, FieldInfo field, ErrorReporter reporter) { this.longName = Parser.LongName(attribute, field); this.explicitShortName = Parser.ExplicitShortName(attribute); this.shortName = Parser.ShortName(attribute, field); this.hasHelpText = Parser.HasHelpText(attribute); this.helpText = Parser.HelpText(attribute, field); this.defaultValue = Parser.DefaultValue(attribute, field); this.elementType = ElementType(field); this.flags = Flags(attribute, field); this.field = field; this.seenValue = false; this.reporter = reporter; this.isDefault = attribute != null && attribute is DefaultArgumentAttribute; if (IsCollection) { this.collectionValues = new ArrayList(); } Debug.Assert(this.longName != null && this.longName != ""); Debug.Assert(!this.isDefault || !this.ExplicitShortName); Debug.Assert(!IsCollection || AllowMultiple, "Collection arguments must have allow multiple"); Debug.Assert(!Unique || IsCollection, "Unique only applicable to collection arguments"); Debug.Assert(IsValidElementType(Type) || IsCollectionType(Type)); Debug.Assert((IsCollection && IsValidElementType(elementType)) || (!IsCollection && elementType == null)); Debug.Assert(!(this.IsRequired && this.HasDefaultValue), "Required arguments cannot have default value"); Debug.Assert(!this.HasDefaultValue || (this.defaultValue.GetType() == field.FieldType), "Type of default value must match field type"); } public bool Finish(object destination) { if (this.SeenValue) { if (this.IsCollection) { this.field.SetValue(destination, this.collectionValues.ToArray(this.elementType)); } } else { if (this.HasDefaultValue) { this.field.SetValue(destination, this.DefaultValue); } } return ReportMissingRequiredArgument(); } private bool ReportMissingRequiredArgument() { if (this.IsRequired && !this.SeenValue) { if (this.IsDefault) reporter(string.Format("Missing required argument '<{0}>'.", this.LongName)); else reporter(string.Format("Missing required argument '/{0}'.", this.LongName)); return true; } return false; } private void ReportDuplicateArgumentValue(string value) { this.reporter(string.Format("Duplicate '{0}' argument '{1}'", this.LongName, value)); } public bool SetValue(string value, object destination) { if (SeenValue && !AllowMultiple) { this.reporter(string.Format("Duplicate '{0}' argument", this.LongName)); return false; } this.seenValue = true; object newValue; if (!ParseValue(this.ValueType, value, out newValue)) return false; if (this.IsCollection) { if (this.Unique && this.collectionValues.Contains(newValue)) { ReportDuplicateArgumentValue(value); return false; } else { this.collectionValues.Add(newValue); } } else { this.field.SetValue(destination, newValue); } return true; } public Type ValueType { get { return this.IsCollection ? this.elementType : this.Type; } } private void ReportBadArgumentValue(string value) { this.reporter(string.Format("'{0}' is not a valid value for the '{1}' command line option", value, this.LongName)); } private bool ParseValue(Type type, string stringData, out object value) { // null is only valid for bool variables // empty string is never valid if ((stringData != null || type == typeof(bool)) && (stringData == null || stringData.Length > 0)) { try { if (type == typeof(string)) { value = stringData; return true; } else if (type == typeof(bool)) { if (stringData == null || stringData == "+") { value = true; return true; } else if (stringData == "-") { value = false; return true; } } else if (type == typeof(int)) { value = int.Parse(stringData); return true; } else if (type == typeof(uint)) { value = int.Parse(stringData); return true; } else { Debug.Assert(type.IsEnum); bool valid = false; foreach (string name in Enum.GetNames(type)) { if (name == stringData) { valid = true; break; } } if (valid) { value = Enum.Parse(type, stringData, true); return true; } } } catch { // catch parse errors } } ReportBadArgumentValue(stringData); value = null; return false; } private void AppendValue(StringBuilder builder, object value) { if (value is string || value is int || value is uint || value.GetType().IsEnum) { builder.Append(value.ToString()); } else if (value is bool) { builder.Append((bool) value ? "+" : "-"); } else { bool first = true; foreach (object o in (System.Array) value) { if (!first) { builder.Append(", "); } AppendValue(builder, o); first = false; } } } public string LongName { get { return this.longName; } } public bool ExplicitShortName { get { return this.explicitShortName; } } public string ShortName { get { return this.shortName; } } public bool HasShortName { get { return this.shortName != null; } } public void ClearShortName() { this.shortName = null; } public bool HasHelpText { get { return this.hasHelpText; } } public string HelpText { get { return this.helpText; } } public object DefaultValue { get { return this.defaultValue; } } public bool HasDefaultValue { get { return null != this.defaultValue; } } public string FullHelpText { get { StringBuilder builder = new StringBuilder(); if (this.HasHelpText) { builder.Append(this.HelpText); } if (this.HasDefaultValue) { if (builder.Length > 0) builder.Append(" "); builder.Append("Default value:'"); AppendValue(builder, this.DefaultValue); builder.Append('\''); } if (this.HasShortName) { if (builder.Length > 0) builder.Append(" "); builder.Append("(short form /"); builder.Append(this.ShortName); builder.Append(")"); } return builder.ToString(); } } public string SyntaxHelp { get { StringBuilder builder = new StringBuilder(); if (this.IsDefault) { builder.Append("<"); builder.Append(this.LongName); builder.Append(">"); } else { builder.Append("/"); builder.Append(this.LongName); Type valueType = this.ValueType; if (valueType == typeof(int)) { builder.Append(":"); } else if (valueType == typeof(uint)) { builder.Append(":"); } else if (valueType == typeof(bool)) { builder.Append("[+|-]"); } else if (valueType == typeof(string)) { builder.Append(":"); } else { Debug.Assert(valueType.IsEnum); builder.Append(":{"); bool first = true; foreach (FieldInfo field in valueType.GetFields()) { if (field.IsStatic) { if (first) first = false; else builder.Append('|'); builder.Append(field.Name); } } builder.Append('}'); } } return builder.ToString(); } } public bool IsRequired { get { return 0 != (this.flags & ArgumentType.Required); } } public bool SeenValue { get { return this.seenValue; } } public bool AllowMultiple { get { return 0 != (this.flags & ArgumentType.Multiple); } } public bool Unique { get { return 0 != (this.flags & ArgumentType.Unique); } } public Type Type { get { return field.FieldType; } } public bool IsCollection { get { return IsCollectionType(Type); } } public bool IsDefault { get { return this.isDefault; } } private string longName; private string shortName; private string helpText; private bool hasHelpText; private bool explicitShortName; private object defaultValue; private bool seenValue; private FieldInfo field; private Type elementType; private ArgumentType flags; private ArrayList collectionValues; private ErrorReporter reporter; private bool isDefault; } private ArrayList arguments; private Hashtable argumentMap; private Argument defaultArgument; private ErrorReporter reporter; } } ================================================ FILE: Examples/SingleProcess/Program.cs ================================================ // SharedMemory (File: SingleProcess\Program.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using CommandLine; #if NET40Plus using System.Threading.Tasks; #endif namespace SingleProcess { class Program { class AppArguments { [Argument(ArgumentType.AtMostOnce, ShortName = "b", DefaultValue = 1048576, HelpText = "The buffer size.")] public int bufferSize = 0; [Argument(ArgumentType.AtMostOnce, ShortName = "n", DefaultValue = 50, HelpText = "The number of nodes.")] public int nodeCount = 0; [Argument(ArgumentType.Required, ShortName = "w", HelpText = "The number of writers.")] public int writers = 0; [Argument(ArgumentType.Required, ShortName = "r", HelpText = "The number of readers.")] public int readers = 0; [Argument(ArgumentType.AtMostOnce, ShortName = "e", DefaultValue = 100000, HelpText = "The number of elements to process.")] public int elements = 0; } static void Main(string[] args) { int elements = 100000; int writeCount = 0; int clientWaitCount = 0; int readCount = 0; int serverWaitCount = 0; long lastTick = 0; int bufferSize = 1048576; int size = sizeof(byte) * bufferSize; int count = 50; // node count within buffer int serverCount = 0; int clientCount = 0; // Process command line AppArguments parsedArgs = new AppArguments(); var validArgs = Parser.ParseArgumentsWithUsage(args, parsedArgs); if (!validArgs) { return; } else { elements = parsedArgs.elements; bufferSize = parsedArgs.bufferSize; serverCount = parsedArgs.writers; clientCount = parsedArgs.readers; size = sizeof(byte) * bufferSize; count = parsedArgs.nodeCount; } Console.WriteLine("Node buffer size: {0}, count: {1}, writers: {2}, readers {3}, elements: {4}", size, count, serverCount, clientCount, elements); int dataListCount = 256; // Generate random data to be written Random random = new Random(); byte[][] dataList = new byte[dataListCount][]; for (var j = 0; j < dataListCount; j++) { var data = new byte[size]; random.NextBytes(data); dataList[j] = data; } long bytesWritten = 0; long bytesRead = 0; string name = Guid.NewGuid().ToString(); var server = new SharedMemory.CircularBuffer(name, count, size); Stopwatch sw = Stopwatch.StartNew(); Action clientAction = () => { byte[] testData = new byte[size]; var client = new SharedMemory.CircularBuffer(name); Stopwatch clientTime = new Stopwatch(); clientTime.Start(); long startTick = 0; long stopTick = 0; for (; ; ) { startTick = clientTime.ElapsedTicks; int amount = client.Read(testData, 100); bytesRead += amount; if (amount == 0) Interlocked.Increment(ref clientWaitCount); else Interlocked.Increment(ref readCount); stopTick = clientTime.ElapsedTicks; if (writeCount > elements && writeCount - readCount == 0) break; } }; for (int c = 0; c < clientCount; c++) { #if NET40Plus Task c1 = Task.Factory.StartNew(clientAction); #else ThreadPool.QueueUserWorkItem((o) => { clientAction(); }); #endif } bool wait = true; int index = 0; Action serverWrite = () => { int serverIndex = Interlocked.Increment(ref index); var writer = (serverIndex == 1 ? server : new SharedMemory.CircularBuffer(name)); bool done = false; TimeSpan doneTime = TimeSpan.MinValue; for (; ; ) { if (writeCount <= elements) { int amount = writer.Write(dataList[random.Next(0, dataListCount)], 100); bytesWritten += amount; if (amount == 0) Interlocked.Increment(ref serverWaitCount); else Interlocked.Increment(ref writeCount); } else { if (!done && serverIndex == 1) { doneTime = sw.Elapsed; done = true; } } if (serverIndex == 1 && sw.ElapsedTicks - lastTick > 1000000) { Console.WriteLine("Write: {0}, Read: {1}, Diff: {5}, Wait(cli/svr): {3}/{2}, {4}MB/s", writeCount, readCount, serverWaitCount, clientWaitCount, (int)((((bytesWritten + bytesRead) / 1048576.0) / sw.ElapsedMilliseconds) * 1000), writeCount - readCount); lastTick = sw.ElapsedTicks; if (writeCount > elements && writeCount - readCount == 0) { Console.WriteLine("Total Time: " + doneTime); wait = false; break; } } } }; for (int s = 0; s < serverCount; s++) { #if NET40Plus Task s1 = Task.Factory.StartNew(serverWrite); #else ThreadPool.QueueUserWorkItem((o) => { serverWrite(); }); #endif } while (wait) Thread.Sleep(100); } } } ================================================ FILE: Examples/SingleProcess/SingleProcess.csproj ================================================  Exe netcoreapp3.0;netcoreapp2.0;net47;net46;net45;net4;net35 ================================================ FILE: LICENSE.md ================================================ SharedMemory Copyright (c) 2014-2020 Justin Stenning http://spazzarama.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The SharedMemory library was inspired by the following Code Project article: "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int ================================================ FILE: README.md ================================================ SharedMemory ============ C# shared memory classes for sharing data between processes (Array, Buffer, Circular Buffer and RPC) [![Build status](https://ci.appveyor.com/api/projects/status/uc32kwm1281y4sie?svg=true)](https://ci.appveyor.com/project/spazzarama/sharedmemory) About ----- The SharedMemory class library provides a set of C# classes that utilise memory mapped files for fast low-level inter-process communication (IPC). Originally only for sharing data between processes, but now also with a simple RPC implementation. The library uses the .NET MemoryMappedFile class in .NET 4.0+, and implements its own wrapper class for .NET 3.5. Classes ------- * `SharedMemory.SharedBuffer` - an abstract base class that wraps a memory mapped file, exposing the read/write operations and implementing a small header to allow clients to open the shared buffer without knowing the size beforehand. * `SharedMemory.BufferWithLocks` - an abstract class that extends SharedMemory.SharedBuffer to provide simple read/write locking support through the use of EventWaitHandles. * `SharedMemory.SharedArray` - a simple generic array implementation utilising a shared memory buffer. Inherits from SharedMemory.BufferWithLocks to provide support for thread synchronisation. * `SharedMemory.BufferReadWrite` - provides read/write access to a shared memory buffer, with various overloads to support reading and writing structures, copying to and from IntPtr and so on. Inherits from SharedMemory.BufferWithLocks to provide support for thread synchronisation. * `SharedMemory.CircularBuffer` - lock-free FIFO circular buffer implementation (aka ring buffer). Supporting 2 or more nodes, this implementation supports multiple readers and writers. The lock-free approach is implemented using Interlocked.Exchange and EventWaitHandles. * `SharedMemory.RpcBuffer` - simple bi-directional RPC channel using `SharedMemory.CircularBuffer`. Supports a master+slave pair per channel. Only available in .NET 4.5+ / .NET Standard 2.0 Example Usage ------------- The output from the of the following examples is: SharedMemory.SharedArray: 123 456 SharedMemory.CircularBuffer: 123 456 SharedMemory.BufferReadWrite: 123 456 SharedMemory.RpcBuffer: 133 **SharedMemory.SharedArray** Console.WriteLine("SharedMemory.SharedArray:"); using (var producer = new SharedMemory.SharedArray("MySharedArray", 10)) using (var consumer = new SharedMemory.SharedArray("MySharedArray")) { producer[0] = 123; producer[producer.Length - 1] = 456; Console.WriteLine(consumer[0]); Console.WriteLine(consumer[consumer.Length - 1]); } **SharedMemory.CircularBuffer** Console.WriteLine("SharedMemory.CircularBuffer:"); using (var producer = new SharedMemory.CircularBuffer(name: "MySharedMemory", nodeCount: 3, nodeBufferSize: 4)) using (var consumer = new SharedMemory.CircularBuffer(name: "MySharedMemory")) { // nodeCount must be one larger than the number // of writes that must fit in the buffer at any one time producer.Write(new int[] { 123 }); producer.Write(new int[] { 456 }); int[] data = new int[1]; consumer.Read(data); Console.WriteLine(data[0]); consumer.Read(data); Console.WriteLine(data[0]); } **SharedMemory.BufferReadWrite** Console.WriteLine("SharedMemory.BufferReadWrite:"); using (var producer = new SharedMemory.BufferReadWrite(name: "MySharedBuffer", bufferSize: 1024)) using (var consumer = new SharedMemory.BufferReadWrite(name: "MySharedBuffer")) { int data = 123; producer.Write(ref data); data = 456; producer.Write(ref data, 1000); int readData; consumer.Read(out readData); Console.WriteLine(readData); consumer.Read(out readData, 1000); Console.WriteLine(readData); } **SharedMemory.RpcBuffer** 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])); }); // Call the remote handler to add 123 and 10 var result = rpcMaster.RemoteRequest(new byte[] { 123, 10 }); Console.WriteLine(result); // outputs 133 Performance ----------- ### RPC Buffer When an `RpcBuffer` is created, a buffer capacity can be specified along with the number of nodes to be created 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 allocated within the underlying buffer to hold at least one message preferably a few (i.e. `bufferCapacity * numberOfNodes > maxMessageSize`). If the message payload exceeds the `bufferCapacity - packetHeaderSize`, then the message is split into multiple packets. Therefore the `RpcBuffer` message throughput depends not only upon the message size, but the relationship between the buffer capacity and the message size (i.e. how many packets are required for a single message). For example, a message size of 512KB that fits in a single packet (i.e. with a `bufferCapacity >= 1024 * 500 + 64`) might achieve a throughput of around 2k messages/sec whereas with a buffer capacity of only 1KB it will achieve around 500 messages/sec. Larger buffer capacities do not necessarily mean greater message throughput, for example 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/sec. ### Circular Buffer The maximum bandwidth achieved was approximately 20GB/s, using 20 nodes of 1MB each with 1 reader and 1 writer. The .NET 3.5 implementation is markedly slower (~14GB/s), probably due to framework level performance improvements. The following chart shows the bandwidth achieved in MB/s using a variety of circular buffer configurations, ranging from 2 nodes to 50 nodes with a varying number of readers/writers, comparing a 1KB vs 1MB node buffer size on .NET 3.5 and .NET 4. ![Circular buffer bandwidth](http://spazzarama.com/wp-content/uploads/2015/12/SharedMemoryBandwidth.png) All results are from a machine running Windows 10 64-bit, Intel Core i7-3770K @ 3.50GHz, 16GB DDR3@1200MHz on an ASUS P8Z77-V motherboard. The data transferred was selected randomly from an array of 256 buffers that had been populated with random bytes. ================================================ FILE: SharedMemory/BufferReadWrite.cs ================================================ // SharedMemory (File: SharedMemory\bufferreadwrite.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; namespace SharedMemory { /// /// Read/Write buffer with support for simple inter-process read/write synchronisation. /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] #endif public unsafe class BufferReadWrite : BufferWithLocks { #region Constructors /// /// Creates a new shared memory buffer with the specified name and size /// /// The name of the shared memory to create /// The size of the buffer public BufferReadWrite(string name, int bufferSize) : base(name, bufferSize, true) { Open(); } /// /// Opens an existing shared memory buffer with the specified name /// /// The name of the shared memory to open public BufferReadWrite(string name) : base(name, 0, false) { Open(); } #endregion #region Writing /// /// Writes an instance of into the buffer /// /// A structure type /// A reference to an instance of to be written /// The offset within the buffer region of the shared memory to write to. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Write(ref T data, long bufferPosition = 0) where T : struct { base.Write(ref data, bufferPosition); } /// /// Writes an array of into the buffer /// /// A structure type /// An array of to be written. The length of this array controls the number of elements to be written. /// The offset within the buffer region of the shared memory to write to. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Write(T[] buffer, long bufferPosition = 0) where T : struct { base.Write(buffer, bufferPosition); } /// /// Writes bytes from the into the shared memory buffer. /// /// A managed pointer to the memory location to be copied into the buffer /// The number of bytes to be copied /// The offset within the buffer region of the shared memory to write to. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Write(IntPtr ptr, int length, long bufferPosition = 0) { base.Write(ptr, length, bufferPosition); } /// /// Prepares an IntPtr to the buffer position and calls to perform the writing. /// /// A function used to write to the buffer. The IntPtr parameter is a pointer to the buffer offset by . /// The offset within the buffer region to start writing from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Write(Action writeFunc, long bufferPosition = 0) { base.Write(writeFunc, bufferPosition); } #endregion #region Reading /// /// Reads an instance of from the buffer /// /// A structure type /// Output parameter that will contain the value read from the buffer /// The offset within the buffer region of the shared memory to read from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Read(out T data, long bufferPosition = 0) where T : struct { base.Read(out data, bufferPosition); } /// /// Reads an array of from the buffer /// /// A structure type /// Array that will contain the values read from the buffer. The length of this array controls the number of elements to read. /// The offset within the buffer region of the shared memory to read from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Read(T[] buffer, long bufferPosition = 0) where T : struct { base.Read(buffer, bufferPosition); } /// /// Reads bytes into the memory location from the shared memory buffer. /// /// A managed pointer to the memory location to copy data into from the buffer /// The number of bytes to be copied /// The offset within the buffer region of the shared memory to read from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Read(IntPtr destination, int length, long bufferPosition = 0) { base.Read(destination, length, bufferPosition); } /// /// Prepares an IntPtr to the buffer position and calls to perform the reading. /// /// A function used to read from the buffer. The IntPtr parameter is a pointer to the buffer offset by . /// The offset within the buffer region of the shared memory to read from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods")] new public void Read(Action readFunc, long bufferPosition = 0) { base.Read(readFunc, bufferPosition); } #endregion } } ================================================ FILE: SharedMemory/BufferWithLocks.cs ================================================ // SharedMemory (File: SharedMemory\BufferWithLocks.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Threading; namespace SharedMemory { /// /// Extends to support simple thread-synchronisation for read/write /// to the buffer by allowing callers to acquire and release read/write locks. /// All buffer read/write operations have been overloaded to first perform a /// using the and respectively. /// By default all read/write operations will not block, it is necessary to first acquire locks /// through calls to and as appropriate, with corresponding /// calls to and to release the locks. /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] [PermissionSet(SecurityAction.InheritanceDemand)] #endif public abstract class BufferWithLocks : SharedBuffer { /// /// An event handle used for blocking write operations. /// protected EventWaitHandle WriteWaitEvent { get; private set; } /// /// An event handle used for blocking read operations. /// protected EventWaitHandle ReadWaitEvent { get; private set; } #region Constructors /// /// Create a new instance with the specified name and buffer size. /// /// The name of the shared memory /// The buffer size in bytes. /// Whether or not the current instance owns the shared memory. If true a new shared memory will be created and initialised otherwise an existing one is opened. protected BufferWithLocks(string name, long bufferSize, bool ownsSharedMemory) : base(name, bufferSize, ownsSharedMemory) { WriteWaitEvent = new EventWaitHandle(true, EventResetMode.ManualReset, Name + "_evt_write"); ReadWaitEvent = new EventWaitHandle(true, EventResetMode.ManualReset, Name + "_evt_read"); } #endregion #region Synchronisation private int _readWriteTimeout = 100; /// /// The Read/Write operation timeout in milliseconds (to prevent deadlocks). Defaults to 100ms and must be larger than -1. /// If a Read or Write operation's WaitEvent does not complete within this timeframe a will be thrown. /// If using AcquireReadLock/ReleaseReadLock and AcquireWriteLock/ReleaseWriteLock correctly this timeout will never occur. /// public virtual int ReadWriteTimeout { get { return _readWriteTimeout; } set { if (value < 0) throw new ArgumentOutOfRangeException("ReadWriteTimeout", "Must be larger than -1."); _readWriteTimeout = value; } } /// /// Blocks the current thread until it is able to acquire a read lock. If successful all subsequent writes will be blocked until after a call to . /// /// The number of milliseconds to wait, or (-1) to wait indefinitely. /// true if the read lock was able to be acquired, otherwise false. /// is a negative number other than -1, which represents an infinite time-out. /// If is (-1), then attempting to acquire a read lock after acquiring a write lock on the same thread will result in a deadlock. public bool AcquireReadLock(int millisecondsTimeout = System.Threading.Timeout.Infinite) { if (!ReadWaitEvent.WaitOne(millisecondsTimeout)) return false; WriteWaitEvent.Reset(); return true; } /// /// Releases the current read lock, allowing all blocked writes to continue. /// public void ReleaseReadLock() { WriteWaitEvent.Set(); } /// /// Blocks the current thread until it is able to acquire a write lock. If successful all subsequent reads will be blocked until after a call to . /// /// The number of milliseconds to wait, or System.Threading.Timeout.Infinite (-1) to wait indefinitely. /// true if the write lock was able to be acquired, otherwise false. /// is a negative number other than -1, which represents an infinite time-out. /// If is (-1), then attempting to acquire a write lock after acquiring a read lock on the same thread will result in a deadlock. public bool AcquireWriteLock(int millisecondsTimeout = System.Threading.Timeout.Infinite) { if (!WriteWaitEvent.WaitOne(millisecondsTimeout)) return false; ReadWaitEvent.Reset(); return true; } /// /// Releases the current write lock, allowing all blocked reads to continue. /// public void ReleaseWriteLock() { ReadWaitEvent.Set(); } #endregion #region Writing /// /// Prevents write operations from deadlocking by throwing a TimeoutException if the WriteWaitEvent is not available within milliseconds /// private void WriteWait() { if (!WriteWaitEvent.WaitOne(ReadWriteTimeout)) throw new TimeoutException("The write operation timed out waiting for the write lock WaitEvent. Check your usage of AcquireWriteLock/ReleaseWriteLock and AcquireReadLock/ReleaseReadLock."); } /// /// Writes an instance of into the buffer /// /// A structure type /// A reference to an instance of to be written /// The offset within the buffer region of the shared memory to write to. protected override void Write(ref T data, long bufferPosition = 0) { WriteWait(); base.Write(ref data, bufferPosition); } /// /// Writes an array of into the buffer /// /// A structure type /// An array of to be written. The length of this array controls the number of elements to be written. /// The offset within the buffer region of the shared memory to write to. protected override void Write(T[] buffer, long bufferPosition = 0) { WriteWait(); base.Write(buffer, bufferPosition); } /// /// Writes bytes from the into the shared memory buffer. /// /// A managed pointer to the memory location to be copied into the buffer /// The number of bytes to be copied /// The offset within the buffer region of the shared memory to write to. protected override void Write(IntPtr ptr, int length, long bufferPosition = 0) { WriteWait(); base.Write(ptr, length, bufferPosition); } /// /// Prepares an IntPtr to the buffer position and calls to perform the writing. /// /// A function used to write to the buffer. The IntPtr parameter is a pointer to the buffer offset by . /// The offset within the buffer region to start writing from. protected override void Write(Action writeFunc, long bufferPosition = 0) { WriteWait(); base.Write(writeFunc, bufferPosition); } #endregion #region Reading /// /// Prevents read operations from deadlocking by throwing a TimeoutException if the ReadWaitEvent is not available within milliseconds /// private void ReadWait() { if (!ReadWaitEvent.WaitOne(ReadWriteTimeout)) throw new TimeoutException("The read operation timed out waiting for the read lock WaitEvent. Check your usage of AcquireWriteLock/ReleaseWriteLock and AcquireReadLock/ReleaseReadLock."); } /// /// Reads an instance of from the buffer /// /// A structure type /// Output parameter that will contain the value read from the buffer /// The offset within the buffer region of the shared memory to read from. protected override void Read(out T data, long bufferPosition = 0) { ReadWait(); base.Read(out data, bufferPosition); } /// /// Reads an array of from the buffer /// /// A structure type /// Array that will contain the values read from the buffer. The length of this array controls the number of elements to read. /// The offset within the buffer region of the shared memory to read from. protected override void Read(T[] buffer, long bufferPosition = 0) { ReadWait(); base.Read(buffer, bufferPosition); } /// /// Reads bytes into the memory location from the shared memory buffer. /// /// A managed pointer to the memory location to copy data into from the buffer /// The number of bytes to be copied /// The offset within the buffer region of the shared memory to read from. protected override void Read(IntPtr destination, int length, long bufferPosition = 0) { ReadWait(); base.Read(destination, length, bufferPosition); } /// /// Prepares an IntPtr to the buffer position and calls to perform the reading. /// /// A function used to read from the buffer. The IntPtr parameter is a pointer to the buffer offset by . /// The offset within the buffer region of the shared memory to read from. protected override void Read(Action readFunc, long bufferPosition = 0) { ReadWait(); base.Read(readFunc, bufferPosition); } #endregion #region IDisposable /// /// IDisposable pattern /// /// true to release managed resources protected override void Dispose(bool disposeManagedResources) { if (disposeManagedResources) { (WriteWaitEvent as IDisposable).Dispose(); (ReadWaitEvent as IDisposable).Dispose(); } base.Dispose(disposeManagedResources); } #endregion } } ================================================ FILE: SharedMemory/CircularBuffer.cs ================================================ // SharedMemory (File: SharedMemory\CircularBuffer.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.IO.MemoryMappedFiles; using System.Linq; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Threading; namespace SharedMemory { /// /// A lock-free FIFO shared memory circular buffer (or ring buffer) utilising a . /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] [PermissionSet(SecurityAction.InheritanceDemand)] #endif public unsafe class CircularBuffer : SharedBuffer { #region Public/Protected properties /// /// The number of nodes within the circular linked-list /// public int NodeCount { get; private set; } /// /// The buffer size of each node /// public int NodeBufferSize { get; private set; } /// /// Event signaled when data has been written if the reading index has caught up to the writing index /// protected EventWaitHandle DataExists { get; set; } /// /// Event signaled when a node becomes available after reading if the writing index has caught up to the reading index /// protected EventWaitHandle NodeAvailable { get; set; } /// /// The offset relative to where the node header starts within the buffer region of the shared memory /// protected virtual long NodeHeaderOffset { get { return 0; } } /// /// Where the linked-list nodes are located within the buffer /// protected virtual long NodeOffset { get { return NodeHeaderOffset + Marshal.SizeOf(typeof(NodeHeader)); } } /// /// Where the list of buffers are located within the shared memory /// protected virtual long NodeBufferOffset { get { return NodeOffset + (Marshal.SizeOf(typeof(Node)) * NodeCount); } } /// /// Provide direct access to the Node[] memory /// /// /// protected virtual Node* this[int i] { get { if (i < 0 || i >= NodeCount) throw new ArgumentOutOfRangeException(); return ((Node*)(BufferStartPtr + NodeOffset)) + i; } } #endregion #region Private field members private NodeHeader* _nodeHeader = null; #endregion #region Structures /// /// Provides cursors for the circular buffer along with dimensions /// /// This structure is the same size on 32-bit and 64-bit architectures. [StructLayout(LayoutKind.Sequential)] public struct NodeHeader { /// /// The index of the first unreadable node /// public volatile int ReadEnd; /// /// The index of the next readable node /// public volatile int ReadStart; /// /// The index of the first unwritable node /// public volatile int WriteEnd; /// /// The index of the next writable node /// public volatile int WriteStart; /// /// The number of nodes within the buffer /// public int NodeCount; /// /// The size of the buffer for each node /// public int NodeBufferSize; } /// /// Represents a node within the buffer's circular linked list /// /// This structure is the same size on 32-bit and 64-bit architectures. [StructLayout(LayoutKind.Sequential)] public struct Node { /// /// The previous node. /// public int Next; /// /// The next node. /// public int Prev; /// /// A flag used while returning a node for writing after having been read. /// public volatile int DoneRead; /// /// A flag used while posting a node for reading after writing is completed. /// public volatile int DoneWrite; /// /// Represents the offset relative to where the data for this node can be found. /// public long Offset; /// /// Represents the index of the current node. /// public int Index; /// /// Holds the number of bytes written into this node. /// public int AmountWritten; } #endregion #region Constructors /// /// Creates and opens a new instance with the specified name, node count and buffer size per node. /// /// The name of the shared memory to be created /// The number of nodes within the circular linked-list (minimum of 2) /// The buffer size per node in bytes. The total shared memory size will be Marshal.SizeOf(SharedMemory.SharedHeader) + Marshal.SizeOf(CircularBuffer.NodeHeader) + (Marshal.SizeOf(CircularBuffer.Node) * nodeCount) + (bufferSize * nodeCount) /// /// The maximum total shared memory size is dependent upon the system and current memory fragmentation. /// The shared memory layout on 32-bit and 64-bit architectures is:
/// /// | Header | NodeHeader | Node[0] | ... | Node[N-1] | buffer[0] | ... | buffer[N-1] |
/// | 16-bytes | 24-bytes | 32-bytes * N | NodeBufferSize * N |
/// |------------------------------BufferSize-----------------------------------|
/// |-----------------------------------------SharedMemorySize---------------------------------------| ///
///
///
public CircularBuffer(string name, int nodeCount, int nodeBufferSize) : this(name, nodeCount, nodeBufferSize, true) { Open(); } /// /// Opens an existing with the specified name. /// /// The name of an existing previously created with =true public CircularBuffer(string name) : this(name, 0, 0, false) { Open(); } private CircularBuffer(string name, int nodeCount, int nodeBufferSize, bool ownsSharedMemory) : base(name, Marshal.SizeOf(typeof(NodeHeader)) + (Marshal.SizeOf(typeof(Node)) * nodeCount) + (nodeCount * (long)nodeBufferSize), ownsSharedMemory) { #region Argument validation if (ownsSharedMemory && nodeCount < 2) throw new ArgumentOutOfRangeException("nodeCount", nodeCount, "The node count must be a minimum of 2."); #if DEBUG else if (!ownsSharedMemory && (nodeCount != 0 || nodeBufferSize > 0)) System.Diagnostics.Debug.Write("Node count and nodeBufferSize are ignored when opening an existing shared memory circular buffer.", "Warning"); #endif #endregion if (IsOwnerOfSharedMemory) { NodeCount = nodeCount; NodeBufferSize = nodeBufferSize; } } #endregion #region Open / Close /// /// Attempts to create the handles and initialise the node header and buffers. /// /// True if the events and nodes were initialised successfully. protected override bool DoOpen() { // Create signal events DataExists = new EventWaitHandle(false, EventResetMode.AutoReset, Name + "_evt_dataexists"); NodeAvailable = new EventWaitHandle(false, EventResetMode.AutoReset, Name + "_evt_nodeavail"); if (IsOwnerOfSharedMemory) { // Retrieve pointer to node header _nodeHeader = (NodeHeader*)(BufferStartPtr + NodeHeaderOffset); // Initialise the node header InitialiseNodeHeader(); // Initialise nodes entries InitialiseLinkedListNodes(); } else { // Load the NodeHeader _nodeHeader = (NodeHeader*)(BufferStartPtr + NodeHeaderOffset); NodeCount = _nodeHeader->NodeCount; NodeBufferSize = _nodeHeader->NodeBufferSize; } return true; } /// /// Initialises the node header within the shared memory. Only applicable if is true. /// private void InitialiseNodeHeader() { if (!IsOwnerOfSharedMemory) return; NodeHeader header = new NodeHeader(); header.ReadStart = 0; header.ReadEnd = 0; header.WriteEnd = 0; header.WriteStart = 0; header.NodeBufferSize = NodeBufferSize; header.NodeCount = NodeCount; base.Write(ref header, NodeHeaderOffset); } /// /// Initialise the nodes of the circular linked-list. Only applicable if is true. /// private void InitialiseLinkedListNodes() { if (!IsOwnerOfSharedMemory) return; int N = 0; Node[] nodes = new Node[NodeCount]; // First node nodes[N].Next = 1; nodes[N].Prev = NodeCount - 1; nodes[N].Offset = NodeBufferOffset; nodes[N].Index = N; // Middle nodes for (N = 1; N < NodeCount - 1; N++) { nodes[N].Next = N + 1; nodes[N].Prev = N - 1; nodes[N].Offset = NodeBufferOffset + (NodeBufferSize * N); nodes[N].Index = N; } // Last node nodes[N].Next = 0; nodes[N].Prev = NodeCount - 2; nodes[N].Offset = NodeBufferOffset + (NodeBufferSize * N); nodes[N].Index = N; // Write the nodes to the shared memory base.WriteArray(nodes, 0, nodes.Length, NodeOffset); } /// /// Closes the events. The shared memory could still be open within one or more other instances. /// protected override void DoClose() { if (DataExists != null) { (DataExists as IDisposable).Dispose(); DataExists = null; (NodeAvailable as IDisposable).Dispose(); NodeAvailable = null; } _nodeHeader = null; } #endregion #region Node Writing /// /// Attempts to reserve a node from the linked-list for writing with the specified timeout. /// /// The number of milliseconds to wait if a node is not immediately available for writing. /// An unsafe pointer to the node if successful, otherwise null protected virtual Node* GetNodeForWriting(int timeout) { for (; ; ) { int blockIndex = _nodeHeader->WriteStart; Node* node = this[blockIndex]; if (node->Next == _nodeHeader->ReadEnd) { // No room is available, wait for room to become available if (NodeAvailable.WaitOne(timeout)) continue; // Timeout return null; } #pragma warning disable 0420 // ignore ref to volatile warning - Interlocked API if (Interlocked.CompareExchange(ref _nodeHeader->WriteStart, node->Next, blockIndex) == blockIndex) return node; #pragma warning restore 0420 // Another thread has already acquired this node for writing, try again. continue; } } /// /// Makes a node available for reading after writing is complete /// /// An unsafe pointer to the node to return protected virtual void PostNode(Node* node) { // Set the write flag for this node (the node is reserved so no need for locks) node->DoneWrite = 1; // Move the write pointer as far forward as we can // always starting from WriteEnd to make all contiguous // completed nodes available for reading. for (; ; ) { int blockIndex = _nodeHeader->WriteEnd; node = this[blockIndex]; #pragma warning disable 0420 // ignore ref to volatile warning - Interlocked API if (Interlocked.CompareExchange(ref node->DoneWrite, 0, 1) != 1) { // If we get here then another thread either another thread // has already moved the write index or we have moved forward // as far as we can return; } // Move the pointer one forward Interlocked.CompareExchange(ref _nodeHeader->WriteEnd, node->Next, blockIndex); #pragma warning restore 0420 // Signal the "data exists" event if read threads are waiting if (blockIndex == _nodeHeader->ReadStart) DataExists.Set(); } } /// /// Writes the byte array buffer to the next available node for writing /// /// Reference to the buffer to write /// The index within the buffer to start writing from /// The maximum number of milliseconds to wait for a node to become available for writing (default 1000ms) /// The number of bytes written /// The maximum number of bytes that can be written is the minimum of the length of and . public virtual int Write(byte[] source, int startIndex = 0, int timeout = 1000) { // Grab a node for writing Node* node = GetNodeForWriting(timeout); if (node == null) return 0; // Copy the data int amount = Math.Min(source.Length - startIndex, NodeBufferSize); Marshal.Copy(source, startIndex, new IntPtr(BufferStartPtr + node->Offset), amount); node->AmountWritten = amount; // Writing is complete, make readable PostNode(node); return amount; } /// /// Writes the structure array buffer to the next available node for writing /// /// Reference to the buffer to write /// The index within the buffer to start writing from /// The maximum number of milliseconds to wait for a node to become available for writing (default 1000ms) /// The number of elements written /// The maximum number of elements that can be written is the minimum of the length of subtracted by and divided by FastStructure.SizeOf>T<(). public virtual int Write(T[] source, int startIndex = 0, int timeout = 1000) where T : struct { // Grab a node for writing Node* node = GetNodeForWriting(timeout); if (node == null) return 0; // Write the data using the FastStructure class (much faster than the MemoryMappedViewAccessor WriteArray method) int count = Math.Min(source.Length - startIndex, NodeBufferSize / FastStructure.SizeOf()); base.WriteArray(source, startIndex, count, node->Offset); node->AmountWritten = count * FastStructure.SizeOf(); // Writing is complete, make node readable PostNode(node); return count; } /// /// Writes the structure to the next available node for writing /// /// The structure type to be written /// The structure to be written /// The maximum number of milliseconds to wait for a node to become available for writing (default 1000ms) /// The number of bytes written - larger than 0 if successful /// If the size of the structure is larger than . public virtual int Write(ref T source, int timeout = 1000) where T : struct { int structSize = Marshal.SizeOf(typeof(T)); if (structSize > NodeBufferSize) throw new ArgumentOutOfRangeException("T", "The size of structure " + typeof(T).Name + " is larger than NodeBufferSize"); // Attempt to retrieve a node for writing Node* node = GetNodeForWriting(timeout); if (node == null) return 0; // Copy the data using the MemoryMappedViewAccessor base.Write(ref source, node->Offset); node->AmountWritten = structSize; // Return the node for further writing PostNode(node); return structSize; } /// /// Writes bytes from to the next available node for writing /// /// Pointer to the buffer to copy /// The maximum number of milliseconds to wait for a node to become available (default 1000ms) /// The number of bytes to attempt to write /// The number of bytes written /// The maximum number of bytes that can be written is the minimum of and . public virtual int Write(IntPtr source, int length, int timeout = 1000) { // Grab a node for writing Node* node = GetNodeForWriting(timeout); if (node == null) return 0; // Copy the data int amount = Math.Min(length, NodeBufferSize); base.Write(source, amount, node->Offset); node->AmountWritten = amount; // Writing is complete, make readable PostNode(node); return amount; } /// /// Reserves a node for writing and then calls the provided to perform the write operation. /// /// A function to used to write to the node's buffer. The first parameter is a pointer to the node's buffer. /// The provided function should return the number of bytes written. /// The maximum number of milliseconds to wait for a node to become available for writing (default 1000ms) /// The number of bytes written public virtual int Write(Func writeFunc, int timeout = 1000) { // Grab a node for writing Node* node = GetNodeForWriting(timeout); if (node == null) return 0; int amount = 0; try { // Pass destination IntPtr to custom write function amount = writeFunc(new IntPtr(BufferStartPtr + node->Offset)); node->AmountWritten = amount; } finally { // Writing is complete, make readable PostNode(node); } return amount; } #endregion #region Node Reading /// /// Returns a copy of the shared memory header /// public NodeHeader ReadNodeHeader() { return (NodeHeader)Marshal.PtrToStructure(new IntPtr(_nodeHeader), typeof(NodeHeader)); } /// /// Attempts to reserve a node from the linked-list for reading with the specified timeout /// /// The number of milliseconds to wait if a node is not immediately available for reading. /// An unsafe pointer to the node if successful, otherwise null protected virtual Node* GetNodeForReading(int timeout) { for (; ; ) { int blockIndex = _nodeHeader->ReadStart; Node* node = this[blockIndex]; if (blockIndex == _nodeHeader->WriteEnd) { // No data is available, wait for it if (DataExists.WaitOne(timeout)) continue; // Timeout return null; } #pragma warning disable 0420 // ignore ref to volatile warning - Interlocked API if (Interlocked.CompareExchange(ref _nodeHeader->ReadStart, node->Next, blockIndex) == blockIndex) return node; #pragma warning restore 0420 // Another thread has already acquired this node for reading, try again continue; } } /// /// Returns a node to the available list of nodes for writing. /// /// An unsafe pointer to the node to be returned protected virtual void ReturnNode(Node* node) { // Set the finished reading flag for this node (the node is reserved so no need for locks) node->DoneRead = 1; // Keep it clean and reset AmountWritten to prepare it for next Write node->AmountWritten = 0; // Move the read pointer forward as far as possible // always starting from ReadEnd to make all contiguous // read nodes available for writing. for (; ; ) { int blockIndex = _nodeHeader->ReadEnd; node = this[blockIndex]; #pragma warning disable 0420 // ignore ref to volatile warning - Interlocked API if (Interlocked.CompareExchange(ref node->DoneRead, 0, 1) != 1) { // If we get here then another read thread has already moved the pointer // or we have moved ReadEnd as far forward as we can return; } // Move the pointer forward one node Interlocked.CompareExchange(ref _nodeHeader->ReadEnd, node->Next, blockIndex); #pragma warning restore 0420 // If a writer thread is waiting on "node available" signal the event if (node->Prev == _nodeHeader->WriteStart) NodeAvailable.Set(); } } /// /// Reads the next available node for reading into the specified byte array /// /// Reference to the buffer /// The index within the buffer to start writing from /// The maximum number of milliseconds to wait for a node to become available for reading (default 1000ms) /// The number of bytes read /// The maximum number of bytes that can be read is the minimum of the length of subtracted by and . public virtual int Read(byte[] destination, int startIndex = 0, int timeout = 1000) { Node* node = GetNodeForReading(timeout); if (node == null) return 0; //int amount = Math.Min(buffer.Length, NodeBufferSize); int amount = Math.Min(destination.Length - startIndex, node->AmountWritten); // Copy the data Marshal.Copy(new IntPtr(BufferStartPtr + node->Offset), destination, startIndex, amount); // Return the node for further writing ReturnNode(node); return amount; } /// /// Reads the next available node for reading into the specified structure array /// /// The structure type to be read /// Reference to the buffer /// The index within the destination to start writing to. /// The maximum number of milliseconds to wait for a node to become available for reading (default 1000ms) /// The number of elements read into destination /// The maximum number of elements that can be read is the minimum of the length of subtracted by and divided by FastStructure.SizeOf>T<(). public virtual int Read(T[] destination, int startIndex = 0, int timeout = 1000) where T : struct { Node* node = GetNodeForReading(timeout); if (node == null) return 0; // Copy the data using the FastStructure class (much faster than the MemoryMappedViewAccessor ReadArray method) int count = Math.Min(destination.Length - startIndex, node->AmountWritten / FastStructure.SizeOf()); base.ReadArray(destination, startIndex, count, node->Offset); // Return the node for further writing ReturnNode(node); return count; } /// /// Reads the next available node for reading into the a structure /// /// The structure type to be read /// The resulting structure if successful otherwise default(T) /// The maximum number of milliseconds to wait for a node to become available for reading (default 1000ms) /// The number of bytes read /// If the size of is larger than . public virtual int Read(out T destination, int timeout = 1000) where T: struct { int structSize = Marshal.SizeOf(typeof(T)); if (structSize > NodeBufferSize) throw new ArgumentOutOfRangeException("T", "The size of structure " + typeof(T).Name + " is larger than NodeBufferSize"); // Attempt to retrieve a node Node* node = GetNodeForReading(timeout); if (node == null) { destination = default(T); return 0; } // Copy the data using the MemoryMappedViewAccessor base.Read(out destination, node->Offset); // Return the node for further writing ReturnNode(node); return structSize; } /// /// Reads the next available node for reading into the specified memory location with the specified length /// /// Pointer to the buffer /// The maximum length of /// The maximum number of milliseconds to wait for a node to become available for reading (default 1000ms) /// The number of bytes read /// The maximum number of bytes that can be read is the minimum of the and . public virtual int Read(IntPtr destination, int length, int timeout = 1000) { Node* node = GetNodeForReading(timeout); if (node == null) return 0; //int amount = Math.Min(length, NodeBufferSize); int amount = Math.Min(length, node->AmountWritten); // Copy the data base.Read(destination, amount, node->Offset); // Return node for further writing ReturnNode(node); return amount; } /// /// Reserves a node for reading and then calls the provided to perform the read operation. /// /// A function used to read from the node's buffer. The first parameter is a pointer to the node's buffer. /// The provided function should return the number of bytes read. /// The maximum number of milliseconds to wait for a node to become available for reading (default 1000ms) /// The number of bytes read public virtual int Read(Func readFunc, int timeout = 1000) { Node* node = GetNodeForReading(timeout); if (node == null) return 0; int amount = 0; try { // Pass pointer to buffer directly to custom read function amount = readFunc(new IntPtr(BufferStartPtr + node->Offset)); } finally { // Return the node for further writing ReturnNode(node); } return amount; } #endregion } } ================================================ FILE: SharedMemory/FastStructure.cs ================================================ // SharedMemory (File: SharedMemory\FastStructure.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace SharedMemory { /// /// Provides fast reading and writing of generic structures to a memory location using IL emitted functions. /// public static class FastStructure { /// /// Retrieve a pointer to the passed generic structure type. This is achieved by emitting a to retrieve a pointer to the structure. /// /// /// /// A pointer to the provided structure in memory. /// public static unsafe void* GetPtr(ref T structure) where T : struct { return FastStructure.GetPtr(ref structure); } /// /// Loads the generic value type from a pointer. This is achieved by emitting a that returns the value in the memory location as a . /// The equivalent non-generic C# code: /// /// unsafe MyStruct ReadFromPointer(byte* pointer) /// { /// return *(MyStruct*)pointer; /// } /// /// /// Any value/structure type /// Unsafe pointer to memory to load the value from /// The newly loaded value public static unsafe T PtrToStructure(IntPtr pointer) where T : struct { return FastStructure.PtrToStructure(pointer); } /// /// Writes the generic value type to the location specified by a pointer. This is achieved by emitting a that copies the value from the referenced structure into the specified memory location. /// There is no exact equivalent possible in C#, the closest possible (generates the same IL) is the following code: /// /// unsafe void WriteToPointer(ref SharedHeader dest, ref SharedHeader src) /// { /// dest = src; /// } /// /// /// /// /// public static unsafe void StructureToPtr(ref T structure, IntPtr pointer) where T : struct { FastStructure.StructureToPtr(ref structure, pointer); } /// /// Copy bytes of structure into the existing buffer at index /// /// /// /// /// /// public static unsafe void CopyTo(ref T structure, byte[] buffer, int startIndex = 0) where T : struct { if (buffer == null) throw new ArgumentNullException("buffer"); if (startIndex > buffer.Length || startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); fixed (byte* p = &buffer[startIndex]) { StructureToPtr(ref structure, new IntPtr(p)); } } /// /// Return a byte[] for the provided structure /// /// /// /// public static unsafe byte[] ToBytes(ref T structure) where T : struct { byte[] result = new byte[FastStructure.Size]; fixed (byte* p = &result[0]) { StructureToPtr(ref structure, new IntPtr(p)); return result; } } /// /// Read structure from the provided byte array /// /// /// /// /// public static unsafe T FromBytes(byte[] buffer, int startIndex = 0) where T : struct { if (buffer == null) throw new ArgumentNullException("buffer"); if (startIndex > buffer.Length || startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); fixed (byte* p = &buffer[startIndex]) { return PtrToStructure(new IntPtr(p)); } } /// /// Retrieve the cached size of a structure /// /// /// /// Caches the size by type /// public static int SizeOf() where T : struct { return FastStructure.Size; } /// /// Reads a number of elements from a memory location into the provided buffer starting at the specified index. /// /// The structure type /// The destination buffer. /// The source memory location. /// The start index within . /// The number of elements to read. public static unsafe void ReadArray(T[] buffer, IntPtr source, int index, int count) where T : struct { uint elementSize = (uint)SizeOf(); if (buffer == null) throw new ArgumentNullException("buffer"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if (index < 0) throw new ArgumentOutOfRangeException("index"); if (buffer.Length - index < count) throw new ArgumentException("Invalid offset into array specified by index and count"); void* ptr = source.ToPointer(); byte* p = (byte*)FastStructure.GetPtr(ref buffer[0]); #if NETCORE Buffer.MemoryCopy(ptr, p + (index * elementSize), elementSize * count, elementSize * count); #else UnsafeNativeMethods.CopyMemoryPtr(p + (index * elementSize), ptr, (uint)(elementSize * count)); #endif } /// /// Reads a number of elements from a memory location into the provided buffer starting at the specified index. /// /// The destination buffer. /// The source memory location. /// The start index within . /// The number of elements to read. public static unsafe void ReadBytes(byte[] buffer, IntPtr source, int index, int count) { uint elementSize = sizeof(byte); if (buffer == null) throw new ArgumentNullException("buffer"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if (index < 0) throw new ArgumentOutOfRangeException("index"); if (buffer.Length - index < count) throw new ArgumentException("Invalid offset into array specified by index and count"); void* ptr = source.ToPointer(); fixed (byte* p = &buffer[0]) { #if NETCORE Buffer.MemoryCopy(ptr, p + (index * elementSize), elementSize * count, elementSize * count); #else UnsafeNativeMethods.CopyMemoryPtr(p + (index * elementSize), ptr, (uint)(elementSize * count)); #endif } } /// /// Writes a number of elements to a memory location from the provided buffer starting at the specified index. /// /// The structure type /// The destination memory location. /// The source buffer. /// The start index within . /// The number of elements to write. public static unsafe void WriteArray(IntPtr destination, T[] buffer, int index, int count) where T : struct { uint elementSize = (uint)SizeOf(); if (buffer == null) throw new ArgumentNullException("buffer"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if (index < 0) throw new ArgumentOutOfRangeException("index"); if (buffer.Length - index < count) throw new ArgumentException("Invalid offset into array specified by index and count"); void* ptr = destination.ToPointer(); byte* p = (byte*)FastStructure.GetPtr(ref buffer[0]); #if NETCORE Buffer.MemoryCopy(p + (index * elementSize), ptr, elementSize * count, elementSize * count); #else UnsafeNativeMethods.CopyMemoryPtr(ptr, p + (index * elementSize), (uint)(elementSize * count)); #endif } /// /// Writes a number of elements to a memory location from the provided buffer starting at the specified index. /// /// The destination memory location. /// The source buffer. /// The start index within . /// The number of elements to write. public static unsafe void WriteBytes(IntPtr destination, byte[] buffer, int index, int count) { uint elementSize = sizeof(byte); if (buffer == null) throw new ArgumentNullException("buffer"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if (index < 0) throw new ArgumentOutOfRangeException("index"); if (buffer.Length - index < count) throw new ArgumentException("Invalid offset into array specified by index and count"); void* ptr = destination.ToPointer(); fixed (byte* p = &buffer[0]) { #if NETCORE Buffer.MemoryCopy(p + (index * elementSize), ptr, elementSize * count, elementSize * count); #else UnsafeNativeMethods.CopyMemoryPtr(ptr, p + (index * elementSize), (uint)(elementSize * count)); #endif } } } /// /// Emits optimized IL for the reading and writing of structures to/from memory. /// For a 32-byte structure with 1 million iterations: /// The method performs approx. 20x faster than /// (8ms vs 160ms), and about 1.6x slower than the non-generic equivalent (8ms vs 5ms) /// The method performs approx. 8x faster than /// (4ms vs 34ms). /// /// public static class FastStructure where T : struct { /// /// Delegate that returns a pointer to the provided structure. Use with extreme caution. /// /// /// public unsafe delegate void* GetPtrDelegate(ref T value); /// /// Delegate for loading a structure from the specified memory address /// /// /// public delegate T PtrToStructureDelegate(IntPtr pointer); /// /// Delegate for writing a structure to the specified memory address /// /// /// public delegate void StructureToPtrDelegate(ref T value, IntPtr pointer); /// /// The delegate for the generated IL to retrieve a pointer to the structure /// public unsafe readonly static GetPtrDelegate GetPtr = BuildFunction(); /// /// The delegate for the generated IL to retrieve a structure from a specified memory address. /// public readonly static PtrToStructureDelegate PtrToStructure = BuildLoadFromPointerFunction(); /// /// The delegate for the generated IL to store a structure at the specified memory address. /// public readonly static StructureToPtrDelegate StructureToPtr = BuildWriteToPointerFunction(); /// /// Cached size of T as determined by . /// public static readonly int Size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)); private static DynamicMethod method; private static DynamicMethod methodLoad; private static DynamicMethod methodWrite; /// /// Performs once of type compatibility check. /// /// Thrown if the type T is incompatible static FastStructure() { // Performs compatibility checks upon T CheckTypeCompatibility(typeof(T)); } private unsafe static GetPtrDelegate BuildFunction() { method = new DynamicMethod("GetStructurePtr<" + typeof(T).FullName + ">", typeof(void*), new Type[1] { typeof(T).MakeByRefType() }, typeof(FastStructure).Module); ILGenerator generator = method.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Conv_U); generator.Emit(OpCodes.Ret); return (GetPtrDelegate)method.CreateDelegate(typeof(GetPtrDelegate)); } private static unsafe PtrToStructureDelegate BuildLoadFromPointerFunction() { methodLoad = new DynamicMethod("PtrToStructure<" + typeof(T).FullName + ">", typeof(T), new Type[1] { typeof(IntPtr) }, typeof(FastStructure).Module); ILGenerator generator = methodLoad.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldobj, typeof(T)); generator.Emit(OpCodes.Ret); return (PtrToStructureDelegate)methodLoad.CreateDelegate(typeof(PtrToStructureDelegate)); } private static unsafe StructureToPtrDelegate BuildWriteToPointerFunction() { methodWrite = new DynamicMethod("StructureToPtr<" + typeof(T).FullName + ">", null, new Type[2] { typeof(T).MakeByRefType(), typeof(IntPtr) }, typeof(FastStructure).Module); ILGenerator generator = methodWrite.GetILGenerator(); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldobj, typeof(T)); generator.Emit(OpCodes.Stobj, typeof(T)); generator.Emit(OpCodes.Ret); return (StructureToPtrDelegate)methodWrite.CreateDelegate(typeof(StructureToPtrDelegate)); } private static void CheckTypeCompatibility(Type t, System.Collections.Generic.HashSet checkedItems = null) { if (checkedItems == null) { checkedItems = new System.Collections.Generic.HashSet(); checkedItems.Add(typeof(char)); checkedItems.Add(typeof(byte)); checkedItems.Add(typeof(sbyte)); checkedItems.Add(typeof(bool)); checkedItems.Add(typeof(double)); checkedItems.Add(typeof(float)); checkedItems.Add(typeof(decimal)); checkedItems.Add(typeof(int)); checkedItems.Add(typeof(short)); checkedItems.Add(typeof(long)); checkedItems.Add(typeof(uint)); checkedItems.Add(typeof(ushort)); checkedItems.Add(typeof(ulong)); checkedItems.Add(typeof(IntPtr)); checkedItems.Add(typeof(void*)); } if (checkedItems.Contains(t)) return; else checkedItems.Add(t); FieldInfo[] fi = t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); foreach (FieldInfo info in fi) { if (!info.FieldType.IsPrimitive && !info.FieldType.IsValueType && !info.FieldType.IsPointer) { throw new ArgumentException(String.Format("Non-value types are not supported: field {0} is of type {1} in structure {2}", info.Name, info.FieldType.Name, info.DeclaringType.Name)); } // Example for adding future marshal attributes as incompatible //System.Runtime.InteropServices.MarshalAsAttribute attr; //if (TryGetAttribute(info, out attr)) //{ // if (attr.Value == System.Runtime.InteropServices.UnmanagedType.ByValArray) // { // throw new ArgumentException(String.Format("UnmanagedType.ByValArray is not supported on field {0} in type [{1}].", info.Name, typeof(T).FullName)); // } //} CheckTypeCompatibility(info.FieldType, checkedItems); } } //private static bool TryGetAttribute(MemberInfo memberInfo, out T1 customAttribute) where T1 : Attribute //{ // var attributes = memberInfo.GetCustomAttributes(typeof(T1), false).FirstOrDefault(); // if (attributes == null) // { // customAttribute = null; // return false; // } // customAttribute = (T1)attributes; // return true; //} } } ================================================ FILE: SharedMemory/MemoryMappedFiles/MemoryMappedFile.cs ================================================ // SharedMemory (File: SharedMemory\MemoryMappedFile.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Linq; using System.Security; using System.Text; using Microsoft.Win32.SafeHandles; using System.Security.Permissions; using System.Runtime; using SharedMemory; using System.Runtime.InteropServices; using System.Threading; namespace System.IO.MemoryMappedFiles { #if !NET40Plus /// /// Very limited .NET 3.5 implementation of a managed wrapper around memory-mapped files to reflect the .NET 4 API. /// Only those methods and features necessary for the SharedMemory library have been implemented. /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] #endif public sealed class MemoryMappedFile: IDisposable { SafeMemoryMappedFileHandle _handle; /// /// Gets the file handle of a memory-mapped file. /// /// The handle to the memory-mapped file. public SafeMemoryMappedFileHandle SafeMemoryMappedFileHandle { [SecurityCritical] [SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] get { return this._handle; } } private MemoryMappedFile(SafeMemoryMappedFileHandle handle) { this._handle = handle; } /// /// /// ~MemoryMappedFile() { this.Dispose(false); } /// /// Creates a new memory-mapped file, throwing an IOException if it already exists /// /// /// /// public static MemoryMappedFile CreateNew(String mapName, long capacity) { if (String.IsNullOrEmpty(mapName)) throw new ArgumentException("mapName cannot be null or empty."); if (capacity <= 0) throw new ArgumentOutOfRangeException("capacity", "Value must be larger than 0."); if (IntPtr.Size == 4 && capacity > ((1024*1024*1024) * (long)4)) throw new ArgumentOutOfRangeException("capacity", "The capacity cannot be greater than the size of the system's logical address space."); return new MemoryMappedFile(DoCreate(mapName, capacity)); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke"), SecurityCritical] [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] private static SafeMemoryMappedFileHandle DoCreate(string mapName, long capacity) { SafeFileHandle fileHandle = new SafeFileHandle(new IntPtr(-1), true); SafeMemoryMappedFileHandle safeHandle = null; safeHandle = UnsafeNativeMethods.CreateFileMapping(fileHandle, (UnsafeNativeMethods.FileMapProtection)MemoryMappedFileAccess.ReadWrite, capacity, mapName); var lastWin32Error = Marshal.GetLastWin32Error(); if (!safeHandle.IsInvalid && (lastWin32Error == UnsafeNativeMethods.ERROR_ALREADY_EXISTS)) { throw new System.IO.IOException(UnsafeNativeMethods.GetMessage(lastWin32Error)); } else if (safeHandle.IsInvalid && lastWin32Error > 0) { throw new System.IO.IOException(UnsafeNativeMethods.GetMessage(lastWin32Error)); } if (safeHandle == null || safeHandle.IsInvalid) throw new InvalidOperationException("Cannot create file mapping"); return safeHandle; } /// /// Creates a new view accessor /// /// /// /// /// [SecurityCritical] [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] public MemoryMappedViewAccessor CreateViewAccessor(long offset, long size, MemoryMappedFileAccess access = MemoryMappedFileAccess.ReadWrite) { if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Value must be non-negative"); if (size < 0) throw new ArgumentOutOfRangeException("size", "Value must be positive or zero for default size"); if (IntPtr.Size == 4 && size > ((1024 * 1024 * 1024) * (long)4)) throw new ArgumentOutOfRangeException("size", "The capacity cannot be greater than the size of the system's logical address space."); MemoryMappedView memoryMappedView = MemoryMappedView.CreateView(this._handle, access, offset, size); return new MemoryMappedViewAccessor(memoryMappedView); } /// /// Dispose pattern /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } void Dispose(bool disposeManagedResources) { if (this._handle != null && !this._handle.IsClosed) { this._handle.Dispose(); this._handle = null; } } /// /// Opens an existing memory-mapped file. Throws FileNotFoundException if it doesn't exist. /// /// /// public static MemoryMappedFile OpenExisting(string mapName) { SafeMemoryMappedFileHandle safeMemoryMappedFileHandle = UnsafeNativeMethods.OpenFileMapping((uint)MemoryMappedFileRights.ReadWrite, false, mapName); int lastWin32Error = Marshal.GetLastWin32Error(); if (safeMemoryMappedFileHandle.IsInvalid) { if (lastWin32Error == UnsafeNativeMethods.ERROR_FILE_NOT_FOUND) throw new FileNotFoundException(); throw new System.IO.IOException(UnsafeNativeMethods.GetMessage(lastWin32Error)); } return new MemoryMappedFile(safeMemoryMappedFileHandle); } } #endif } ================================================ FILE: SharedMemory/MemoryMappedFiles/MemoryMappedFileAccess.cs ================================================ // SharedMemory (File: SharedMemory\MemoryMappedFileAccess.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using SharedMemory; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace System.IO.MemoryMappedFiles { #if !NET40Plus /// /// Used when creating a memory mapped file /// public enum MemoryMappedFileAccess: uint { /// /// Read /// Read = 2, /// /// Read/Write /// ReadWrite = 4, /// /// CopyOnWrite /// CopyOnWrite = 8, /// /// Read Execute /// ReadExecute = 32, /// /// Read/Write Execute /// ReadWriteExecute = 64 } internal static class MemoryMappedFileAccessExtensions { internal static UnsafeNativeMethods.FileMapAccess ToMapViewFileAccess(this MemoryMappedFileAccess access) { switch (access) { case MemoryMappedFileAccess.Read: return UnsafeNativeMethods.FileMapAccess.FileMapRead; case MemoryMappedFileAccess.ReadWrite: return UnsafeNativeMethods.FileMapAccess.FileMapRead | UnsafeNativeMethods.FileMapAccess.FileMapWrite; case MemoryMappedFileAccess.ReadExecute: return UnsafeNativeMethods.FileMapAccess.FileMapRead | UnsafeNativeMethods.FileMapAccess.FileMapExecute; case MemoryMappedFileAccess.ReadWriteExecute: return UnsafeNativeMethods.FileMapAccess.FileMapRead | UnsafeNativeMethods.FileMapAccess.FileMapWrite | UnsafeNativeMethods.FileMapAccess.FileMapExecute; default: return UnsafeNativeMethods.FileMapAccess.FileMapAllAccess; } } } #endif } ================================================ FILE: SharedMemory/MemoryMappedFiles/MemoryMappedFileRights.cs ================================================ // SharedMemory (File: SharedMemory\MemoryMappedFileRights.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace System.IO.MemoryMappedFiles { #if !NET40Plus /// /// Used for opening a memory-mapped file /// [Flags] public enum MemoryMappedFileRights: uint { /// The right to add data to a file or remove data from a file. Write = 0x02, /// The right to open and copy a file as read-only. Read = 0x04, /// The right to open and copy a file, and the right to add data to a file or remove data from a file. ReadWrite = MemoryMappedFileRights.Write | MemoryMappedFileRights.Read, } #endif } ================================================ FILE: SharedMemory/MemoryMappedFiles/MemoryMappedView.cs ================================================ // SharedMemory (File: SharedMemory\MemoryMappedView.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using Microsoft.Win32.SafeHandles; using SharedMemory; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; namespace System.IO.MemoryMappedFiles { #if !NET40Plus /// /// Very limited .NET 3.5 implementation of a managed wrapper around memory-mapped files to reflect the .NET 4 API. /// Only those methods and features necessary for the SharedMemory library have been implemented. /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] #endif public sealed class MemoryMappedView : IDisposable { SafeMemoryMappedViewHandle _handle; /// /// /// public SafeMemoryMappedViewHandle SafeMemoryMappedViewHandle { get { return this._handle; } } long _size; long _offset; /// /// The size of the view (from offset to end) /// public long Size { get { return _size; } } /// /// The start of the view (the handle itself will be aligned based on the allocation granularity) /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa366548(v=vs.85).aspx /// public long ViewStartOffset { get { return _offset; } } private MemoryMappedView(SafeMemoryMappedViewHandle handle, long offset, long size) { this._handle = handle; this._offset = offset; this._size = size; } /// /// /// ~MemoryMappedView() { Dispose(false); } /// /// Dispose pattern /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } void Dispose(bool disposeManagedResources) { if (this._handle != null && !this._handle.IsClosed) this._handle.Dispose(); this._handle = null; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke")] internal static MemoryMappedView CreateView(SafeMemoryMappedFileHandle safeMemoryMappedFileHandle, MemoryMappedFileAccess access, long offset, long size) { // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366548(v=vs.85).aspx UnsafeNativeMethods.SYSTEM_INFO info = new UnsafeNativeMethods.SYSTEM_INFO(); UnsafeNativeMethods.GetSystemInfo(ref info); // To calculate where to start the file mapping, round down the // offset of the data into the memory-mapped file to the nearest multiple of the // system allocation granularity. long fileMapStart = (offset / info.dwAllocationGranularity) * info.dwAllocationGranularity; // How large will the file mapping object be? long mapViewSize = (offset % info.dwAllocationGranularity) + size; // The data of interest is not necessarily at the beginning of the // view, so determine how far into the view to set the pointer. long viewDelta = offset - fileMapStart; SafeMemoryMappedViewHandle safeHandle = UnsafeNativeMethods.MapViewOfFile(safeMemoryMappedFileHandle, access.ToMapViewFileAccess(), (ulong)fileMapStart, new UIntPtr((ulong)mapViewSize)); var lastWin32Error = Marshal.GetLastWin32Error(); if (safeHandle.IsInvalid) { if (lastWin32Error == UnsafeNativeMethods.ERROR_FILE_NOT_FOUND) throw new FileNotFoundException(); throw new System.IO.IOException(UnsafeNativeMethods.GetMessage(lastWin32Error)); } return new MemoryMappedView(safeHandle, viewDelta, size); } } #endif } ================================================ FILE: SharedMemory/MemoryMappedFiles/MemoryMappedViewAccessor.cs ================================================ // SharedMemory (File: SharedMemory\MemoryMappedViewAccessor.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using Microsoft.Win32.SafeHandles; using SharedMemory; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Text; namespace System.IO.MemoryMappedFiles { #if !NET40Plus /// /// /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] #endif public sealed class MemoryMappedViewAccessor : IDisposable { MemoryMappedView _view; internal MemoryMappedViewAccessor(MemoryMappedView memoryMappedView) { this._view = memoryMappedView; } /// /// /// public SafeMemoryMappedViewHandle SafeMemoryMappedViewHandle { [SecurityCritical] [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] get { return this._view.SafeMemoryMappedViewHandle; } } /// /// Dispose pattern /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposeManagedResources) { if (_view != null) _view.Dispose(); _view = null; } internal static unsafe void PtrToStructure(byte* ptr, out T structure) where T : struct { structure = FastStructure.PtrToStructure((IntPtr)ptr); //var tr = __makeref(structure); //*(IntPtr*)&tr = (IntPtr)ptr; //structure = __refvalue( tr,T); } internal static unsafe void StructureToPtr(ref T structure, byte* ptr) where T : struct { FastStructure.StructureToPtr(ref structure, (IntPtr)ptr); } internal unsafe void Write(long position, ref T structure) where T: struct { uint elementSize = (uint)Marshal.SizeOf(typeof(T)); if (position > this._view.Size - elementSize) throw new ArgumentOutOfRangeException("position", ""); try { byte* ptr = null; _view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); ptr += +_view.ViewStartOffset + position; StructureToPtr(ref structure, ptr); } finally { _view.SafeMemoryMappedViewHandle.ReleasePointer(); } } internal unsafe void WriteArray(long position, T[] buffer, int index, int count) where T : struct { uint elementSize = (uint)Marshal.SizeOf(typeof(T)); if (position > this._view.Size - (elementSize * count)) throw new ArgumentOutOfRangeException("position"); try { byte* ptr = null; _view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); ptr += _view.ViewStartOffset + position; FastStructure.WriteArray((IntPtr)ptr, buffer, index, count); //for (var i = 0; i < count; i++) //{ // StructureToPtr(ref buffer[index + i], ptr + (i * elementSize)); //} } finally { _view.SafeMemoryMappedViewHandle.ReleasePointer(); } } internal unsafe void Read(long position, out T structure) where T: struct { uint size = (uint)Marshal.SizeOf(typeof(T)); if (position > this._view.Size - size) throw new ArgumentOutOfRangeException("position", ""); try { byte* ptr = null; _view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); ptr += _view.ViewStartOffset + position; PtrToStructure(ptr, out structure); } finally { _view.SafeMemoryMappedViewHandle.ReleasePointer(); } } internal unsafe void ReadArray(long position, T[] buffer, int index, int count) where T : struct { uint elementSize = (uint)FastStructure.SizeOf(); if (buffer == null) throw new ArgumentNullException("buffer"); if (position > this._view.Size - (elementSize * count)) throw new ArgumentOutOfRangeException("position"); try { byte* ptr = null; _view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); ptr += _view.ViewStartOffset + position; FastStructure.ReadArray(buffer, (IntPtr)ptr, index, count); //for (var i = 0; i < count; i++) //{ // PtrToStructure(ptr + (i * elementSize), out buffer[index + i]); //} } finally { _view.SafeMemoryMappedViewHandle.ReleasePointer(); } } } #endif } ================================================ FILE: SharedMemory/MemoryMappedFiles/SafeMemoryMappedFileHandle.cs ================================================ // SharedMemory (File: SharedMemory\safememorymappedfilehandle.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using SharedMemory; using System; using System.Collections.Generic; using System.Linq; using System.Security.Permissions; using System.Text; namespace Microsoft.Win32.SafeHandles { #if !NET40Plus /// /// Provides a safe handle that represents a memory-mapped file for sequential access. /// public sealed class SafeMemoryMappedFileHandle: SafeHandleZeroOrMinusOneIsInvalid { [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] internal SafeMemoryMappedFileHandle() : base(true) { } [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] internal SafeMemoryMappedFileHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle) { base.SetHandle(handle); } /// /// Closes the memory-mapped file handle /// /// protected override bool ReleaseHandle() { try { return UnsafeNativeMethods.CloseHandle(this.handle); } finally { this.handle = IntPtr.Zero; } } } #endif } ================================================ FILE: SharedMemory/MemoryMappedFiles/SafeMemoryMappedViewHandle.cs ================================================ // SharedMemory (File: SharedMemory\safememorymappedviewhandle.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using SharedMemory; using System; using System.Collections.Generic; using System.Linq; using System.Security.Permissions; using System.Text; namespace Microsoft.Win32.SafeHandles { #if !NET40Plus /// /// Provides a safe handle that represents a view of a block of unmanaged memory for random access. /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] #endif public sealed class SafeMemoryMappedViewHandle: SafeHandleZeroOrMinusOneIsInvalid { [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] internal SafeMemoryMappedViewHandle() : base(true) { } [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] internal SafeMemoryMappedViewHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle) { base.SetHandle(handle); } /// /// Unmap's the view of the file /// /// protected override bool ReleaseHandle() { try { return UnsafeNativeMethods.UnmapViewOfFile(this.handle); } finally { this.handle = IntPtr.Zero; } } /// /// Acquires a reference to the pointer, incrementing the internal ref count. Should be followed by corresponding call to /// /// public unsafe void AcquirePointer(ref byte* pointer) { bool flag = false; base.DangerousAddRef(ref flag); pointer = (byte*)this.handle.ToPointer(); } /// /// Release the pointer /// public void ReleasePointer() { base.DangerousRelease(); } } #endif } ================================================ FILE: SharedMemory/RpcBuffer.cs ================================================ // SharedMemory (File: SharedMemory\RpcBuffer.cs) // Copyright (c) 2020 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; namespace SharedMemory { // Only supported in .NET 4.5+ and .NET Standard 2.0 using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; internal enum InstanceType { Master, Slave } /// /// The available RPC protocols /// public enum RpcProtocol { /// /// Version 1 - messages are split into packets that fit the buffer capacity and include a protocol header in each packet /// V1 = 1 } /// /// Extension methods for adorning RPC tasks /// public static class ResponseTaskHelper { static readonly RpcResponse CancelledRpcResponse = new RpcResponse(false, null); static readonly Task CancelledRpcResponseTask = Task.FromResult(CancelledRpcResponse); /// /// Adds timeout and manual cancellation capabilities to an existing Task /// /// /// /// /// public static async Task TimeoutOrCancel(this Task task, int millisecondsTimeout, CancellationToken cancellationToken = default) { if (task.IsCompleted) { // the task has already completed // No proxy necessary. return await task.ConfigureAwait(false); } // Short-circuit #2: zero timeout if (millisecondsTimeout == 0) { // We've already timed out. return new RpcResponse(false, null); } if (millisecondsTimeout == Timeout.Infinite) { return await task.WaitAsync(cancellationToken).ConfigureAwait(false); } using (var cts = new CancellationTokenSource(millisecondsTimeout)) { var timeoutToken = cts.Token; return await task.WaitAsync(cancellationToken).WaitAsync(timeoutToken).ConfigureAwait(false); } } /// /// Asynchronously waits for the task to complete, or for the cancellation token to be canceled. /// /// The task to wait for. May not be null. /// The cancellation token that cancels the wait. private static Task WaitAsync(this Task @this, CancellationToken cancellationToken) { if (@this == null) throw new ArgumentNullException(nameof(@this)); if (!cancellationToken.CanBeCanceled) return @this; if (cancellationToken.IsCancellationRequested) return CancelledRpcResponseTask; return DoWaitAsync(@this, cancellationToken); } private static async Task DoWaitAsync(Task task, CancellationToken cancellationToken) { using (var cancelTaskSource = new RpcResponseCancellationTokenTaskSource(cancellationToken)) return await (await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false)).ConfigureAwait(false); } /// /// Holds the task for a cancellation token, as well as the token registration. The registration is disposed when this instance is disposed. /// public sealed class RpcResponseCancellationTokenTaskSource : IDisposable { /// /// The cancellation token registration, if any. This is null if the registration was not necessary. /// private readonly IDisposable _registration; /// /// Creates a task for the specified cancellation token, registering with the token if necessary. /// /// The cancellation token to observe. public RpcResponseCancellationTokenTaskSource(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { Task = CancelledRpcResponseTask; return; } var tcs = new TaskCompletionSource(); _registration = cancellationToken.Register(() => tcs.TrySetResult(CancelledRpcResponse), useSynchronizationContext: false); Task = tcs.Task; } /// /// Gets the task for the source cancellation token. /// public Task Task { get; private set; } /// /// Disposes the cancellation token registration, if any. Note that this may cause to never complete. /// public void Dispose() { _registration?.Dispose(); } } } /// /// The RPC message type /// public enum MessageType : byte { /// /// A request message /// RpcRequest = 1, /// /// A response message /// RpcResponse = 2, /// /// An error message /// ErrorInRpc = 3, } /// /// The V1 protocol header /// public struct RpcProtocolHeaderV1 { /// /// Message Type /// public MessageType MsgType; /// /// Message Id /// public ulong MsgId; /// /// Total message size /// public int PayloadSize; /// /// The current packet number /// public ushort CurrentPacket; /// /// The total number of packets in the message /// public ushort TotalPackets; /// /// If a response, the Id of the remote message this is a response to /// public ulong ResponseId; } /// /// Represents a request to be sent on the channel /// public class RpcRequest { internal RpcRequest() { } /// /// The message Id /// public ulong MsgId { get; set; } /// /// The message type /// public MessageType MsgType { get; set; } /// /// The message payload (if any) /// public byte[] Data { get; set; } /// /// A wait event that is signaled when a response is ready /// public TaskCompletionSource ResponseReady { get; } = new TaskCompletionSource(); /// /// Was the request successful /// public bool IsSuccess { get; internal set; } /// /// When the request was created /// public DateTime Created { get; } = DateTime.Now; } /// /// Represents the result of a remote request. /// public class RpcResponse { /// /// Constructs an RpcResponse /// /// was it a success /// the message data (if any) public RpcResponse(bool success, byte[] data) { this.Success = success; this.Data = data; } /// /// If the request was successful /// public bool Success { get; } /// /// The returned result (if applicable) /// public byte[] Data { get; } } /// /// Represents the channel statistics of an instance /// public class RpcStatistics { /// /// The protocol overhead per packet /// public int ProtocolOverheadPerPacket { get; internal set; } /// /// Bytes read from channel (excluding protocol overhead) /// public ulong BytesRead { get; private set; } /// /// Number of packets read from channel /// public ulong PacketsRead { get; private set; } /// /// The largest packet read from channel (excluding protocol overhead) /// public int ReadingMaxPacketSize { get; private set; } /// /// The size of last packet read from channel (excluding protocol overhead) /// public int ReadingLastPacketSize { get; private set; } = -1; /// /// The size of last message read from channel (excluding protocol overhead) /// public int ReadingLastMessageSize { get; private set; } = -1; /// /// The total number of messages received /// public ulong MessagesReceived { get { return RequestsReceived + ResponsesReceived + ErrorsReceived; } } /// /// The number of request messages received /// public ulong RequestsReceived { get; private set; } /// /// The number of response messages received /// public ulong ResponsesReceived { get; private set; } /// /// The number of error message received /// public ulong ErrorsReceived { get; private set; } /// /// The number of bytes written to channel (excluding protocol overhead) /// public ulong BytesWritten { get; private set; } /// /// The number of packets written to channel /// public ulong PacketsWritten { get; private set; } /// /// The largest packet written to channel (excluding protocol overhead) /// public int WritingMaxPacketSize { get; private set; } /// /// The size of last packet written to channel (excluding protocol overhead) /// public int WritingLastPacketSize { get; private set; } = -1; /// /// The size of last message written to channel (excluding protocol overhead) /// public int WritingLastMessageSize { get; private set; } = -1; /// /// Number of response messages received that were discarded (provided a non-existent message Id) /// public ulong DiscardedResponses { get; private set; } /// /// The response message Id that was last discarded /// public ulong LastDiscardedResponseId { get; private set; } /// /// The total number of messages sent /// public ulong MessagesSent { get { return RequestsSent + ResponsesSent + ErrorsSent; } } /// /// The number of request messages sent /// public ulong RequestsSent { get; private set; } /// /// The number of response messages sent /// public ulong ResponsesSent { get; private set; } /// /// The number of error messages sent /// public ulong ErrorsSent { get; private set; } /// /// Number of timeouts /// public ulong Timeouts { get; private set; } /// /// DateTime of last timeout /// public DateTime LastTimeout { get; private set; } DateTime StartWaitWriteTimestamp { get; set; } DateTime EndWaitWriteTimestamp { get; set; } /// /// Maximum Ticks waited for available write slot /// public long MaxWaitWriteTicks { get; private set; } = -1; DateTime StartWaitReadTimestamp { get; set; } DateTime EndWaitReadTimestamp { get; set; } /// /// Maximum Ticks waiting for read slot (cannot exceed 1sec) /// public long MaxWaitReadTicks { get; private set; } = -1; internal void StartWaitRead() { StartWaitReadTimestamp = DateTime.Now; } internal void ReadPacket(int bytes) { EndWaitReadTimestamp = DateTime.Now; var ticks = EndWaitReadTimestamp.Ticks - StartWaitReadTimestamp.Ticks; if (ticks > MaxWaitReadTicks) { MaxWaitReadTicks = ticks; } PacketsRead++; BytesRead += (ulong)bytes; ReadingLastPacketSize = bytes; if (bytes > ReadingMaxPacketSize) { ReadingMaxPacketSize = bytes; } } internal void StartWaitWrite() { StartWaitWriteTimestamp = DateTime.Now; } internal void WritePacket(int bytes) { EndWaitWriteTimestamp = DateTime.Now; var ticks = EndWaitWriteTimestamp.Ticks - StartWaitWriteTimestamp.Ticks; if (ticks > MaxWaitWriteTicks) { MaxWaitWriteTicks = ticks; } PacketsWritten++; BytesWritten += (ulong)bytes; WritingLastPacketSize = bytes; if (bytes > WritingMaxPacketSize) { WritingMaxPacketSize = bytes; } } internal void MessageReceived(MessageType msgType, int size) { ReadingLastMessageSize = size; switch (msgType) { case MessageType.RpcRequest: RequestsReceived++; break; case MessageType.RpcResponse: ResponsesReceived++; break; case MessageType.ErrorInRpc: ErrorsReceived++; break; } } internal void MessageSent(MessageType msgType, int size) { WritingLastMessageSize = size; switch (msgType) { case MessageType.RpcRequest: RequestsSent++; break; case MessageType.RpcResponse: ResponsesSent++; break; case MessageType.ErrorInRpc: ErrorsSent++; break; } } internal void Timeout() { Timeouts++; LastTimeout = DateTime.Now; } internal void DiscardResponse(ulong msgId) { DiscardedResponses++; LastDiscardedResponseId = msgId; } /// /// Reset all statistics /// public void Reset() { Timeouts = 0; LastTimeout = DateTime.MinValue; PacketsRead = 0; BytesRead = 0; MaxWaitReadTicks = -1; ReadingMaxPacketSize = 0; ReadingLastMessageSize = -1; ReadingLastPacketSize = -1; RequestsReceived = 0; ResponsesReceived = 0; ErrorsReceived = 0; PacketsWritten = 0; BytesWritten = 0; MaxWaitWriteTicks = -1; WritingMaxPacketSize = 0; WritingLastMessageSize = -1; WritingLastPacketSize = -1; RequestsSent = 0; ResponsesSent = 0; ErrorsSent = 0; DiscardedResponses = 0; LastDiscardedResponseId = 0; } } /// /// A simple RPC implementation designed for a single master/slave pair /// public class RpcBuffer : IDisposable { private Mutex masterMutex; private long _disposed = 0; /// /// Whether the RpcBuffer has been disposed /// protected bool Disposed { get { return Interlocked.Read(ref _disposed) != 0; } } /// /// Dispose has completed /// public bool DisposeFinished { get { return Interlocked.Read(ref _disposed) == 2; } } private readonly InstanceType instanceType; private readonly RpcProtocol protocolVersion; private readonly int protocolLength; private readonly int bufferCapacity; private readonly int bufferNodeCount; private readonly int msgBufferLength; // The amount of room left in the node after protocol header /// /// The buffer used to send messages to remote channel endpoint /// protected CircularBuffer WriteBuffer { get; private set; } /// /// The buffer used to receive message from the remote channel endpoint /// protected CircularBuffer ReadBuffer { get; private set; } /// /// Channel endpoint statistics /// public RpcStatistics Statistics { get; private set; } const int defaultTimeoutMs = 30000; object lock_sendQ = new object(); /// /// Outgoing requests waiting for responses /// protected ConcurrentDictionary Requests { get; } = new ConcurrentDictionary(); /// /// Incoming requests waiting for more packets /// protected ConcurrentDictionary IncomingRequests { get; } = new ConcurrentDictionary(); Action RemoteCallHandler = null; Func AsyncRemoteCallHandler = null; Func RemoteCallHandlerWithResult = null; Func> AsyncRemoteCallHandlerWithResult = null; /// /// Construct a new RpcBuffer /// /// The channel name. This is the name to be shared between the master/slave pair. Each pair must have a unique value. /// Action to handle requests with no response. /// Master only: Maximum buffer capacity. Messages will be split into packets that fit this capacity (including a packet header of 64-bytes). The slave will use the same size as defined by the master /// ProtocolVersion.V1 = 64-byte header for each packet /// Master only: The number of nodes in the underlying circular buffers, each with a size of public RpcBuffer(string name, Action remoteCallHandler, int bufferCapacity = 50000, RpcProtocol protocolVersion = RpcProtocol.V1, int bufferNodeCount = 10) : this(name, bufferCapacity, protocolVersion, bufferNodeCount) { RemoteCallHandler = remoteCallHandler; } /// /// Construct a new RpcBuffer /// /// The unique channel name. This is the name to be shared between the master/slave pair. Each pair must have a unique value. /// Asynchronous action to handle requests with no response. /// Master only: Maximum buffer capacity. Messages will be split into packets that fit this capacity (including a packet header of 64-bytes). The slave will use the same size as defined by the master /// ProtocolVersion.V1 = 64-byte header for each packet /// Master only: The number of nodes in the underlying circular buffers, each with a size of public RpcBuffer(string name, Func asyncRemoteCallHandler, int bufferCapacity = 50000, RpcProtocol protocolVersion = RpcProtocol.V1, int bufferNodeCount = 10) : this(name, bufferCapacity, protocolVersion, bufferNodeCount) { AsyncRemoteCallHandler = asyncRemoteCallHandler; } /// /// Construct a new RpcBuffer /// /// The unique channel name. This is the name to be shared between the master/slave pair. Each pair must have a unique value. /// Function to handle requests with a response. /// Master only: Maximum buffer capacity. Messages will be split into packets that fit this capacity (including a packet header of 64-bytes). The slave will use the same size as defined by the master /// ProtocolVersion.V1 = 64-byte header for each packet /// Master only: The number of nodes in the underlying circular buffers, each with a size of public RpcBuffer(string name, Func remoteCallHandlerWithResult, int bufferCapacity = 50000, RpcProtocol protocolVersion = RpcProtocol.V1, int bufferNodeCount = 10) : this(name, bufferCapacity, protocolVersion, bufferNodeCount) { RemoteCallHandlerWithResult = remoteCallHandlerWithResult; } /// /// Construct a new RpcBuffer /// /// The unique channel name. This is the name to be shared between the master/slave pair. Each pair must have a unique value. /// Function to asynchronously handle requests with a response. /// Master only: Maximum buffer capacity. Messages will be split into packets that fit this capacity (including a packet header of 64-bytes). The slave will use the same size as defined by the master /// ProtocolVersion.V1 = 64-byte header for each packet /// Master only: The number of nodes in the underlying circular buffers, each with a size of public RpcBuffer(string name, Func> asyncRemoteCallHandlerWithResult, int bufferCapacity = 50000, RpcProtocol protocolVersion = RpcProtocol.V1, int bufferNodeCount = 10) : this(name, bufferCapacity, protocolVersion, bufferNodeCount) { AsyncRemoteCallHandlerWithResult = asyncRemoteCallHandlerWithResult; } /// /// Construct a new RpcBuffer /// /// The unique channel name. This is the name to be shared between the master/slave pair. Each pair must have a unique value. /// Master only: Maximum buffer capacity. Messages will be split into packets that fit this capacity (including a packet header of 64-bytes). The slave will use the same size as defined by the master /// ProtocolVersion.V1 = 64-byte header for each packet /// Master only: The number of nodes in the underlying circular buffers, each with a size of public RpcBuffer(string name, int bufferCapacity = 50000, RpcProtocol protocolVersion = RpcProtocol.V1, int bufferNodeCount = 10) { if (bufferCapacity < 256) // min 256 bytes { throw new ArgumentOutOfRangeException(nameof(bufferCapacity), "cannot be less than 256 bytes"); } if (bufferCapacity > 1024 * 1024) // max 1MB { throw new ArgumentOutOfRangeException(nameof(bufferCapacity), "cannot be larger than 1MB"); } Statistics = new RpcStatistics(); masterMutex = new Mutex(true, name + "SharedMemory_MasterMutex", out bool createdNew); if (createdNew && masterMutex.WaitOne(500)) { instanceType = InstanceType.Master; } else { instanceType = InstanceType.Slave; if (masterMutex != null) { masterMutex.Close(); masterMutex.Dispose(); masterMutex = null; } } switch (protocolVersion) { case RpcProtocol.V1: this.protocolVersion = protocolVersion; protocolLength = FastStructure.SizeOf(); Statistics.ProtocolOverheadPerPacket = protocolLength; break; } this.bufferCapacity = bufferCapacity; this.bufferNodeCount = bufferNodeCount; if (instanceType == InstanceType.Master) { WriteBuffer = new CircularBuffer(name + "_Slave_SharedMemory_MMF", bufferNodeCount, this.bufferCapacity); ReadBuffer = new CircularBuffer(name + "_Master_SharedMemory_MMF", bufferNodeCount, this.bufferCapacity); } else { ReadBuffer = new CircularBuffer(name + "_Slave_SharedMemory_MMF"); WriteBuffer = new CircularBuffer(name + "_Master_SharedMemory_MMF"); this.bufferCapacity = ReadBuffer.NodeBufferSize; this.bufferNodeCount = ReadBuffer.NodeCount; } this.msgBufferLength = Convert.ToInt32(this.bufferCapacity) - protocolLength; Task readTask = new Task(() => { switch (protocolVersion) { case RpcProtocol.V1: ReadThreadV1(); break; } }, TaskCreationOptions.LongRunning); readTask.Start(); } object mutex = new object(); ulong messageId = 1; /// /// Constructs a new request message, giving it a new unique MsgId /// /// protected RpcRequest CreateMessageRequest() { RpcRequest request = new RpcRequest(); lock (mutex) { request.MsgId = messageId++; } return request; } /// /// Send a remote request on the channel, blocking until a result is returned /// /// Arguments (if any) as a byte array to be sent to the remote endpoint /// Timeout in milliseconds (defaults to 30sec) /// A cancellation token /// The returned response /// Thrown if this object has been disposed /// Thrown if the underlying buffers have been closed by the channel owner public RpcResponse RemoteRequest(byte[] args = null, int timeoutMs = defaultTimeoutMs, CancellationToken cancellationToken = default) { ThrowIfDisposedOrShutdown(); var request = CreateMessageRequest(); Task sendMessage = SendMessage(request, args, timeoutMs, cancellationToken); return sendMessage.Result; } /// /// Send a remote request on the channel (awaitable) /// /// Arguments (if any) as a byte array to be sent to the remote endpoint /// Timeout in milliseconds (defaults to 30sec) /// A cancellation token /// /// Thrown if this object has been disposed /// Thrown if the underlying buffers have been closed by the channel owner public Task RemoteRequestAsync(byte[] args = null, int timeoutMs = defaultTimeoutMs, CancellationToken cancellationToken = default) { ThrowIfDisposedOrShutdown(); var request = CreateMessageRequest(); return SendMessage(request, args, timeoutMs, cancellationToken); } async Task SendMessage(RpcRequest request, byte[] payload, int timeout = defaultTimeoutMs, CancellationToken cancellationToken = default) { return await SendMessage(MessageType.RpcRequest, request, payload, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); } /// /// Sends a message to the remote endpoint /// /// /// /// /// /// /// /// /// Thrown if this object has been disposed /// Thrown if the underlying buffers have been closed by the channel owner protected virtual Task SendMessage(MessageType msgType, RpcRequest request, byte[] payload, ulong responseMsgId = 0, int timeout = defaultTimeoutMs, CancellationToken cancellationToken = default) { ThrowIfDisposedOrShutdown(); var msgId = request.MsgId; if (msgType == MessageType.RpcRequest) { Requests[request.MsgId] = request; } var success = false; switch (this.protocolVersion) { case RpcProtocol.V1: success = WriteProtocolV1(msgType, msgId, payload, responseMsgId, timeout); break; default: // Invalid protocol return Task.FromResult(new RpcResponse(false, null)); } if (success) { Statistics.MessageSent(msgType, payload?.Length ?? 0); } if (success && msgType == MessageType.RpcRequest) { if (request != null) { return request.ResponseReady.Task.TimeoutOrCancel(timeout, cancellationToken); } else { return Task.FromResult(new RpcResponse(true, null)); } } else { RpcResponse rpcResponse = new RpcResponse(success, null); if (request != null) { request.IsSuccess = success; request.ResponseReady.SetResult(rpcResponse); } return Task.FromResult(rpcResponse); } } bool WriteProtocolV1(MessageType msgType, ulong msgId, byte[] msg, ulong responseMsgId, int timeout) { if (Disposed) { return false; } if (WriteBuffer.ShuttingDown) { return false; } // Send the request packets lock (lock_sendQ) { // Split message into correct packet size int i = 0; int left = msg?.Length ?? 0; byte[] pMsg = null; ushort totalPackets = ((msg?.Length ?? 0) == 0) ? (ushort)1 : Convert.ToUInt16(Math.Ceiling((double)msg.Length / (double)msgBufferLength)); ushort currentPacket = 1; while (true) { if (WriteBuffer.ShuttingDown) { return false; } pMsg = new byte[left > msgBufferLength ? msgBufferLength + protocolLength : left + protocolLength]; // Writing protocol header var header = new RpcProtocolHeaderV1 { MsgType = msgType, MsgId = msgId, CurrentPacket = currentPacket, TotalPackets = totalPackets, PayloadSize = msg?.Length ?? 0, ResponseId = responseMsgId }; FastStructure.CopyTo(ref header, pMsg, 0); if (left > msgBufferLength) { // Writing payload if (msg != null && msg.Length > 0) Buffer.BlockCopy(msg, i, pMsg, protocolLength, msgBufferLength); left -= msgBufferLength; i += msgBufferLength; } else { // Writing last packet of payload if (msg != null && msg.Length > 0) { Buffer.BlockCopy(msg, i, pMsg, protocolLength, left); } left = 0; } Statistics.StartWaitWrite(); var bytes = WriteBuffer.Write((ptr) => { FastStructure.WriteBytes(ptr, pMsg, 0, pMsg.Length); return pMsg.Length; }, 1000); Statistics.WritePacket(bytes - protocolLength); if (left <= 0) { break; } currentPacket++; } } return true; } private bool m_ReadThreadIsReading = false; private object m_ReadThreadIsReadingLock = new object(); void ReadThreadV1() { try { // Work with Local Variable to prevent NPE after dispose CircularBuffer l_TempReadBuffer = ReadBuffer; while (true && !l_TempReadBuffer.ShuttingDown) { if (Interlocked.Read(ref _disposed) == 1) return; // Check If Reading must be stopped if (_needDisposeManagedResources) { DisposeManagedResources(); return; } // Set Marker for Reading in Progress lock (m_ReadThreadIsReadingLock) { m_ReadThreadIsReading = true; } try { Statistics.StartWaitRead(); l_TempReadBuffer.Read((ptr) => { int readLength = 0; var header = FastStructure.PtrToStructure(ptr); ptr = ptr + protocolLength; readLength += protocolLength; RpcRequest request = null; if (header.MsgType == MessageType.RpcResponse || header.MsgType == MessageType.ErrorInRpc) { if (!Requests.TryGetValue(header.ResponseId, out request)) { // The response received does not have a matching message that was sent Statistics.DiscardResponse(header.ResponseId); return protocolLength; } } else { request = IncomingRequests.GetOrAdd(header.MsgId, new RpcRequest { MsgId = header.MsgId }); } int packetSize = header.PayloadSize < msgBufferLength ? header.PayloadSize : (header.CurrentPacket < header.TotalPackets ? msgBufferLength : header.PayloadSize % msgBufferLength); if (header.PayloadSize > 0) { if (request.Data == null) { request.Data = new byte[header.PayloadSize]; } int index = msgBufferLength * (header.CurrentPacket - 1); FastStructure.ReadBytes(request.Data, ptr, index, packetSize); readLength += packetSize; } if (header.CurrentPacket == header.TotalPackets) { if (header.MsgType == MessageType.RpcResponse || header.MsgType == MessageType.ErrorInRpc) { Requests.TryRemove(request.MsgId, out RpcRequest removed); } else { IncomingRequests.TryRemove(request.MsgId, out RpcRequest removed); } // Full message is ready Statistics.MessageReceived(header.MsgType, request.Data?.Length ?? 0); if (header.MsgType == MessageType.RpcResponse) { request.IsSuccess = true; request.ResponseReady.SetResult(new RpcResponse(request.IsSuccess, request.Data)); } else if (header.MsgType == MessageType.ErrorInRpc) { request.IsSuccess = false; request.ResponseReady.SetResult(new RpcResponse(request.IsSuccess, request.Data)); } else if (header.MsgType == MessageType.RpcRequest) { // For Handling Request we create an new Task because this can take sometime Task.Run(async () => { try { await ProcessCallHandler(request).ConfigureAwait(false); } catch(Exception ex) { // Ignore Object Disposed and Invalid Operation Exceptions // because the other side of the rpc buffers may // not know if this Buffer is shutting down or disposed if (!(ex is ObjectDisposedException || ex is InvalidOperationException)) { throw; } } }); } } Statistics.ReadPacket(packetSize); return protocolLength + packetSize; }, 500); } finally { lock (m_ReadThreadIsReadingLock) { m_ReadThreadIsReading = false; } } } } finally { // Make sure that Dispose ManageResource has been progressed if (_needDisposeManagedResources) { DisposeManagedResources(); } } } private int _processCount = 0; private object _processLock = new object(); async Task ProcessCallHandler(RpcRequest request, CancellationToken cancellationToken = default) { // Mark as processing lock (_processLock) { _processCount++; } try { if (RemoteCallHandler != null) { RemoteCallHandler(request.MsgId, request.Data); await SendMessage(MessageType.RpcResponse, CreateMessageRequest(), null, request.MsgId, cancellationToken: cancellationToken) .ConfigureAwait(false); } else if (AsyncRemoteCallHandler != null) { await AsyncRemoteCallHandler(request.MsgId, request.Data).ConfigureAwait(false); await SendMessage(MessageType.RpcResponse, CreateMessageRequest(), null, request.MsgId, cancellationToken: cancellationToken) .ConfigureAwait(false); } else if (RemoteCallHandlerWithResult != null) { var result = RemoteCallHandlerWithResult(request.MsgId, request.Data); await SendMessage(MessageType.RpcResponse, CreateMessageRequest(), result, request.MsgId, cancellationToken: cancellationToken) .ConfigureAwait(false); } else if (AsyncRemoteCallHandlerWithResult != null) { var result = await AsyncRemoteCallHandlerWithResult(request.MsgId, request.Data) .ConfigureAwait(false); await SendMessage(MessageType.RpcResponse, CreateMessageRequest(), result, request.MsgId, cancellationToken: cancellationToken) .ConfigureAwait(false); } } catch { await SendMessage(MessageType.ErrorInRpc, CreateMessageRequest(), null, request.MsgId) .ConfigureAwait(false); } finally { lock (_processLock) { _processCount--; } // Make sure that ManagedResources are disposed if needed if (_needDisposeManagedResources) { DisposeManagedResources(); } } } #region IDisposable /// /// Checks that the object has not been disposed, and that the underlying buffers are not shutting down. /// /// Thrown if this object has been disposed /// Thrown if the underlying buffers have been closed by the channel owner protected void ThrowIfDisposedOrShutdown() { if (Disposed) { throw new ObjectDisposedException("RpcBuffer"); } if (ReadBuffer.ShuttingDown || WriteBuffer.ShuttingDown) { throw new InvalidOperationException("Channel owner has closed buffers"); } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// IDisposable pattern - dispose of managed/unmanaged resources /// /// true to dispose of managed resources as well as unmanaged. protected virtual void Dispose(bool disposeManagedResources) { if (Disposed) { return; } if (disposeManagedResources) { DisposeManagedResources(); } } private bool _needDisposeManagedResources = false; private void DisposeManagedResources() { lock (_processLock) { lock (m_ReadThreadIsReadingLock) { // Check if dispose is possible otherwise // mark for dispose later if (_processCount > 0) { _needDisposeManagedResources = true; return; } if (m_ReadThreadIsReading) { _needDisposeManagedResources = true; return; } // Disconnect handle to prevent processing RemoteCallHandler = null; AsyncRemoteCallHandler = null; RemoteCallHandlerWithResult = null; AsyncRemoteCallHandlerWithResult = null; _needDisposeManagedResources = false; } } // Mark as Disposed first otherwise ReadThread has NullPointerException because // ReadBuffer is already null but Disposed is false long l_OldValue = Interlocked.CompareExchange(ref _disposed, 1, 0); // Make sure that only one Thread is processing the dispose if (l_OldValue != 0) return; if (WriteBuffer != null) { WriteBuffer.Dispose(); WriteBuffer = null; } if (ReadBuffer != null) { ReadBuffer.Dispose(); ReadBuffer = null; } if (masterMutex != null) { masterMutex.Close(); masterMutex.Dispose(); masterMutex = null; } // Mark as DisposeFinished Interlocked.Exchange(ref _disposed, 2); } #endregion } } ================================================ FILE: SharedMemory/SharedArray.cs ================================================ // SharedMemory (File: SharedMemory\SharedArray.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Security.Permissions; namespace SharedMemory { /// /// A generic fixed-length shared memory array of structures with support for simple inter-process read/write synchronisation. /// /// The structure type that will be stored in the elements of this fixed array buffer. #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] [PermissionSet(SecurityAction.InheritanceDemand)] #endif public class SharedArray : BufferWithLocks, IList where T : struct { /// /// Gets a 32-bit integer that represents the total number of elements in the /// public int Length { get; private set; } /// /// Gets or sets the element at the specified index /// /// The zero-based index of the element to get or set. /// The element at the specified index. /// is less than 0 -or- index is equal to or greater than . public T this[int index] { get { T item; Read(out item, index); return item; } set { Write(ref value, index); } } private int _elementSize; #region Constructors /// /// Creates the shared memory array with the name specified by . /// /// The name of the shared memory array to be created. /// The number of elements to make room for within the shared memory array. public SharedArray(string name, int length) : base(name, Marshal.SizeOf(typeof(T)) * length, true) { Length = length; _elementSize = Marshal.SizeOf(typeof(T)); Open(); } /// /// Opens an existing shared memory array with the name as specified by . /// /// The name of the shared memory array to open. /// If the shared memory location specified by does not have a that is evenly divisible by the size of . public SharedArray(string name) : base(name, 0, false) { _elementSize = Marshal.SizeOf(typeof(T)); Open(); } #endregion /// /// Perform any initialisation required when opening the shared memory array /// /// true if successful protected override bool DoOpen() { if (!IsOwnerOfSharedMemory) { if (BufferSize % _elementSize != 0) throw new ArgumentOutOfRangeException("name", "BufferSize is not evenly divisible by the size of " + typeof(T).Name); Length = (int)(BufferSize / _elementSize); } return true; } #region Writing /// /// Copy to the shared memory array element at index . /// /// The data to be written. /// The zero-based index of the element to set. public void Write(ref T data, int index) { if (index > Length - 1 || index < 0) throw new ArgumentOutOfRangeException("index"); base.Write(ref data, index * _elementSize); } /// /// Copy the elements of the array into the shared memory array starting at index . /// /// The source array to copy elements from. /// The zero-based index of the shared memory array element to begin writing to. /// is less than 0 -or- length of + is greater than . /// must not be null public void Write(T[] buffer, int startIndex = 0) { if (buffer == null) throw new ArgumentNullException("buffer"); if (buffer.Length + startIndex > Length || startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); base.Write(buffer, startIndex * _elementSize); } #endregion #region Reading /// /// Reads a single element from the shared memory array into located at . /// /// The element at the specified index. /// The zero-based index of the element to get. /// The element at the specified index. /// is less than 0 -or- index is equal to or greater than . public void Read(out T data, int index) { if (index > Length - 1 || index < 0) throw new ArgumentOutOfRangeException("index"); base.Read(out data, index * _elementSize); } /// /// Reads buffer.Length elements from the shared memory array into starting at the shared memory array element located at . /// /// The destination array to copy the elements into. /// The zero-based index of the shared memory array element to begin reading from. /// is less than 0 -or- length of + is greater than . /// must not be null public void CopyTo(T[] buffer, int startIndex = 0) { if (buffer == null) throw new ArgumentNullException("buffer"); if (buffer.Length + startIndex > Length || startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); base.Read(buffer, startIndex * _elementSize); } #endregion #region IEnumerable /// /// Returns an enumerator that iterates through the collection. /// /// An instance that can be used to iterate through the collection public IEnumerator GetEnumerator() { for (int i = 0; i < Length; i++) { yield return this[i]; } } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion #region IList /// /// Operation not supported. Throws /// /// public void Add(T item) { throw new NotImplementedException(); } /// /// Operation not supported. Throws /// public void Clear() { throw new NotImplementedException(); } /// /// Checks if the list contains the specified item. /// /// /// True if found public bool Contains(T item) { return IndexOf(item) >= 0; } /// /// Operation not supported. Throws /// /// /// public bool Remove(T item) { throw new NotImplementedException(); } /// /// The number of elements in the array /// public int Count { get { return Length; } } /// /// The elements are not read-only /// public bool IsReadOnly { get { return true; } } /// /// Return the index of the specified item. /// /// /// The index of the item if found, otherwise -1. public int IndexOf(T item) { for (var i = 0; i < Count; i++) { if (this[i].Equals(item)) return i; } return -1; } /// /// Operation not supported. Throws /// /// /// public void Insert(int index, T item) { throw new NotImplementedException(); } /// /// Operation not supported. Throws /// /// public void RemoveAt(int index) { throw new NotImplementedException(); } #endregion } } ================================================ FILE: SharedMemory/SharedBuffer.cs ================================================ // SharedMemory (File: SharedMemory\SharedBuffer.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.IO.MemoryMappedFiles; using System.Linq; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Threading; namespace SharedMemory { /// /// Abstract base class that provides client/server support for reading/writing structures to a buffer within a . /// A header structure allows clients to open the buffer without knowing the size. /// #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] [PermissionSet(SecurityAction.InheritanceDemand)] #endif public abstract unsafe class SharedBuffer : IDisposable { #region Public/Protected properties /// /// The name of the Shared Memory instance /// public string Name { get; private set; } /// /// The buffer size /// public long BufferSize { get; private set; } /// /// The total shared memory size, including header and buffer. /// public virtual long SharedMemorySize { get { return HeaderOffset + Marshal.SizeOf(typeof(SharedHeader)) + BufferSize; } } /// /// Indicates whether this instance owns the shared memory (i.e. creator of the shared memory) /// public bool IsOwnerOfSharedMemory { get; private set; } /// /// Returns true if the SharedMemory owner has/is shutting down /// public bool ShuttingDown { get { if (Header == null || Header->Shutdown == 1) { return true; } else { return false; } } } /// /// Where the header starts within the shared memory /// protected virtual long HeaderOffset { get { return 0; } } /// /// Where the buffer is located within the shared memory /// protected virtual long BufferOffset { get { return HeaderOffset + Marshal.SizeOf(typeof(SharedHeader)); } } #endregion #region Protected field members /// /// Memory mapped file /// protected MemoryMappedFile Mmf; /// /// Memory mapped view /// protected MemoryMappedViewAccessor View; /// /// Pointer to the memory mapped view /// protected byte* ViewPtr = null; /// /// Pointer to the start of the buffer region of the memory mapped view /// protected byte* BufferStartPtr = null; /// /// Pointer to the header within shared memory /// protected SharedHeader* Header = null; #endregion #region Constructor / destructor /// /// Create a new instance with the specified name and buffer size /// /// The name of the shared memory /// The buffer size in bytes. The total shared memory size will be Marshal.SizeOf(SharedMemory.SharedHeader) + bufferSize /// Whether or not the current instance owns the shared memory. If true a new shared memory will be created and initialised otherwise an existing one is opened. /// /// The maximum total shared memory size is dependent upon the system and current memory fragmentation. /// The shared memory layout on 32-bit and 64-bit is:
/// /// | Header | Buffer |
/// | 16-bytes | bufferSize | ///
///
///
protected SharedBuffer(string name, long bufferSize, bool ownsSharedMemory) { #region Argument validation if (name == String.Empty || name == null) throw new ArgumentException("Cannot be String.Empty or null", "name"); if (ownsSharedMemory && bufferSize <= 0) throw new ArgumentOutOfRangeException("bufferSize", bufferSize, "Buffer size must be larger than zero when creating a new shared memory buffer."); #if DEBUG else if (!ownsSharedMemory && bufferSize > 0) System.Diagnostics.Debug.Write("Buffer size is ignored when opening an existing shared memory buffer.", "Warning"); #endif #endregion IsOwnerOfSharedMemory = ownsSharedMemory; Name = name; if (IsOwnerOfSharedMemory) { BufferSize = bufferSize; } } /// /// Destructor - for Dispose(false) /// ~SharedBuffer() { Dispose(false); } #endregion #region Open / Close /// /// Creates a new or opens an existing shared memory buffer with the name of depending on the value of . /// /// True if the memory was successfully mapped /// If is true then the shared memory buffer will be created, opening will fail in this case if the shared memory already exists. Otherwise if IsOwnerOfSharedMemory is false then the shared memory buffer will be opened, which will fail if it doesn't already exist. /// If trying to create a new shared memory buffer with a duplicate name as buffer owner. /// If trying to open a new shared memory buffer that does not exist as a consumer of existing buffer. /// If trying to create a new shared memory buffer with a size larger than the logical addressable space. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] protected bool Open() { Close(); try { // Attempts to create or open the shared memory with a name of this.Name if (IsOwnerOfSharedMemory) { // Create a new shared memory mapping Mmf = MemoryMappedFile.CreateNew(Name, SharedMemorySize); // Create a view to the entire region of the shared memory View = Mmf.CreateViewAccessor(0, SharedMemorySize, MemoryMappedFileAccess.ReadWrite); View.SafeMemoryMappedViewHandle.AcquirePointer(ref ViewPtr); Header = (SharedHeader*)(ViewPtr + HeaderOffset); BufferStartPtr = ViewPtr + BufferOffset; // Initialise the header InitialiseHeader(); } else { // Open an existing shared memory mapping Mmf = MemoryMappedFile.OpenExisting(Name); // Retrieve the header from the shared memory in order to initialise the correct size using (var headerView = Mmf.CreateViewAccessor(0, HeaderOffset + Marshal.SizeOf(typeof(SharedHeader)), MemoryMappedFileAccess.Read)) { byte* headerPtr = null; headerView.SafeMemoryMappedViewHandle.AcquirePointer(ref headerPtr); var header = (SharedHeader*)(headerPtr + HeaderOffset); BufferSize = header->SharedMemorySize - Marshal.SizeOf(typeof(SharedHeader)); headerView.SafeMemoryMappedViewHandle.ReleasePointer(); } // Create a view to the entire region of the shared memory View = Mmf.CreateViewAccessor(0, SharedMemorySize, MemoryMappedFileAccess.ReadWrite); View.SafeMemoryMappedViewHandle.AcquirePointer(ref ViewPtr); Header = (SharedHeader*)(ViewPtr + HeaderOffset); BufferStartPtr = ViewPtr + HeaderOffset + Marshal.SizeOf(typeof(SharedHeader)); } } catch { Close(); throw; } // Complete any additional open logic try { if (!DoOpen()) { Close(); return false; } else { return true; } } catch { Close(); throw; } } /// /// Allows any classes that inherit from to perform additional open logic. There is no need to call base.DoOpen() from these implementations. /// /// True if successful, otherwise false. /// By throwing an exception or returning false, the call to will fail and will be called. protected virtual bool DoOpen() { return true; } /// /// Initialises the header within the shared memory. Only applicable if is true. /// protected void InitialiseHeader() { if (!IsOwnerOfSharedMemory) return; SharedHeader header = new SharedHeader(); header.SharedMemorySize = SharedMemorySize; header.Shutdown = 0; View.Write(HeaderOffset, ref header); } /// /// Sets the flag, and disposes of the MemoryMappedFile and MemoryMappedViewAccessor.
/// Attempting to read/write to the buffer after closing will result in a . ///
public virtual void Close() { if (IsOwnerOfSharedMemory && View != null) { // Indicates to any open instances that the owner is no longer open #pragma warning disable 0420 // ignore ref to volatile warning - Interlocked API Interlocked.Exchange(ref Header->Shutdown, 1); #pragma warning restore 0420 } // Allow additional close logic DoClose(); // Close the MemoryMappedFile and MemoryMappedViewAccessor if (View != null) { View.SafeMemoryMappedViewHandle.ReleasePointer(); View.Dispose(); } if (Mmf != null) { Mmf.Dispose(); } Header = null; ViewPtr = null; BufferStartPtr = null; View = null; Mmf = null; } /// /// Any classes that inherit from should implement any logic here, and are still active at this point. There is no need to call base.DoClose() from these classes. /// /// It is possible for to be called before has completed successfully, in this situation should fail gracefully. protected virtual void DoClose() { } #endregion #region Writing /// /// Writes an instance of into the buffer /// /// A structure type /// A reference to an instance of to be written into the buffer /// The offset within the buffer region of the shared memory to write to. protected virtual void Write(ref T source, long bufferPosition = 0) where T : struct { View.Write(BufferOffset + bufferPosition, ref source); } /// /// Writes an array of into the buffer /// /// A structure type /// An array of to be written. The length of this array controls the number of elements to be written. /// The offset within the buffer region of the shared memory to write to. protected virtual void Write(T[] source, long bufferPosition = 0) where T : struct { Write(source, 0, bufferPosition); } /// /// Writes an array of into the buffer /// /// A structure type /// An array of to be written. The length of this array controls the number of elements to be written. /// The index within the array to start writing from. /// The offset within the buffer region of the shared memory to write to. protected virtual void Write(T[] source, int index, long bufferPosition = 0) where T : struct { FastStructure.WriteArray((IntPtr)(BufferStartPtr + bufferPosition), source, index, source.Length - index); } /// /// Writes an array of into the buffer /// /// A structure type /// The source data to be written to the buffer /// The start index within . /// The number of elements to write. /// The offset within the buffer region of the shared memory to write to. protected virtual void WriteArray(T[] source, int index, int count, long bufferPosition = 0) where T : struct { FastStructure.WriteArray((IntPtr)(BufferStartPtr + bufferPosition), source, index, count); } /// /// Writes bytes from the into the shared memory buffer. /// /// A managed pointer to the memory location to be copied into the buffer /// The number of bytes to be copied /// The offset within the buffer region of the shared memory to write to. protected virtual void Write(IntPtr source, int length, long bufferPosition = 0) { #if NETCORE Buffer.MemoryCopy((void*)source, BufferStartPtr + bufferPosition, BufferSize - bufferPosition, length); #else UnsafeNativeMethods.CopyMemory(new IntPtr(BufferStartPtr + bufferPosition), source, (uint)length); #endif } /// /// Prepares an IntPtr to the buffer position and calls to perform the writing. /// /// A function used to write to the buffer. The IntPtr parameter is a pointer to the buffer location offset by . /// The offset within the buffer region to start writing to. protected virtual void Write(Action writeFunc, long bufferPosition = 0) { writeFunc(new IntPtr(BufferStartPtr + bufferPosition)); } #endregion #region Reading /// /// Reads an instance of from the buffer /// /// A structure type /// Output parameter that will contain the value read from the buffer /// The offset within the buffer region of the shared memory to read from. protected virtual void Read(out T data, long bufferPosition = 0) where T : struct { View.Read(BufferOffset + bufferPosition, out data); } /// /// Reads an array of from the buffer. /// /// A structure type /// Array that will contain the values read from the buffer. The length of this array controls the number of elements to read. /// The offset within the buffer region of the shared memory to read from. protected virtual void Read(T[] destination, long bufferPosition = 0) where T : struct { FastStructure.ReadArray(destination, (IntPtr)(BufferStartPtr + bufferPosition), 0, destination.Length); } /// /// Reads a number of elements from a memory location into the provided buffer starting at the specified index. /// /// The structure type /// The destination buffer. /// The start index within . /// The number of elements to read. /// The source offset within the buffer region of the shared memory. protected virtual void ReadArray(T[] destination, int index, int count, long bufferPosition) where T : struct { FastStructure.ReadArray(destination, (IntPtr)(BufferStartPtr + bufferPosition), index, count); } /// /// Reads bytes into the memory location from the buffer region of the shared memory. /// /// A managed pointer to the memory location to copy data into from the buffer /// The number of bytes to be copied /// The offset within the buffer region of the shared memory to read from. protected virtual void Read(IntPtr destination, int length, long bufferPosition = 0) { #if NETCORE Buffer.MemoryCopy(BufferStartPtr + bufferPosition, (void*)destination, length, length); #else UnsafeNativeMethods.CopyMemory(destination, new IntPtr(BufferStartPtr + bufferPosition), (uint)length); #endif } /// /// Prepares an IntPtr to the buffer position and calls to perform the reading. /// /// A function used to read from the buffer. The IntPtr parameter is a pointer to the buffer offset by . /// The offset within the buffer region of the shared memory to read from. protected virtual void Read(Action readFunc, long bufferPosition = 0) { readFunc(new IntPtr(BufferStartPtr + bufferPosition)); } #endregion #region IDisposable /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// IDisposable pattern - dispose of managed/unmanaged resources /// /// true to dispose of managed resources as well as unmanaged. protected virtual void Dispose(bool disposeManagedResources) { if (disposeManagedResources) { this.Close(); } } #endregion } } ================================================ FILE: SharedMemory/SharedHeader.cs ================================================ // SharedMemory (File: SharedMemory\SharedHeader.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace SharedMemory { /// /// A structure that is always located at the start of the shared memory in a instance. /// This allows the shared memory to be opened by other instances without knowing its size before hand. /// /// This structure is the same size on 32-bit and 64-bit architectures. [StructLayout(LayoutKind.Sequential)] public struct SharedHeader { /// /// The total size of the buffer including , i.e. BufferSize + Marshal.SizeOf(typeof(SharedMemory.SharedHeader)). /// public long SharedMemorySize; /// /// Flag indicating whether the owner of the buffer has closed its and . /// public volatile int Shutdown; /// /// Pad to 16-bytes. /// int _padding0; } } ================================================ FILE: SharedMemory/SharedMemory.csproj ================================================  netstandard2.1;netstandard2.0;net47;net46;net45;net4;net35 ..\bin\$(Configuration)\ ..\bin\$(Configuration)\$(TargetFramework)\SharedMemory.xml true false Justin Stenning Copyright (c) 2020 Justin Stenning NETCORE;NETCORE3_0;NET40Plus NETCORE;NETCORE2_0;NET40Plus NET45;NETFULL;NET40Plus NET45;NETFULL;NET40Plus NET45;NETFULL;NET40Plus NET40;NETFULL;NET40Plus NET35;NETFULL 4.7.0 4.7.0 ================================================ FILE: SharedMemory/SharedMemory.licenseheader ================================================ extensions: designer.cs generated.cs extensions: .cs .cpp .h // SharedMemory (File: %Project%\%FileName%) // Copyright (c) %CurrentYear% Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int extensions: .aspx .ascx <%-- Sample license text. --%> extensions: .vb 'Sample license text. extensions: .xml .config .xsd ================================================ FILE: SharedMemory/UnsafeNativeMethods.cs ================================================ // SharedMemory (File: SharedMemory\UnsafeNativeMethods.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; using Microsoft.Win32.SafeHandles; namespace SharedMemory { [System.Security.SuppressUnmanagedCodeSecurity] internal class UnsafeNativeMethods { private UnsafeNativeMethods() { } #if !NETCORE /// /// Allow copying memory from one IntPtr to another. Required as the implementation does not provide an appropriate override. /// /// /// /// [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] [SecurityCritical] internal static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] [SecurityCritical] internal static extern unsafe void CopyMemoryPtr(void* dest, void* src, uint count); #endif #if !NET40Plus [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Auto, ExactSpelling = false)] [SecurityCritical] internal static extern int FormatMessage(int dwFlags, IntPtr lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr va_list_arguments); [SecurityCritical] internal static string GetMessage(int errorCode) { StringBuilder stringBuilder = new StringBuilder(512); if (UnsafeNativeMethods.FormatMessage(12800, IntPtr.Zero, errorCode, 0, stringBuilder, stringBuilder.Capacity, IntPtr.Zero) != 0) { return stringBuilder.ToString(); } return string.Concat("UnknownError_Num ", errorCode); } [StructLayout(LayoutKind.Sequential)] internal struct SYSTEM_INFO { internal _PROCESSOR_INFO_UNION uProcessorInfo; public uint dwPageSize; public IntPtr lpMinimumApplicationAddress; public IntPtr lpMaximumApplicationAddress; public IntPtr dwActiveProcessorMask; public uint dwNumberOfProcessors; public uint dwProcessorType; public uint dwAllocationGranularity; public ushort dwProcessorLevel; public ushort dwProcessorRevision; } [StructLayout(LayoutKind.Explicit)] internal struct _PROCESSOR_INFO_UNION { [FieldOffset(0)] internal uint dwOemId; [FieldOffset(0)] internal ushort wProcessorArchitecture; [FieldOffset(2)] internal ushort wReserved; } [Flags] public enum FileMapAccess : uint { FileMapCopy = 0x0001, FileMapWrite = 0x0002, FileMapRead = 0x0004, FileMapAllAccess = 0x001f, FileMapExecute = 0x0020, } [Flags] internal enum FileMapProtection : uint { PageReadonly = 0x02, PageReadWrite = 0x04, PageWriteCopy = 0x08, PageExecuteRead = 0x20, PageExecuteReadWrite = 0x40, SectionCommit = 0x8000000, SectionImage = 0x1000000, SectionNoCache = 0x10000000, SectionReserve = 0x4000000, } /// /// Cannot create a file when that file already exists. /// internal const int ERROR_ALREADY_EXISTS = 0xB7; // 183 /// /// The system cannot open the file. /// internal const int ERROR_TOO_MANY_OPEN_FILES = 0x4; // 4 /// /// Access is denied. /// internal const int ERROR_ACCESS_DENIED = 0x5; // 5 /// /// The system cannot find the file specified. /// internal const int ERROR_FILE_NOT_FOUND = 0x2; // 2 [DllImport("kernel32.dll", CharSet = CharSet.None, SetLastError = true)] [SecurityCritical] internal static extern bool CloseHandle(IntPtr handle); [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true, ThrowOnUnmappableChar = true)] [SecurityCritical] internal static extern SafeMemoryMappedFileHandle CreateFileMapping(SafeFileHandle hFile, IntPtr lpAttributes, FileMapProtection fProtect, int dwMaxSizeHi, int dwMaxSizeLo, string lpName); internal static SafeMemoryMappedFileHandle CreateFileMapping(SafeFileHandle hFile, FileMapProtection flProtect, Int64 ddMaxSize, string lpName) { int hi = (Int32)(ddMaxSize / Int32.MaxValue); int lo = (Int32)(ddMaxSize % Int32.MaxValue); return CreateFileMapping(hFile, IntPtr.Zero, flProtect, hi, lo, lpName); } [DllImport("kernel32.dll")] internal static extern void GetSystemInfo([MarshalAs(UnmanagedType.Struct)] ref SYSTEM_INFO lpSystemInfo); [DllImport("kernel32.dll", SetLastError = true)] internal static extern SafeMemoryMappedViewHandle MapViewOfFile( SafeMemoryMappedFileHandle hFileMappingObject, FileMapAccess dwDesiredAccess, UInt32 dwFileOffsetHigh, UInt32 dwFileOffsetLow, UIntPtr dwNumberOfBytesToMap); internal static SafeMemoryMappedViewHandle MapViewOfFile(SafeMemoryMappedFileHandle hFileMappingObject, FileMapAccess dwDesiredAccess, ulong ddFileOffset, UIntPtr dwNumberofBytesToMap) { uint hi = (UInt32)(ddFileOffset / UInt32.MaxValue); uint lo = (UInt32)(ddFileOffset % UInt32.MaxValue); return MapViewOfFile(hFileMappingObject, dwDesiredAccess, hi, lo, dwNumberofBytesToMap); } [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true, ThrowOnUnmappableChar = true)] internal static extern SafeMemoryMappedFileHandle OpenFileMapping( uint dwDesiredAccess, bool bInheritHandle, string lpName); [DllImport("kernel32.dll", SetLastError = true)] internal static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); #endif } } ================================================ FILE: SharedMemory/Utilities/ArraySlice.cs ================================================ // SharedMemory (File: SharedMemory\Utilities\ArraySlice.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections; using System.Collections.Generic; using System.Security.Permissions; namespace SharedMemory.Utilities { /// /// Like , but works with not just an array. /// /// The type that is stored in the elements of the . #if NETFULL [PermissionSet(SecurityAction.LinkDemand)] [PermissionSet(SecurityAction.InheritanceDemand)] #endif public struct ArraySlice : IList { private readonly IList _list; private readonly int _offset; private readonly int _count; /// /// No slicing. Just mirror the list /// /// The list to be sliced. /// Thrown if is null. public ArraySlice(IList list) { if (list == null) throw new ArgumentNullException("list"); _list = list; _offset = 0; _count = list.Count; } /// /// Create a slice of a list (virtually). /// /// The list to be sliced. /// The offset into to start the slice. /// The number of elements to be included in this slice. /// Thrown if is null. /// Thrown if or are less than zero. /// Thrown if the number of elements in - are not less than . public ArraySlice(IList list, int offset, int count) { if (list == null) throw new ArgumentNullException("list"); if (offset < 0) throw new ArgumentOutOfRangeException("offset", "ArgumentOutOfRange_NeedNonNegNum"); if (count < 0) throw new ArgumentOutOfRangeException("count", "ArgumentOutOfRange_NeedNonNegNum"); if (list.Count - offset < count) throw new ArgumentException("Argument_InvalidOffLen"); _list = list; _offset = offset; _count = count; } /// /// The list that is being sliced. /// public IList List { get { return _list; } } /// /// The offset into the . /// public int Offset { get { return _offset; } } /// /// The number of elements to be included in this slice. /// public int Count { get { return _count; } } /// /// Used to determine uniqueness. /// /// public override int GetHashCode() { return null == _list ? 0 : _list.GetHashCode() ^ _offset ^ _count; } /// Indicates whether this instance and a specified object are equal. /// true if and this instance are the same type and represent the same value; otherwise, false. /// Another object to compare to. /// 2 public override bool Equals(Object obj) { if (obj is ArraySlice) return Equals((ArraySlice)obj); else return false; } /// Indicates whether this instance and a specified object are equal. /// /// public bool Equals(ArraySlice obj) { return obj._list == _list && obj._offset == _offset && obj._count == _count; } /// Indicates whether this instance and a specified object are equal. /// /// /// public static bool operator ==(ArraySlice a, ArraySlice b) { return a.Equals(b); } /// Indicates whether this instance and a specified object are not equal. /// /// /// public static bool operator !=(ArraySlice a, ArraySlice b) { return !(a == b); } #region IList /// Gets or sets the element at the specified index. /// The element at the specified index. /// The zero-based index of the element to get or set. /// /// is not a valid index in the . /// The property is set and the is read-only. public T this[int index] { get { if (_list == null) throw new InvalidOperationException("InvalidOperation_NullArray"); if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException("index"); return _list[_offset + index]; } set { if (_list == null) throw new InvalidOperationException("InvalidOperation_NullArray"); if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException("index"); _list[_offset + index] = value; } } /// Determines the index of a specific item in the . /// The index of if found in the list; otherwise, -1. /// The object to locate in the . public int IndexOf(T item) { if (_list == null) throw new InvalidOperationException("InvalidOperation_NullArray"); for (var i = 0; i < Count; i++) { if (this[i].Equals(item)) return i; } return -1; } void IList.Insert(int index, T item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } #endregion #region ICollection bool ICollection.IsReadOnly { get { // the indexer setter does not throw an exception although IsReadOnly is true. // This is to match the behavior of arrays. return true; } } void ICollection.Add(T item) { throw new NotSupportedException(); } void ICollection.Clear() { throw new NotSupportedException(); } bool ICollection.Contains(T item) { if (_list == null) throw new InvalidOperationException("InvalidOperation_NullArray"); return IndexOf(item) >= 0; } void ICollection.CopyTo(T[] array, int arrayIndex) { throw new NotSupportedException(); } bool ICollection.Remove(T item) { throw new NotSupportedException(); } #endregion #region IEnumerable IEnumerator IEnumerable.GetEnumerator() { if (_list == null) throw new InvalidOperationException("InvalidOperation_NullArray"); return new ArraySliceEnumerator(this); } #endregion #region IEnumerable IEnumerator IEnumerable.GetEnumerator() { if (_list == null) throw new InvalidOperationException("InvalidOperation_NullArray"); return new ArraySliceEnumerator(this); } #endregion [Serializable] private sealed class ArraySliceEnumerator : IEnumerator { private IList _array; private int _start; private int _end; private int _current; internal ArraySliceEnumerator(ArraySlice arraySlice) { _array = arraySlice._list; _start = arraySlice._offset; _end = _start + arraySlice._count; _current = _start - 1; } public bool MoveNext() { if (_current < _end) { _current++; return _current < _end; } return false; } public T Current { get { if (_current < _start) throw new InvalidOperationException("InvalidOperation_EnumNotStarted"); if (_current >= _end) throw new InvalidOperationException("InvalidOperation_EnumEnded"); return _array[_current]; } } object IEnumerator.Current { get { return Current; } } void IEnumerator.Reset() { _current = _start - 1; } public void Dispose() { } } } } ================================================ FILE: SharedMemory/Utilities/ExpandingArray.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace SharedMemory.Utilities { /// /// This is a dynamic array like .NET's List'T, except for the following: /// - Should be used only when growing the list incrementally. No inserts or deletes in the middle of the list. /// - You can specify a custom allocator, so the data can be stored in any IList'T, including a memory-mapped file. /// for struct types. /// - Unlike a typical dynamic array, such as List'T, this class does not copy data and fragment the memory by /// leaving holes that have to be freed/reallocated. /// /// public class ExpandingArray : IList { /// /// Passed in allocator. We use to this get "memory" where the actual data is stored. Can be any IList'T /// private readonly Func> _allocator; /// /// Backing field for Count. Contains the number of elements in the list from the user's perspective. /// private int _count; /// /// This is where all the allocations are stored. The 1st bucket is a special case /// and represents the first 3 elements of the list. /// The 2nd bucket contains the next 4 element of the list. /// The 3rd bucket contains the next 8 element of the list. /// The 4th bucket contains the next 16 element of the list. /// The 5th bucket contains the next 32 element of the list. /// etc. /// This way, given the index into the list, we can calculate which bucket it belongs to using log(i)/log(2) /// This array is initially allocated to contain only one bucket. But a hint can be passed in to the constructor /// to pre-allocate more pubckets. This will reduce reallocating the array as it grows. /// private IList[] _buckets; /// /// The allocation is used to specify a customer allocator. /// It's a function that returns an IList of the indicated size. /// It can be as simple as something like: size => new int[size] /// The finalCapacityHint allows us to preallocate the buckets based on what the /// the final capacity might be. This does not allocate the entire list. /// For instance, if finalCapacityHint one-million, an array of 18 objects /// are allocated. /// Guessing a smaller number simply means that the buckets are reallocated /// as the array grows, causing GC churn which could otherwise be avoided. /// /// /// public ExpandingArray(Func> allocator = null, int finalCapacityHint = 1) { _allocator = allocator ?? (size => new T[size]); _buckets = new IList[Math.Max(GetBucketIndex(finalCapacityHint), 1)]; } /// /// Given the bucket number, returns the bucket entry, which may be null if it hasn't /// been allocated yet. /// /// /// private IList GetBucket(int bucketIndex) { if (bucketIndex >= _buckets.Length) { var newBuckets = new IList[_buckets.Length + 1]; _buckets.CopyTo(newBuckets, 0); _buckets = newBuckets; } return _buckets[bucketIndex]; } /// /// Given the index into the list, determines which bucket the element resides in. /// Made pubic for testing. /// /// /// public static int GetBucketIndex(int index) { return Math.Max((int) (Math.Log(index + 1)/Math.Log(2)) - 1, 0); } /// /// Given the index into the list, returns the index into the bucket where the actual element resides. /// along with the bucket itself. /// /// /// /// private int GetLocalIndex(int globalIndex, out IList bucket) { var bucketIndex = GetBucketIndex(globalIndex); bucket = GetBucket(bucketIndex) ?? (_buckets[bucketIndex] = _allocator(Math.Max(3, globalIndex + 1))); return globalIndex - (bucketIndex > 0 ? (int)Math.Pow(2, bucketIndex + 1) - 1 : 0); } /// /// IList.Add(). Add an item to the list. /// /// public void Add(T item) { var indexNewEntry = _count; IList bucket; var localIndex = GetLocalIndex(indexNewEntry, out bucket); bucket[localIndex] = item; _count = indexNewEntry + 1; } /// Gets or sets the element at the specified index. /// The element at the specified index. /// The zero-based index of the element to get or set. /// /// is not a valid index in the . /// The property is set and the is read-only. public T this[int index] { get { IList bucket; var localIndex = GetLocalIndex(index, out bucket); return bucket[localIndex]; } set { IList bucket; var localIndex = GetLocalIndex(index, out bucket); bucket[localIndex] = value; } } /// Removes all items from the . /// The is read-only. public void Clear() { _count = 0; _buckets = new IList[1]; } /// Determines whether the contains a specific value. /// true if is found in the ; otherwise, false. /// The object to locate in the . public bool Contains(T item) { return IndexOf(item) >= 0; } /// Copies the elements of the to an , starting at a particular index. /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in at which copying begins. /// /// is null. /// /// is less than 0. /// The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(T[] array, int arrayIndex) { for (var i = 0; i < _count; i++) array[arrayIndex + i] = this[i]; } /// not implemented Removes the first occurrence of a specific object from the . /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . /// The object to remove from the . /// The is read-only. public bool Remove(T item) { throw new NotImplementedException(); } /// Gets the number of elements contained in the . /// The number of elements contained in the . public int Count { get { return _count; } } /// Gets a value indicating whether the is read-only. /// true if the is read-only; otherwise, false. public bool IsReadOnly { get { return false; } } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() { for (var i = 0; i < _count; i++) yield return this[i]; } /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// Determines the index of a specific item in the . /// The index of if found in the list; otherwise, -1. /// The object to locate in the . public int IndexOf(T item) { for (var i = 0; i < Count; i++) { if (this[i].Equals(item)) return i; } return -1; } /// not implemented. Inserts an item to the at the specified index. /// The zero-based index at which should be inserted. /// The object to insert into the . /// /// is not a valid index in the . /// The is read-only. public void Insert(int index, T item) { throw new NotImplementedException(); } /// not implemented. Removes the item at the specified index. /// The zero-based index of the item to remove. /// /// is not a valid index in the . /// The is read-only. public void RemoveAt(int index) { throw new NotImplementedException(); } } } ================================================ FILE: SharedMemory.Tests/ArraySliceTests.cs ================================================ // SharedMemory (File: SharedMemoryTests\ArraySliceTests.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using SharedMemory.Utilities; namespace SharedMemoryTests { [TestClass] public class ArraySliceTests { [TestMethod] public void ArraySlice_WorksLikeArray() { var a = new[] {1.0, 2.71828, 3.14, 4, 4.99999, 42, 1024}; var slicea = new ArraySlice(a); var sliceaSame = new ArraySlice(a); var b = new[] { 1.0, 2, 3, 4, 5, 99, 1024 }; var sliceb = new ArraySlice(b); Assert.AreEqual(a, slicea.List); Assert.AreEqual(0, slicea.Offset); Assert.AreEqual(7, slicea.Count); Assert.IsTrue(slicea.Equals(sliceaSame)); Assert.IsTrue(slicea.Equals((object)sliceaSame)); Assert.AreEqual(sliceaSame.GetHashCode(), sliceaSame.GetHashCode()); Assert.IsTrue(slicea == sliceaSame); Assert.IsTrue(slicea != sliceb); Assert.IsTrue(ApproximatelyEqual(4, slicea[3])); Assert.AreEqual(6, slicea.IndexOf(1024)); Assert.AreEqual(-1, slicea.IndexOf(1025)); Assert.IsTrue(slicea.Contains(1024)); Assert.IsFalse(slicea.Contains(1025)); Assert.IsTrue(ApproximatelyEqual(1081.85827, slicea.Sum())); IList asList = slicea; Assert.IsTrue(ApproximatelyEqual(4, asList[3])); Assert.AreEqual(6, asList.IndexOf(1024)); Assert.AreEqual(-1, asList.IndexOf(1025)); Assert.IsTrue(asList.Contains(1024)); Assert.IsFalse(asList.Contains(1025)); Assert.IsTrue(ApproximatelyEqual(1081.85827, asList.Sum())); } [TestMethod] public void ArraySlice_TestSlice() { var a = new[] { 1.0, 2.71828, 3.14, 4, 4.99999, 42, 1024 }; var slicea = new ArraySlice(a, 2, 3); var sliceaSame = new ArraySlice(a, 2, 3); var b = new[] { 1.0, 2, 3, 4, 5, 99, 1024 }; var sliceb = new ArraySlice(b, 2, 3); Assert.AreEqual(a, slicea.List); Assert.AreEqual(2, slicea.Offset); Assert.AreEqual(3, slicea.Count); Assert.IsTrue(slicea.Equals(sliceaSame)); Assert.IsTrue(slicea.Equals((object)sliceaSame)); Assert.AreEqual(sliceaSame.GetHashCode(), sliceaSame.GetHashCode()); Assert.IsTrue(slicea == sliceaSame); Assert.IsTrue(slicea != sliceb); Assert.IsTrue(ApproximatelyEqual(4.99999, slicea[2])); Assert.AreEqual(1, slicea.IndexOf(4)); Assert.AreEqual(-1, slicea.IndexOf(1025)); Assert.IsTrue(slicea.Contains(4)); Assert.IsFalse(slicea.Contains(1025)); Assert.IsTrue(ApproximatelyEqual(12.13999, slicea.Sum())); IList asList = slicea; Assert.IsTrue(ApproximatelyEqual(4.99999, asList[2])); Assert.AreEqual(1, asList.IndexOf(4)); Assert.AreEqual(-1, asList.IndexOf(1025)); Assert.IsTrue(asList.Contains(4)); Assert.IsFalse(asList.Contains(1025)); Assert.IsTrue(ApproximatelyEqual(12.13999, asList.Sum())); } // http://stackoverflow.com/a/2411661/75129 public static bool ApproximatelyEqual(double x, double y) { var epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15; return Math.Abs(x - y) <= epsilon; } } } ================================================ FILE: SharedMemory.Tests/ArrayTests.cs ================================================ // SharedMemory (File: SharedMemoryTests\ArrayTests.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using SharedMemory; using System.Runtime.InteropServices; using System.Threading.Tasks; using SharedMemory.Utilities; namespace SharedMemoryTests { [TestClass] public class ArrayTests { [TestMethod] public void Indexer_ReadWriteInteger_DataMatches() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { sma[0] = 3; sma[4] = 10; using (var smr = new SharedArray(name)) { Assert.AreEqual(0, smr[1], ""); Assert.AreEqual(3, smr[0], ""); Assert.AreEqual(10, smr[4], ""); } IList list = sma; list[0] = 5; list[4] = 55; using (var smr = new SharedArray(name)) { IList r = smr; Assert.AreEqual(0, r[1], ""); Assert.AreEqual(5, r[0], ""); Assert.AreEqual(55, r[4], ""); } list[3] = 68; IList arraySlice = new ArraySlice(list, 1, 8); arraySlice[0] = 67; using (var smr = new SharedArray(name)) { IList r = smr; IList rarraySlice = new ArraySlice(r, 1, 8); Assert.AreEqual(67, rarraySlice[0], ""); Assert.AreEqual(68, rarraySlice[2], ""); Assert.AreEqual(55, rarraySlice[3], ""); } } } [TestMethod] public void Indexer_OutOfRange_ThrowsException() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { bool exceptionThrown = false; try { sma[-1] = 0; } catch (ArgumentOutOfRangeException) { exceptionThrown = true; } Assert.IsTrue(exceptionThrown, "Index of -1 should result in ArgumentOutOfRangeException"); exceptionThrown = false; IList a = sma; try { a[-1] = 0; } catch (ArgumentOutOfRangeException) { exceptionThrown = true; } Assert.IsTrue(exceptionThrown, "Index of -1 should result in ArgumentOutOfRangeException"); try { exceptionThrown = false; sma[sma.Length] = 0; } catch (ArgumentOutOfRangeException) { exceptionThrown = true; } Assert.IsTrue(exceptionThrown, "Index of Length should result in ArgumentOutOfRangeException"); try { exceptionThrown = false; a[a.Count] = 0; } catch (ArgumentOutOfRangeException) { exceptionThrown = true; } Assert.IsTrue(exceptionThrown, "Index of Length should result in ArgumentOutOfRangeException"); } } [StructLayout(LayoutKind.Sequential)] unsafe struct MyTestStruct { const int MAXLENGTH = 100; fixed char name[MAXLENGTH]; public int ValueA; public string Name { get { fixed (char* n = name) { return new String(n); } } set { fixed (char* n = name) { int indx = 0; foreach (char c in value) { *(n + indx) = c; indx++; if (indx >= MAXLENGTH - 1) break; } *(n + indx) = '\0'; } } } } [TestMethod] public void Test_MyTestStruct() { var my = new MyTestStruct(); my.Name = "long string long string"; my.Name = "short string"; Assert.AreEqual("short string", my.Name); } [TestMethod] public void Indexer_ReadWriteComplexStruct_DataMatches() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { sma[0] = new MyTestStruct { ValueA = 3, Name = "My Test Name" }; sma[4] = new MyTestStruct { ValueA = 10, Name = "My Test Name2" }; using (var smr = new SharedArray(name)) { Assert.AreEqual(0, smr[1].ValueA, ""); Assert.AreEqual(3, smr[0].ValueA, ""); Assert.AreEqual("My Test Name", smr[0].Name, ""); Assert.AreEqual(10, smr[4].ValueA, ""); Assert.AreEqual("My Test Name2", smr[4].Name, ""); } } } [TestMethod] public void CopyTo_NullArray_ThrowsException() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { bool exceptionThrown = false; try { sma.CopyTo(null); } catch (ArgumentNullException) { exceptionThrown = true; } Assert.IsTrue(exceptionThrown, "null buffer should result in ArgumentNullException"); } } [TestMethod] public void Write_NullArray_ThrowsException() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { bool exceptionThrown = false; try { sma.Write(null); } catch (ArgumentNullException) { exceptionThrown = true; } Assert.IsTrue(exceptionThrown, "null buffer should result in ArgumentNullException"); } } [TestMethod] public void GetEnumerator_IterateItems_DataMatches() { var name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 1024; byte[] data = new byte[bufSize]; byte[] readBuf = new byte[bufSize]; using (var sma = new SharedArray(name, bufSize)) { sma.Write(data); int value = 0; foreach (var item in sma) { Assert.AreEqual(data[value], item); value++; } } } [TestMethod] public void AcquireWriteLock_ReadWrite_LocksCorrectly() { var name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 1024; byte[] data = new byte[bufSize]; byte[] readBuf = new byte[bufSize]; bool readIsFirst = false; bool readBlocked = false; int syncValue = 0; // Fill with random data r.NextBytes(data); using (var sma = new SharedArray(name, bufSize)) { // Acquire write lock early sma.AcquireWriteLock(); using (var smr = new SharedArray(name)) { var t1 = Task.Factory.StartNew(() => { if (System.Threading.Interlocked.Exchange(ref syncValue, 1) == 0) readIsFirst = true; // Should block until write lock is released smr.AcquireReadLock(); if (System.Threading.Interlocked.Exchange(ref syncValue, 3) == 4) readBlocked = true; smr.CopyTo(readBuf); smr.ReleaseReadLock(); }); System.Threading.Thread.Sleep(10); var t2 = Task.Factory.StartNew(() => { var val = System.Threading.Interlocked.Exchange(ref syncValue, 2); if (val == 0) readIsFirst = false; else if (val == 3) readBlocked = false; System.Threading.Thread.Sleep(10); sma.Write(data); System.Threading.Interlocked.Exchange(ref syncValue, 4); sma.ReleaseWriteLock(); }); Task.WaitAll(t1, t2); Assert.IsTrue(readIsFirst, "The read thread did not enter first."); Assert.IsTrue(readBlocked, "The read thread did not block."); // Check data was written before read for (var i = 0; i < readBuf.Length; i++) { Assert.AreEqual(data[i], readBuf[i]); } } } } [TestMethod] public void AcquireReadWriteLocks_ReadWrite_Blocks() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { using (var smr = new SharedArray(name)) { // Acquire write lock sma.AcquireWriteLock(); // Should block (and fail to reset write signal) Assert.IsFalse(smr.AcquireReadLock(10)); sma.ReleaseWriteLock(); smr.AcquireReadLock(); // Should block (and fail to reset read signal) Assert.IsFalse(sma.AcquireWriteLock(10)); smr.ReleaseReadLock(); } } } [TestMethod] public void IList_Contains() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { sma[0] = 3; sma[4] = 10; IList a = sma; Assert.IsTrue(a.Contains(10)); Assert.IsFalse(a.Contains(11)); } } [TestMethod] public void IList_IndexOf() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { sma[0] = 3; sma[4] = 10; IList a = sma; Assert.AreEqual(4, a.IndexOf(10)); Assert.AreEqual(-1, a.IndexOf(11)); } } [TestMethod] public void IList_IsReadOnly() { var name = Guid.NewGuid().ToString(); using (var sma = new SharedArray(name, 10)) { sma[0] = 3; sma[4] = 10; IList a = sma; Assert.IsTrue(a.IsReadOnly); } } } } ================================================ FILE: SharedMemory.Tests/BufferReadWriteTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace SharedMemoryTests { [TestClass] public class BufferReadWriteTests { [TestMethod] public void Constructor_ProducerConsumer_Created() { var name = Guid.NewGuid().ToString(); using (var buf = new SharedMemory.BufferReadWrite(name, 1024)) using (var buf2 = new SharedMemory.BufferReadWrite(name)) { } } [TestMethod] public void ReadWrite_Bytes_DataMatches() { var name = Guid.NewGuid().ToString(); Random r = new Random(); byte[] data = new byte[1024]; byte[] readData = new byte[1024]; r.NextBytes(data); using (var buf = new SharedMemory.BufferReadWrite(name, 1024)) using (var buf2 = new SharedMemory.BufferReadWrite(name)) { buf.Write(data); buf2.Read(readData); for (var i = 0; i < data.Length; i++) { Assert.AreEqual(data[i], readData[i]); } } } [TestMethod] public void ReadWrite_TimeoutException() { bool timedout = false; var name = Guid.NewGuid().ToString(); byte[] data = new byte[1024]; byte[] readData = new byte[1024]; using (var buf = new SharedMemory.BufferReadWrite(name, 1024)) using (var buf2 = new SharedMemory.BufferReadWrite(name)) { // Set a small timeout to speed up the test buf2.ReadWriteTimeout = 0; // Introduce possible deadlock by acquiring without releasing the write lock. buf.AcquireWriteLock(); // We want the AcquireReadLock to fail if (!buf2.AcquireReadLock(1)) { try { // Read should timeout with TimeoutException because buf.ReleaseWriteLock has not been called buf2.Read(readData); } catch (TimeoutException e) { timedout = true; } } Assert.AreEqual(true, timedout, "The TimeoutException was not thrown."); // Remove the deadlock situation, by releasing the write lock... buf.ReleaseWriteLock(); // ...and ensure that we can now read the data if (buf.AcquireReadLock(1)) buf2.Read(readData); else Assert.Fail("Failed to acquire read lock after releasing write lock."); } } } } ================================================ FILE: SharedMemory.Tests/CircularBufferTests.cs ================================================ // SharedMemory (File: SharedMemoryTests\CircularBufferTests.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Threading; using SharedMemory; namespace SharedMemoryTests { [TestClass] public class CircularBufferTests { #region Constructor tests [TestMethod] public void Constructor_ProducerEmptyName_ExceptionThrown() { string name = String.Empty; try { using (var smr = new CircularBuffer(name, 2, 1)) { // Allowed String.Empty name Assert.Fail(); } } catch (ArgumentException ae) { Assert.AreEqual(ae.ParamName, "name"); return; } } //[TestMethod] //public void Constructor_ConsumerNodeCount1_ValueIgnored() //{ // string name = Guid.NewGuid().ToString(); // using (var smr = new CircularBuffer(name, 1, 0, false)) // { // Assert.AreEqual(0, smr.NodeCount); // Assert.AreEqual(0, smr.BufferSize); // } //} //[TestMethod] //public void Constructor_ConsumerBufferSize1_ValueIgnored() //{ // string name = Guid.NewGuid().ToString(); // using (var smr = new CircularBuffer(name, 0, 1, false)) // { // Assert.AreEqual(0, smr.BufferSize); // } //} [TestMethod] public void Constructor_ProducerNodeCount1_ExceptionThrown() { string name = Guid.NewGuid().ToString(); try { using (var smr = new CircularBuffer(name, 1, 1)) { // Allowed single element circular buffer Assert.Fail(); } } catch (ArgumentOutOfRangeException aor) { Assert.AreEqual(aor.ParamName, "nodeCount"); return; } } [TestMethod] public void Constructor_ProducerNodeCount0_ExceptionThrown() { string name = Guid.NewGuid().ToString(); try { using (var smr = new CircularBuffer(name, 0, 1)) { // Allowed zero element circular buffer Assert.Fail(); } } catch (ArgumentOutOfRangeException aor) { Assert.AreEqual(aor.ParamName, "nodeCount"); return; } } #endregion #region Open/Close tests [TestMethod] public void Constructor_Producer_True() { string name = Guid.NewGuid().ToString(); using (var smr = new CircularBuffer(name, 2, 1)) { } } [TestMethod] public void Constructor_ConsumerWithoutProducer_FileNotFoundException() { string name = Guid.NewGuid().ToString(); try { using (var smr = new CircularBuffer(name)) { } } catch (System.IO.FileNotFoundException) { return; } Assert.Fail("Trying to open non-existant MMF did not throw FileNotFoundException"); } [TestMethod] public void Constructor_DuplicateProducer_IOException() { string name = Guid.NewGuid().ToString(); try { using (var smr = new CircularBuffer(name, 2, 1)) using (var smr2 = new CircularBuffer(name, 2, 1)) { } } catch (System.IO.IOException) { return; } Assert.Fail("Trying to create duplicate MMF did not throw IOException"); } [TestMethod] public void Constructor_ProducerAndConsumer_True() { string name = Guid.NewGuid().ToString(); using (var producer = new CircularBuffer(name, 2, 1)) using (var consumer = new CircularBuffer(name)) { } } [TestMethod] public void Close_CheckShuttingDown_True() { string name = Guid.NewGuid().ToString(); using (var producer = new CircularBuffer(name, 2, 1)) using (var consumer = new CircularBuffer(name)) { producer.Close(); Assert.IsTrue(consumer.ShuttingDown); } } [TestMethod] public void Constructor_BufferTooLarge_ArgumentOutOfRangeException() { // If it's not 32-bit build, then we can't test for out-of-range. // Test below always succeeds on 64-bit systems. if (IntPtr.Size != sizeof(int)) return; string name = Guid.NewGuid().ToString(); try { using (var smr = new CircularBuffer(name, 6, int.MaxValue)) { } } catch (System.IO.IOException) { // Success (insufficient resources) return; } catch (ArgumentOutOfRangeException) { // Success return; } Assert.Fail("Opening memory mapped file did not throw ArgumentOutOfRangeException for large memory buffer."); } #endregion #region Size assumption tests [TestMethod] public void StructSize_SharedMemoryHeader_Is16bytes() { Assert.AreEqual(16, Marshal.SizeOf(typeof(SharedHeader))); } [TestMethod] public void StructSize_Node_Is32bytes() { Assert.AreEqual(32, Marshal.SizeOf(typeof(CircularBuffer.Node))); } [TestMethod] public void StructSize_SharedMemoryNodeHeader_Is24bytes() { Assert.AreEqual(24, Marshal.SizeOf(typeof(CircularBuffer.NodeHeader))); } #endregion #region Read/Write tests [TestMethod] public void ReadWrite_SingleNode_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 1024; byte[] data = new byte[bufSize]; byte[] readBuf = new byte[bufSize]; // Fill with random data r.NextBytes(data); using (var smr = new CircularBuffer(name, 2, bufSize)) { Assert.AreEqual(bufSize, smr.Write(data), String.Format("Failed to write {0} bytes", bufSize)); Assert.AreEqual(bufSize, smr.Read(readBuf), String.Format("Failed to read {0} bytes", bufSize)); for (var i = 0; i < data.Length; i++) Assert.AreEqual(data[i], readBuf[i], String.Format("Data written does not match data read at index {0}", i)); CircularBuffer.NodeHeader header = smr.ReadNodeHeader(); Assert.AreEqual(1, header.WriteStart); Assert.AreEqual(1, header.WriteEnd); Assert.AreEqual(1, header.ReadStart); Assert.AreEqual(1, header.ReadEnd); } } /// /// Test that the SharedHeader is correct before, during and after a single read/write /// [TestMethod] public void ReadWrite_SingleNode_HeaderIndexesCorrect() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 1024; byte[] data = new byte[bufSize]; byte[] readBuf = new byte[bufSize]; CircularBuffer.NodeHeader header; // Fill with random data r.NextBytes(data); using (var smr = new CircularBuffer(name, 2, bufSize)) { header = smr.ReadNodeHeader(); Assert.AreEqual(0, header.WriteStart, "Initial WriteStart"); Assert.AreEqual(0, header.WriteEnd, "Intial WriteEnd"); Assert.AreEqual(0, header.ReadStart, "Initial ReadStart"); Assert.AreEqual(0, header.ReadEnd, "Initial ReadEnd"); Assert.AreEqual(bufSize, smr.Write((ptr) => { header = smr.ReadNodeHeader(); Assert.AreEqual(1, header.WriteStart, "During single write WriteStart"); Assert.AreEqual(0, header.WriteEnd, "During single write WriteEnd"); Marshal.Copy(data, 0, ptr, bufSize); return data.Length; }), String.Format("Failed to write {0} bytes", bufSize)); header = smr.ReadNodeHeader(); Assert.AreEqual(1, header.WriteStart, "After single write WriteStart"); Assert.AreEqual(1, header.WriteEnd, "After single write WriteEnd"); Assert.AreEqual(bufSize, smr.Read((ptr) => { header = smr.ReadNodeHeader(); Assert.AreEqual(1, header.ReadStart, "During single read ReadStart"); Assert.AreEqual(0, header.ReadEnd, "During single read ReadEnd"); Marshal.Copy(ptr, readBuf, 0, smr.NodeBufferSize); return smr.NodeBufferSize; }), String.Format("Failed to read {0} bytes", bufSize)); header = smr.ReadNodeHeader(); Assert.AreEqual(1, header.ReadStart, "After single read ReadStart"); Assert.AreEqual(1, header.ReadEnd, "After single read ReadEnd"); } } [StructLayout(LayoutKind.Sequential)] struct MyTestStruct { public int Prop1; public int Prop2; public int Prop3; public int Prop4; } [TestMethod] public void ReadWrite_MyTestStruct_DataMatches() { string name = Guid.NewGuid().ToString(); int nodeSize = Marshal.SizeOf(typeof(MyTestStruct)); using (var smr = new CircularBuffer(name, 2, nodeSize)) using (var sm2 = new CircularBuffer(name)) { MyTestStruct obj = new MyTestStruct { Prop1 = 1, Prop2 = 2, Prop3 = 3, Prop4 = 4 }; smr.Write(ref obj); MyTestStruct read; int bytesRead = sm2.Read(out read); if (bytesRead > 0) { Assert.AreEqual(FastStructure.SizeOf(), bytesRead); Assert.AreEqual(obj, read); } else { Assert.Fail(); } } } [TestMethod] public void ReadWrite_1000NodesIn2NodeRing_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 32; int iterations = 1000; byte[][] data = new byte[iterations][]; byte[] readBuf = new byte[bufSize]; byte[] writeBuf = null; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i] = new byte[bufSize]; r.NextBytes(data[i]); } using (var smr = new CircularBuffer(name, 2, bufSize)) { for (var iteration = 0; iteration < iterations; iteration++) { writeBuf = data[iteration]; Assert.AreEqual(bufSize, smr.Write(writeBuf), String.Format("Failed to write {0} bytes", bufSize)); Assert.AreEqual(bufSize, smr.Read(readBuf), String.Format("Failed to read {0} bytes", bufSize)); for (var i = 0; i < writeBuf.Length; i++) Assert.AreEqual(writeBuf[i], readBuf[i], String.Format("Data written does not match data read at index {0}", i)); } } } private long WriteMultiple(CircularBuffer smr, T[][] data, out int timeouts, int delay = 0, int timeout = 1000) where T: struct { T[] writeBuf = null; long totalBytesWritten = 0; int iteration = 0; timeouts = 0; while (iteration < data.Length) { if (delay > 0) Thread.Sleep(delay); writeBuf = data[iteration]; int written = smr.Write(writeBuf, timeout: timeout); totalBytesWritten += written; if (written == 0) timeouts++; else iteration++; } return totalBytesWritten; } private long ReadMultiple(CircularBuffer smr, T[][] writtenData, out int timeouts, int delay = 0, int timeout = 1000) where T : struct { T[] readBuf = new T[writtenData[0].Length]; long totalBytesRead = 0; int iteration = 0; timeouts = 0; while (iteration < writtenData.Length) { if (delay > 0) Thread.Sleep(delay); int read = smr.Read(readBuf, timeout: timeout); totalBytesRead += read; if (read == 0) timeouts++; else { iteration++; } } return totalBytesRead; } private long ReadMultipleWithCheck(CircularBuffer smr, T[][] writtenData, out int timeouts, int delay = 0, int timeout = 1000) where T: struct { T[] readBuf = new T[writtenData[0].Length]; long totalBytesRead = 0; int iteration = 0; timeouts = 0; while (iteration < writtenData.Length) { int read = smr.Read(readBuf, timeout: timeout); totalBytesRead += read; if (read == 0) timeouts++; else { for (var i = 0; i < readBuf.Length; i++) Assert.AreEqual(writtenData[iteration][i], readBuf[i], String.Format("Data written does not match data read for iteration {0} at index {1}", iteration, i)); iteration++; } if (delay > 0) Thread.Sleep(delay); } return totalBytesRead; } [TestMethod] public void ReadWriteAsync_1000NodesIn2NodeRing_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 32; int iterations = 1000; byte[][] data = new byte[iterations][]; int timeouts = 0; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i] = new byte[bufSize]; r.NextBytes(data[i]); } using (var producer = new CircularBuffer(name, 2, bufSize)) using (var consumer = new CircularBuffer(name)) { Action writer = () => { long totalBytesWritten = WriteMultiple(producer, data, out timeouts); Assert.AreEqual(totalBytesWritten, iterations * bufSize, "Failed to write all bytes"); }; Action reader = () => { long totalBytesRead = ReadMultipleWithCheck(consumer, data, out timeouts); Assert.AreEqual(totalBytesRead, iterations * bufSize, "Failed to read all bytes"); }; Task tWriter = Task.Factory.StartNew(writer); Task tReader = Task.Factory.StartNew(reader); if (!Task.WaitAll(new Task[] { tWriter, tReader }, 5000)) { Assert.Fail("Reader or writer took too long"); } } } /// /// Test the write node available event signal by making the writer thread wait for the reader thread. Done by simulating a slightly slower read vs write, with a low write timeout of 1ms. /// [TestMethod] public void ReadWriteAsync_SlowReaderSmallWriterTimeout_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 32; int iterations = 10; byte[][] data = new byte[iterations][]; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i] = new byte[bufSize]; r.NextBytes(data[i]); } using (var producer = new CircularBuffer(name, 2, bufSize)) using (var consumer = new CircularBuffer(name)) { Action writer = () => { int writeTimeouts = 0; long totalBytesWritten = WriteMultiple(producer, data, out writeTimeouts, 0, 1); Assert.IsTrue(writeTimeouts > 0); }; Action reader = () => { int readTimeouts = 0; long totalBytesRead = ReadMultipleWithCheck(consumer, data, out readTimeouts, 1); }; Task tWriter = Task.Factory.StartNew(writer); Task tReader = Task.Factory.StartNew(reader); if (!Task.WaitAll(new Task[] { tWriter, tReader }, 5000)) { Assert.Fail("Reader or writer took too long"); } } } /// /// Test the read data exists event signal by making the reader thread wait for the writer thread. Done by simulating a slightly slower write vs read, with a low read timeout of 1ms. /// [TestMethod] public void ReadWriteAsync_SlowWriterSmallReaderTimeout_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 32; int iterations = 10; byte[][] data = new byte[iterations][]; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i] = new byte[bufSize]; r.NextBytes(data[i]); } using (var producer = new CircularBuffer(name, 2, bufSize)) using (var consumer = new CircularBuffer(name)) { Action writer = () => { int writeTimeouts = 0; long totalBytesWritten = WriteMultiple(producer, data, out writeTimeouts, 1); }; Action reader = () => { int readTimeouts = 0; long totalBytesRead = ReadMultiple(consumer, data, out readTimeouts, 0, 1); Assert.IsTrue(readTimeouts > 0); }; Task tWriter = Task.Factory.StartNew(writer); Task tReader = Task.Factory.StartNew(reader); //Task.WaitAll(tReader, tWriter); if (!Task.WaitAll(new Task[] { tWriter, tReader }, 10000)) { Assert.Fail("Reader or writer took too long"); } } } /// /// Tests the integrity of the protected and functions to ensure that /// nodes are made available in the same order that they were reserved regardless of the order Post/ReturnNode is called. /// E.g. if nodes 1, 2 & 3 are reserved for writing in sequence, but they are ready in reverse order (i.e. PostNode is called for node 3, 2 and then 1), /// the call to PostNode for node 3 and 2 will simply mark as 1 and then return, while the call to PostNode /// for node 1 will move the WriteEnd pointer for node 1, 2 and then 3 also clearing the DoneWrite flag. This ensures that the nodes are ready for reading in /// the correct order and the read/write indexes maintain their integrity. The same applies to reading and ReturnNode. /// [TestMethod] public void ReadWrite_NonSequentialReadWrite_HeaderIndexesCorrect() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 1024; byte[] data = new byte[bufSize]; byte[] readBuf = new byte[bufSize]; CircularBuffer.NodeHeader header; // Fill with random data r.NextBytes(data); using (var smr = new CircularBuffer(name, 5, bufSize)) { header = smr.ReadNodeHeader(); Assert.AreEqual(0, header.WriteStart, "Initial WriteStart"); Assert.AreEqual(0, header.WriteEnd, "Intial WriteEnd"); Assert.AreEqual(0, header.ReadStart, "Initial ReadStart"); Assert.AreEqual(0, header.ReadEnd, "Initial ReadEnd"); Assert.AreEqual(bufSize, smr.Write((ptr) => { header = smr.ReadNodeHeader(); Assert.AreEqual(1, header.WriteStart, "During nested out of order write (1) WriteStart"); Assert.AreEqual(0, header.WriteEnd, "During nested out of order write (1) WriteEnd"); smr.Write((ptr2) => { header = smr.ReadNodeHeader(); Assert.AreEqual(2, header.WriteStart, "During nested out of order write (2) WriteStart"); Assert.AreEqual(0, header.WriteEnd, "During nested out of order write (2) WriteEnd"); smr.Write((ptr3) => { header = smr.ReadNodeHeader(); Assert.AreEqual(3, header.WriteStart, "During nested out of order write (3) WriteStart"); Assert.AreEqual(0, header.WriteEnd, "During nested out of order write (3) WriteEnd"); Marshal.Copy(data, 0, ptr3, bufSize); return bufSize; }); header = smr.ReadNodeHeader(); Assert.AreEqual(0, header.WriteEnd, "After nested out of order write (3) WriteEnd"); Marshal.Copy(data, 0, ptr2, bufSize); return bufSize; }); header = smr.ReadNodeHeader(); Assert.AreEqual(0, header.WriteEnd, "After nested out of order write (2) WriteEnd"); Marshal.Copy(data, 0, ptr, bufSize); return data.Length; }), String.Format("Failed to write {0} bytes", bufSize)); header = smr.ReadNodeHeader(); Assert.AreEqual(3, header.WriteStart, "After nested out of order writes (1,2,3) WriteStart"); Assert.AreEqual(3, header.WriteEnd, "After nested out of order writes (1,2,3) WriteEnd"); Assert.AreEqual(bufSize, smr.Read((ptr) => { header = smr.ReadNodeHeader(); Assert.AreEqual(1, header.ReadStart, "During nested out of order read (1) ReadStart"); Assert.AreEqual(0, header.ReadEnd, "During nested out of order read (1) ReadEnd"); smr.Read((ptr2) => { header = smr.ReadNodeHeader(); Assert.AreEqual(2, header.ReadStart, "During nested out of order read (2) ReadStart"); Assert.AreEqual(0, header.ReadEnd, "During nested out of order read (2) ReadEnd"); smr.Read((ptr3) => { header = smr.ReadNodeHeader(); Assert.AreEqual(3, header.ReadStart, "During nested out of order read (3) ReadStart"); Assert.AreEqual(0, header.ReadEnd, "During nested out of order read (3) ReadEnd"); Marshal.Copy(ptr3, readBuf, 0, smr.NodeBufferSize); return smr.NodeBufferSize; }); header = smr.ReadNodeHeader(); Assert.AreEqual(0, header.ReadEnd, "After nested out of order read (3) ReadEnd"); Marshal.Copy(ptr2, readBuf, 0, smr.NodeBufferSize); return smr.NodeBufferSize; }); header = smr.ReadNodeHeader(); Assert.AreEqual(0, header.ReadEnd, "After nested out of order read (2) ReadEnd"); Marshal.Copy(ptr, readBuf, 0, smr.NodeBufferSize); return smr.NodeBufferSize; }), String.Format("Failed to read {0} bytes", bufSize)); header = smr.ReadNodeHeader(); Assert.AreEqual(3, header.ReadStart, "After nested out of order read (1,2,3) ReadStart"); Assert.AreEqual(3, header.ReadEnd, "After nested out of order read (1,2,3) ReadEnd"); } } [StructLayout(LayoutKind.Sequential)] struct TestStruct { public int Value1; public int Value2; } [TestMethod] public void ReadWrite_StructuredData_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); TestStruct[] data = new TestStruct[100]; TestStruct[] readBuff = new TestStruct[100]; int bufSize = Marshal.SizeOf(typeof(TestStruct)) * 100; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i].Value1 = r.Next(); data[i].Value2 = r.Next(); } using (var smr = new CircularBuffer(name, 2, bufSize)) { var writeCount = smr.Write(data); var readCount = smr.Read(readBuff); Assert.AreEqual(100, writeCount); Assert.AreEqual(100, readCount); for (var i = 0; i < data.Length; i++) Assert.AreEqual(data[i], readBuff[i], String.Format("Data written does not match data read at index {0}", i)); } } [TestMethod] public void ReadWrite_StructuredData_ReadWriteStartIndex() { string name = Guid.NewGuid().ToString(); Random r = new Random(); TestStruct[] data = new TestStruct[88]; TestStruct[] readBuff = new TestStruct[88]; // Enough room to only fit 3 TestStruct elements int bufSize = Marshal.SizeOf(typeof(TestStruct)) * 3; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i].Value1 = r.Next(); data[i].Value2 = r.Next(); } // Tests that writing to the circular buffer using startIndex will correctly write and read the entire // source array, even though its size in bytes is not evenly divisible by the buffer size. using (var smr = new CircularBuffer(name, 2, bufSize)) { var totalWriteCount = 0; var iterations = 0; while (totalWriteCount < data.Length) { var writeCount = smr.Write(data, startIndex: totalWriteCount); var readCount = smr.Read(readBuff, startIndex: totalWriteCount); Assert.AreEqual(writeCount, readCount); totalWriteCount += writeCount; iterations++; } Assert.AreEqual(Math.Ceiling((double)(data.Length * Marshal.SizeOf(typeof(TestStruct))) / bufSize), iterations); Assert.AreEqual(data.Length, totalWriteCount); for (var i = 0; i < totalWriteCount; i++) Assert.AreEqual(data[i], readBuff[i], String.Format("Data written does not match data read at index {0}", i)); } } [TestMethod] public unsafe void ReadWrite_IntPtr_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 32; int iterations = 1; byte[][] data = new byte[iterations][]; byte[] readBuf = new byte[bufSize]; byte[] writeBuf = null; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i] = new byte[bufSize]; r.NextBytes(data[i]); } using (var smr = new CircularBuffer(name, 2, bufSize)) { for (var iteration = 0; iteration < iterations; iteration++) { writeBuf = data[iteration]; fixed (byte* wPtr = &writeBuf[0]) { Assert.AreEqual(bufSize, smr.Write((IntPtr)wPtr, bufSize), String.Format("Failed to write {0} bytes", bufSize)); } fixed (byte* rPtr = &readBuf[0]) { Assert.AreEqual(bufSize, smr.Read((IntPtr)rPtr, bufSize), String.Format("Failed to write {0} bytes", bufSize)); } for (var i = 0; i < writeBuf.Length; i++) Assert.AreEqual(writeBuf[i], readBuf[i], String.Format("Data written does not match data read at index {0}", i)); } } } [TestMethod] public unsafe void ReadWrite_DelegateIntPtr_DataMatches() { string name = Guid.NewGuid().ToString(); Random r = new Random(); int bufSize = 32; int iterations = 1; byte[][] data = new byte[iterations][]; byte[] readBuf = new byte[bufSize]; byte[] writeBuf = null; // Fill with random data for (var i = 0; i < data.Length; i++) { data[i] = new byte[bufSize]; r.NextBytes(data[i]); } // create write delegate Func writeFunc = (addr) => { int indx = 0; int count = writeBuf.Length; while (count > 0) { var b = writeBuf[indx++]; Marshal.WriteByte(addr, indx, b); count--; } return writeBuf.Length; }; // create read delegate Func readFunc = (addr) => { int indx = 0; int count = readBuf.Length; while (count > 0) { readBuf[indx++] = Marshal.ReadByte(addr, indx); count--; } return readBuf.Length; }; using (var smr = new CircularBuffer(name, 2, bufSize)) { for (var iteration = 0; iteration < iterations; iteration++) { writeBuf = data[iteration]; Assert.AreEqual(bufSize, smr.Write(writeFunc), String.Format("Failed to write {0} bytes", bufSize)); Assert.AreEqual(bufSize, smr.Read(readFunc), String.Format("Failed to write {0} bytes", bufSize)); for (var i = 0; i < writeBuf.Length; i++) Assert.AreEqual(writeBuf[i], readBuf[i], String.Format("Data written does not match data read at index {0}", i)); } } } #endregion } } ================================================ FILE: SharedMemory.Tests/ExpandingArrayTests.cs ================================================ using System; using System.Linq; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using SharedMemory.Utilities; namespace SharedMemoryTests { [TestClass] public class ExpandingArrayTests { [TestMethod] public void ExpandingArrayTests_GrownReport() { var sb = new StringBuilder(); for (int i = 0; i < 32; i++) { sb.AppendFormat("{0}/{1}\r\n", i, ExpandingArray.GetBucketIndex(i)); } Assert.AreEqual( @"0/0 1/0 2/0 3/1 4/1 5/1 6/1 7/2 8/2 9/2 10/2 11/2 12/2 13/2 14/2 15/3 16/3 17/3 18/3 19/3 20/3 21/3 22/3 23/3 24/3 25/3 26/3 27/3 28/3 29/3 30/3 31/4 ", sb.ToString()); } [TestMethod] public void ExpandingArrayTests_Basic() { var ea = new ExpandingArray(size => new int[size]); TestEArray(ea); ea.Clear(); TestEArray(ea); } private static void TestEArray(ExpandingArray ea) { Assert.AreEqual(0, ea.Count); ea.Add(11); Assert.AreEqual(1, ea.Count); ea.Add(22); Assert.AreEqual(2, ea.Count); Assert.IsTrue(ea.Contains(11)); Assert.IsFalse(ea.Contains(222)); ea.Add(33); ea.Add(44); ea.Add(55); ea.Add(66); ea.Add(77); ea.Add(88); ea.Add(99); ea.Add(1010); ea.Add(111); ea.Add(1212); ea.Add(1313); ea.Add(1414); ea.Add(1515); ea.Add(1616); ea.Add(1717); Assert.AreEqual(10403, ea.Sum()); Assert.AreEqual(6, ea.IndexOf(77)); Assert.AreEqual(77, ea[6]); ea[6] = 777; Assert.AreEqual(777, ea[6]); Assert.AreEqual(11103, ea.Sum()); // var a = new int[ea.Count + 1]; // ea.CopyTo(a, 1); // Assert.AreEqual( // @"[0], //[11], //[22], //[33], //[44], //[55], //[66], //[777], //[88], //[99], //[1010], //[111], //[1212], //[1313], //[1414], //[1515], //[1616], //[1717] //", a.Dump()); } } } ================================================ FILE: SharedMemory.Tests/FastStructureTests.cs ================================================ // SharedMemory (File: SharedMemoryTests\FastStructureTests.cs) // Copyright (c) 2014 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // The SharedMemory library is inspired by the following Code Project article: // "Fast IPC Communication Using Shared Memory and InterlockedCompareExchange" // http://www.codeproject.com/Articles/14740/Fast-IPC-Communication-Using-Shared-Memory-and-Int using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Runtime.InteropServices; using SharedMemory; namespace SharedMemoryTests { [TestClass] public class FastStructureTests { #region Test Structures [StructLayout(LayoutKind.Sequential, Pack = 1)] public unsafe struct CompatibleStructure { public int Integer1; public IntPtr Pointer1; public IntPtr Pointer2; public IntPtr Pointer3; public IntPtr Pointer4; public fixed byte Contents[8]; public int Bookend; } [StructLayout(LayoutKind.Sequential)] public struct IncompatibleNestedStructure { public int IncompatibleNestedStructure_One; public object IncompatibleNestedStructure_Two; } [StructLayout(LayoutKind.Sequential)] public struct IncompatibleNestedStructure2 { public int IncompatibleNestedStructure_One; public object IncompatibleNestedStructure_Two; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512, ArraySubType = UnmanagedType.I2)] public char[] Filename; } public struct HasIncompatibleStructure { public int HasIncompatibleStructure_One; public IncompatibleNestedStructure HasIncompatibleStructure_Two; } [StructLayout(LayoutKind.Sequential)] public struct ComplexStructure { public int FirstElement; public CompatibleStructure Compatible; public int FinalElement; } #endregion [TestMethod] public void FastStructure_IncompabitibleNestedType() { try { var size = FastStructure.Size; } catch (TypeInitializationException e) { return; } Assert.Fail("Did not throw TypeInitializationException for incompatible nested type: IncompatibleNestedStructure."); } [TestMethod] public void FastStructure_IncompatibleStructure() { try { var size = FastStructure.Size; } catch (TypeInitializationException e) { return; } Assert.Fail("Did not throw TypeInitializationException for incompatible type: NestedStructure2."); } [TestMethod] public void FastStructure_CompatibleStructureSize() { Assert.AreEqual(IntPtr.Size * 4 + 8 + (sizeof(int) * 2), FastStructure.Size); } [TestMethod] public void FastStructure_ComplexStructureSize() { var sizeOfCompatibleStructure = IntPtr.Size * 4 + 8 + (sizeof(int) * 2); var sizeOfComplexStructure = (sizeof(int) * 2) + sizeOfCompatibleStructure; Assert.AreEqual(sizeOfComplexStructure, FastStructure.Size); } [TestMethod] public void FastStructure_AllocHGlobalReadWrite() { IntPtr mem = Marshal.AllocHGlobal(FastStructure.SizeOf()); ComplexStructure n = new ComplexStructure(); n.Compatible.Integer1 = 1; n.Compatible.Bookend = 2; n.FirstElement = 3; n.FinalElement = 9; unsafe { n.Compatible.Contents[0] = 4; n.Compatible.Contents[7] = 5; } FastStructure.StructureToPtr(ref n, mem); // Assert that the reading and writing result in same structure ComplexStructure m = FastStructure.PtrToStructure(mem); Assert.AreEqual(n, m); Assert.AreEqual(n.Compatible.Integer1, m.Compatible.Integer1); Assert.AreEqual(n.Compatible.Bookend, m.Compatible.Bookend); unsafe { Assert.AreEqual(n.Compatible.Contents[0], m.Compatible.Contents[0]); Assert.AreEqual(n.Compatible.Contents[7], m.Compatible.Contents[7]); } // Assert that Marshal.PtrToStructure is compatible m = (ComplexStructure)Marshal.PtrToStructure(mem, typeof(ComplexStructure)); Assert.AreEqual(n, m); Assert.AreEqual(n.Compatible.Integer1, m.Compatible.Integer1); Assert.AreEqual(n.Compatible.Bookend, m.Compatible.Bookend); unsafe { Assert.AreEqual(n.Compatible.Contents[0], m.Compatible.Contents[0]); Assert.AreEqual(n.Compatible.Contents[7], m.Compatible.Contents[7]); } Marshal.FreeHGlobal(mem); } } } ================================================ FILE: SharedMemory.Tests/RpcBufferTests.cs ================================================ // SharedMemory (File: SharedMemoryTests\RpcBufferTests.cs) // Copyright (c) 2020 Justin Stenning // http://spazzarama.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Threading; using SharedMemory; using System.Diagnostics; namespace SharedMemoryTests { [TestClass] public class RpcBufferTests { string ipcName; RpcBuffer ipcMaster; RpcBuffer ipcSlave; [TestInitialize] public void Initialise() { ipcName = "MasterSlaveTest" + Guid.NewGuid().ToString(); } [TestCleanup] public void Cleanup() { ipcMaster?.Dispose(); ipcSlave?.Dispose(); } [TestMethod] public void Constructor_MasterSlave_Create() { ipcMaster = new RpcBuffer(ipcName, (msgId, payload) => { }); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { }); } [TestMethod] public void Constructor_BufferCapacityOutOfRange() { Assert.ThrowsException(() => new RpcBuffer(ipcName, 255)); Assert.ThrowsException(() => new RpcBuffer(ipcName, 1024*1024 + 1)); } [TestMethod] public void RPC_MasterCallsSlave() { ipcMaster = new RpcBuffer(ipcName); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { Assert.IsTrue(payload != null); Assert.IsTrue(payload.Length == 2); // Add the two bytes together return BitConverter.GetBytes((payload[0] + payload[1])); }); var result = ipcMaster.RemoteRequest(new byte[] { 123, 10 }); Assert.IsTrue(result.Success); Assert.AreEqual(123 + 10, BitConverter.ToInt32(result.Data, 0)); } [TestMethod] public async Task RPC_MasterCallsSlave_Async_WithCancellationToken_WithActualCancellation() { ipcMaster = new RpcBuffer(ipcName); var slaveBlockingTcs = new TaskCompletionSource(); var slaveBlockingTask = slaveBlockingTcs.Task; ipcSlave = new RpcBuffer(ipcName, async (msgId, payload) => { await slaveBlockingTask; return BitConverter.GetBytes((payload[0] + payload[1])); }); using (var cts = new CancellationTokenSource()) { var remoteRequestTask = ipcMaster.RemoteRequestAsync(new byte[] { 123, 10 }, cancellationToken: cts.Token); cts.Cancel(); if (await Task.WhenAny(remoteRequestTask, Task.Delay(TimeSpan.FromMilliseconds(50))) == remoteRequestTask) { var result = await remoteRequestTask; Assert.IsFalse(result.Success); } else { Assert.Fail("cancellation seems not to have worked"); } } } [TestMethod] public async Task RPC_MasterCallsSlave_Async_WithCancellationToken_WithoutCancellation() { ipcMaster = new RpcBuffer(ipcName); var slaveBlockingTcs = new TaskCompletionSource(); var slaveBlockingTask = slaveBlockingTcs.Task; ipcSlave = new RpcBuffer(ipcName, async (msgId, payload) => { await slaveBlockingTask; return BitConverter.GetBytes((payload[0] + payload[1])); }); using (var cts = new CancellationTokenSource()) { var remoteRequestTask = ipcMaster.RemoteRequestAsync(new byte[] { 123, 10 }, cancellationToken: cts.Token); slaveBlockingTcs.SetResult(false); var result = await remoteRequestTask; Assert.IsTrue(result.Success); } } [TestMethod] public async Task RPC_MasterCallsSlave_Sync_WithCancellationToken_WithActualCancellation() { ipcMaster = new RpcBuffer(ipcName); var slaveBlockingTcs = new TaskCompletionSource(); var slaveBlockingTask = slaveBlockingTcs.Task; ipcSlave = new RpcBuffer(ipcName, async (msgId, payload) => { await slaveBlockingTask; return BitConverter.GetBytes((payload[0] + payload[1])); }); using (var cts = new CancellationTokenSource()) { var remoteRequestTask = Task.Run(() => ipcMaster.RemoteRequest(new byte[] { 123, 10 }, cancellationToken: cts.Token)); cts.Cancel(); if (await Task.WhenAny(remoteRequestTask, Task.Delay(TimeSpan.FromMilliseconds(50))) == remoteRequestTask) { var result = await remoteRequestTask; Assert.IsFalse(result.Success); } else { Assert.Fail("cancellation seems not to have worked"); } } } [TestMethod] public async Task RPC_MasterCallsSlave_Sync_WithCancellationToken_WithoutCancellation() { ipcMaster = new RpcBuffer(ipcName); var slaveBlockingTcs = new TaskCompletionSource(); var slaveBlockingTask = slaveBlockingTcs.Task; ipcSlave = new RpcBuffer(ipcName, async (msgId, payload) => { await slaveBlockingTask; return BitConverter.GetBytes((payload[0] + payload[1])); }); using (var cts = new CancellationTokenSource()) { var remoteRequestTask = Task.Run(() => ipcMaster.RemoteRequest(new byte[] { 123, 10 }, cancellationToken: cts.Token)); slaveBlockingTcs.SetResult(false); var result = await remoteRequestTask; Assert.IsTrue(result.Success); } } [TestMethod] public void RPC_Statistics_Reset() { ipcMaster = new RpcBuffer(ipcName); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { Assert.IsTrue(payload != null); Assert.IsTrue(payload.Length == 2); // Add the two bytes together return BitConverter.GetBytes((payload[0] + payload[1])); }); var result = ipcMaster.RemoteRequest(new byte[] { 123, 10 }); Assert.IsTrue(result.Success); Assert.AreEqual((ulong)1, ipcMaster.Statistics.RequestsSent); Assert.AreEqual((ulong)1, ipcSlave.Statistics.RequestsReceived); Assert.AreEqual((ulong)1, ipcSlave.Statistics.ResponsesSent); Assert.AreEqual((ulong)1, ipcMaster.Statistics.ResponsesReceived); ipcMaster.Statistics.Reset(); var empty = new RpcStatistics(); Assert.AreEqual(empty.RequestsSent, ipcMaster.Statistics.RequestsSent); Assert.AreEqual(empty.ResponsesReceived, ipcMaster.Statistics.ResponsesReceived); Assert.AreEqual(empty.ReadingLastMessageSize, ipcMaster.Statistics.ReadingLastMessageSize); Assert.AreEqual(empty.WritingLastMessageSize, ipcMaster.Statistics.WritingLastMessageSize); } [TestMethod] public void RPC_MasterCallsSlave_Exception() { ipcMaster = new RpcBuffer(ipcName); ipcSlave = new RpcBuffer(ipcName, async (msgId, payload) => { throw new Exception("test exception"); }); var result = ipcMaster.RemoteRequest(null); Assert.IsFalse(result.Success); } [TestMethod] public void RPC_Bidirectional_Nested() { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { // Ask slave to multiply the two bytes return (await ipcMaster.RemoteRequestAsync(new byte[] { 3, 3 }).ConfigureAwait(false)).Data; }); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { return new byte[] { (byte)(payload[0] * payload[1]) }; }); // Send request to master from slave var result = ipcSlave.RemoteRequest(null); Assert.IsTrue(result.Success); Assert.AreEqual((3 * 3), result.Data[0]); } [TestMethod] public void RPC_Timeout() { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { }, bufferCapacity: 256); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { Task.Delay(1000).Wait(); return new byte[] { (byte)(payload[0] * payload[1]) }; }); var result = ipcMaster.RemoteRequest(new byte[] { 3, 3 }, 100); Assert.IsFalse(result.Success); } [TestMethod] public void RPC_Timeout_FireAndForget() { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { }, bufferCapacity: 256); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { Task.Delay(1000).Wait(); return new byte[] { (byte)(payload[0] * payload[1]) }; }); var result = ipcMaster.RemoteRequest(new byte[] { 3, 3 }, 0); Assert.IsFalse(result.Success); } #if DEBUG [TestMethod] public void RPC_LoadTest_5k_Small() { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { }, bufferCapacity: 256); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { return new byte[] { (byte)(payload[0] * payload[1]) }; }); Stopwatch watch = Stopwatch.StartNew(); // Send request to slave from master for (var i = 0; i < 5000; i++) { var result = ipcMaster.RemoteRequest(new byte[] { 3, 3 }, 100); Assert.IsTrue(result.Success); Assert.AreEqual((3 * 3), result.Data[0]); } watch.Stop(); Assert.IsTrue(watch.ElapsedMilliseconds < 1000); } [TestMethod] public void RPC_LoadTest_5k_Small_Multi_Thread() { // Warmup the Theadpool ThreadPool.SetMinThreads(15, 10); ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { }, bufferCapacity: 256); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { return new byte[] { (byte)(payload[0] * payload[1]) }; }); Stopwatch watch = Stopwatch.StartNew(); List tasks = new List(); for (int i = 0; i < 10; i++) { tasks.Add(Task.Run(() => { // Send request to slave from master for (var j = 0; j < 5000; j++) { var result = ipcMaster.RemoteRequest(new byte[] { 3, 3 }); Assert.IsTrue(result.Success); Assert.AreEqual((3 * 3), result.Data[0]); } })); } Task.WaitAll(tasks.ToArray()); watch.Stop(); Assert.IsTrue(watch.ElapsedMilliseconds < 1000); } [TestMethod] public void RPC_LoadTest_1k_Large() { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { }, bufferCapacity: 1025 * 512); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { return new byte[] { (byte)(payload[0] * payload[1]) }; }); var buf = new byte[1025 * 512]; buf[0] = 3; buf[1] = 3; Stopwatch watch = Stopwatch.StartNew(); // Send request to slave from master for (var i = 0; i < 1000; i++) { var result = ipcMaster.RemoteRequest(buf, 100); Assert.IsTrue(result.Success); Assert.AreEqual((3 * 3), result.Data[0]); } watch.Stop(); Assert.IsTrue(watch.ElapsedMilliseconds < 2000); } [TestMethod] public void RPC_LoadTest_NestedCalls() { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { // Ask slave to multiply the two bytes return (await ipcMaster.RemoteRequestAsync(new byte[] { 3, 3 }).ConfigureAwait(false)).Data; }); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { return new byte[] { (byte)(payload[0] * payload[1]) }; }); Stopwatch watch = Stopwatch.StartNew(); // Send request to master from slave for (var i = 0; i < 10; i++) { var result = ipcSlave.RemoteRequest(null, 30000); Assert.IsTrue(result.Success); Assert.AreEqual((3 * 3), result.Data[0]); } watch.Stop(); Assert.IsTrue(watch.ElapsedMilliseconds < 1000); } #endif [TestMethod] public void RPC_SlaveCallsMasterAfterClosed_Exception() { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { }); ipcSlave = new RpcBuffer(ipcName); ipcSlave.RemoteRequest(null); ipcMaster.Dispose(); while (!ipcMaster.DisposeFinished) { Task.Delay(125).Wait(); } Assert.ThrowsException(() => ipcSlave.RemoteRequest(null)); } [TestMethod] public void RPC_Dispose() { // Warmup the Theadpool ThreadPool.SetMinThreads(15, 10); for (int i = 0; i < 10; i++) { ipcMaster = new RpcBuffer(ipcName, async (msgId, payload) => { }, bufferCapacity: 256); ipcSlave = new RpcBuffer(ipcName, (msgId, payload) => { ipcSlave.Dispose(); return new byte[] { (byte)(payload[0] * payload[1]) }; }); Stopwatch watch = Stopwatch.StartNew(); ipcMaster.RemoteRequestAsync(new byte[] { 3, 3 }); Task.Delay(125).Wait(); ipcSlave.Dispose(); watch.Stop(); ipcMaster.Dispose(); while (!ipcMaster.DisposeFinished || !ipcSlave.DisposeFinished) { Task.Delay(125).Wait(); } } } } } ================================================ FILE: SharedMemory.Tests/SharedMemory.Tests.csproj ================================================ netcoreapp3.0;netcoreapp2.0;net47;net46;net45 false true ================================================ FILE: SharedMemory.nuspec ================================================  SharedMemory $version$ SharedMemory Justin Stenning Justin Stenning https://github.com/spazzarama/SharedMemory/blob/master/LICENSE.md https://github.com/spazzarama/SharedMemory true The SharedMemory library provides a set of C# classes that utilise a memory-mapped file for fast low-level inter-process communication (IPC) - specifically for sharing data between processes. It features: * a lock-free FIFO circular buffer * a simple fixed-size generic shared memory array class * a bi-directional RPC implementation (.NET 4.5+ / .NET Standard 2.0+ only) * an implementation of a shared memory buffer for read/write. * support for memory-mapped files in .NET 3.5/4/4.5 and .NET Standard 2.0+ * fast generic structure reading/writing Usage: https://github.com/spazzarama/SharedMemory Shared memory classes for sharing data between processes (Array, Buffer, Circular Buffer and RPC Buffer) $version$ 1. Added bi-directional RPC implementation (RpcBuffer) for .NET 4.5+ / .NET Standard 2.0+ 1. Added CopyTo/ToBytes/FromBytes/ReadBytes/WriteBytes to FastStructure 1. Added .NET Standard 2.1 build Copyright (c) 2014-2020 Justin Stenning IPC RPC memory mapped file shared data circular ring buffer ================================================ FILE: SharedMemory.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29728.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedMemory", "SharedMemory\SharedMemory.csproj", "{49EEAE24-99C6-48F9-8784-4C9196BD1958}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedMemory.Tests", "SharedMemory.Tests\SharedMemory.Tests.csproj", "{51DD9C46-955A-454A-952E-44046FFBEEB5}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{D3675823-5797-428B-A5D4-83824612F924}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientTest", "Examples\ClientTest\ClientTest.csproj", "{69FC3A5E-C4C7-47BA-B553-FF9477D56B9B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerTest", "Examples\ServerTest\ServerTest.csproj", "{6FC3F56C-B2DD-4E81-8425-CCAF2A6FF343}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleProcess", "Examples\SingleProcess\SingleProcess.csproj", "{58858E6C-4455-443F-A288-2B4BDCEDFB10}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RpcTest", "Examples\RpcTest\RpcTest.csproj", "{295402B7-EEF8-4A79-968E-58135AE5D385}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {49EEAE24-99C6-48F9-8784-4C9196BD1958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {49EEAE24-99C6-48F9-8784-4C9196BD1958}.Debug|Any CPU.Build.0 = Debug|Any CPU {49EEAE24-99C6-48F9-8784-4C9196BD1958}.Release|Any CPU.ActiveCfg = Release|Any CPU {49EEAE24-99C6-48F9-8784-4C9196BD1958}.Release|Any CPU.Build.0 = Release|Any CPU {51DD9C46-955A-454A-952E-44046FFBEEB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {51DD9C46-955A-454A-952E-44046FFBEEB5}.Debug|Any CPU.Build.0 = Debug|Any CPU {51DD9C46-955A-454A-952E-44046FFBEEB5}.Release|Any CPU.ActiveCfg = Release|Any CPU {51DD9C46-955A-454A-952E-44046FFBEEB5}.Release|Any CPU.Build.0 = Release|Any CPU {69FC3A5E-C4C7-47BA-B553-FF9477D56B9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {69FC3A5E-C4C7-47BA-B553-FF9477D56B9B}.Debug|Any CPU.Build.0 = Debug|Any CPU {69FC3A5E-C4C7-47BA-B553-FF9477D56B9B}.Release|Any CPU.ActiveCfg = Release|Any CPU {69FC3A5E-C4C7-47BA-B553-FF9477D56B9B}.Release|Any CPU.Build.0 = Release|Any CPU {6FC3F56C-B2DD-4E81-8425-CCAF2A6FF343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6FC3F56C-B2DD-4E81-8425-CCAF2A6FF343}.Debug|Any CPU.Build.0 = Debug|Any CPU {6FC3F56C-B2DD-4E81-8425-CCAF2A6FF343}.Release|Any CPU.ActiveCfg = Release|Any CPU {6FC3F56C-B2DD-4E81-8425-CCAF2A6FF343}.Release|Any CPU.Build.0 = Release|Any CPU {58858E6C-4455-443F-A288-2B4BDCEDFB10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58858E6C-4455-443F-A288-2B4BDCEDFB10}.Debug|Any CPU.Build.0 = Debug|Any CPU {58858E6C-4455-443F-A288-2B4BDCEDFB10}.Release|Any CPU.ActiveCfg = Release|Any CPU {58858E6C-4455-443F-A288-2B4BDCEDFB10}.Release|Any CPU.Build.0 = Release|Any CPU {295402B7-EEF8-4A79-968E-58135AE5D385}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {295402B7-EEF8-4A79-968E-58135AE5D385}.Debug|Any CPU.Build.0 = Debug|Any CPU {295402B7-EEF8-4A79-968E-58135AE5D385}.Release|Any CPU.ActiveCfg = Release|Any CPU {295402B7-EEF8-4A79-968E-58135AE5D385}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {69FC3A5E-C4C7-47BA-B553-FF9477D56B9B} = {D3675823-5797-428B-A5D4-83824612F924} {6FC3F56C-B2DD-4E81-8425-CCAF2A6FF343} = {D3675823-5797-428B-A5D4-83824612F924} {58858E6C-4455-443F-A288-2B4BDCEDFB10} = {D3675823-5797-428B-A5D4-83824612F924} {295402B7-EEF8-4A79-968E-58135AE5D385} = {D3675823-5797-428B-A5D4-83824612F924} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DD0E1614-8CF6-460D-95BD-8E178CA17132} EndGlobalSection EndGlobal ================================================ FILE: appveyor-develop.yml ================================================ version: 2.2.{build} pull_requests: do_not_increment_build_number: true branches: only: - master skip_tags: true image: Visual Studio 2019 configuration: Release platform: Any CPU shallow_clone: true build_script: - cmd: >- dotnet build -f netstandard2.1 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory\SharedMemory.csproj dotnet build -f netstandard2.0 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory\SharedMemory.csproj dotnet build -f netcoreapp3.0 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory.Tests\SharedMemory.Tests.csproj dotnet build -f netcoreapp2.0 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory.Tests\SharedMemory.Tests.csproj dotnet build -f net47 -v m /p:Version=%APPVEYOR_BUILD_VERSION% dotnet build -f net46 -v m /p:Version=%APPVEYOR_BUILD_VERSION% dotnet build -f net45 -v m /p:Version=%APPVEYOR_BUILD_VERSION% msbuild .\SharedMemory\SharedMemory.csproj /verbosity:m /p:TargetFramework=net4 /p:Version=%APPVEYOR_BUILD_VERSION% msbuild .\SharedMemory\SharedMemory.csproj /verbosity:m /p:TargetFramework=net35 /p:Version=%APPVEYOR_BUILD_VERSION% nuget pack SharedMemory.nuspec -Symbols -Version %APPVEYOR_BUILD_VERSION% artifacts: - path: bin\Release\net47\SharedMemory.dll name: SharedMemory.dll - path: Examples\SingleProcess\bin\Release\net47\SingleProcess.exe name: SingleProcess.exe - path: Examples\ClientTest\bin\Release\net47\ClientTest.exe name: ClientTest.exe - path: Examples\ServerTest\bin\Release\net47\ServerTest.exe name: ServerTest.exe - path: Examples\RpcTest\bin\Release\net47\RpcTest.exe name: RpcTest.exe - path: '**\*.nupkg' name: NuGet ================================================ FILE: appveyor.yml ================================================ version: 2.3.{build} pull_requests: do_not_increment_build_number: true branches: only: - master skip_tags: true image: Visual Studio 2019 configuration: Release platform: Any CPU shallow_clone: true build_script: - cmd: >- dotnet build -f netstandard2.1 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory\SharedMemory.csproj dotnet build -f netstandard2.0 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory\SharedMemory.csproj dotnet build -f netcoreapp3.0 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory.Tests\SharedMemory.Tests.csproj dotnet build -f netcoreapp2.0 -v m /p:Version=%APPVEYOR_BUILD_VERSION% .\SharedMemory.Tests\SharedMemory.Tests.csproj dotnet build -f net47 -v m /p:Version=%APPVEYOR_BUILD_VERSION% dotnet build -f net46 -v m /p:Version=%APPVEYOR_BUILD_VERSION% dotnet build -f net45 -v m /p:Version=%APPVEYOR_BUILD_VERSION% msbuild .\SharedMemory\SharedMemory.csproj /verbosity:m /p:TargetFramework=net4 /p:Version=%APPVEYOR_BUILD_VERSION% msbuild .\SharedMemory\SharedMemory.csproj /verbosity:m /p:TargetFramework=net35 /p:Version=%APPVEYOR_BUILD_VERSION% nuget pack SharedMemory.nuspec -Symbols -Version %APPVEYOR_BUILD_VERSION% artifacts: - path: bin\Release\net47\SharedMemory.dll name: SharedMemory.dll - path: Examples\SingleProcess\bin\Release\net47\SingleProcess.exe name: SingleProcess.exe - path: Examples\ClientTest\bin\Release\net47\ClientTest.exe name: ClientTest.exe - path: Examples\ServerTest\bin\Release\net47\ServerTest.exe name: ServerTest.exe - path: Examples\RpcTest\bin\Release\net47\RpcTest.exe name: RpcTest.exe - path: '**\*.nupkg' name: NuGet deploy: - provider: NuGet api_key: secure: e4koh5XAZTPiyEU1naOGPiO3wvz14pD6huFncm2ffzxbO+Vh0kNX2lOBQoa/BV+2