Created
February 27, 2019 20:41
-
-
Save ghorsington/b612e1fa0ce9c62c850612060dfee063 to your computer and use it in GitHub Desktop.
A simple ghetto named pipe implementation for win32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.ComponentModel; | |
| using System.IO; | |
| using System.Runtime.InteropServices; | |
| namespace GhettoPipes | |
| { | |
| static class Win32 | |
| { | |
| public const uint GENERIC_READ = 0x80000000; | |
| public const uint GENERIC_WRITE = 0x40000000; | |
| public const int INVALID_HANDLE_VALUE = -1; | |
| public const uint OPEN_EXISTING = 3; | |
| public const uint PIPE_WAIT = 0x00000000; | |
| public const uint PIPE_TYPE_BYTE = 0x00000000; | |
| public const uint PIPE_UNLIMITED_INSTANCES = 255; | |
| public const uint NMPWAIT_WAIT_FOREVER = 0xffffffff; | |
| public const ulong ERROR_PIPE_CONNECTED = 535; | |
| [DllImport("kernel32.dll", EntryPoint = "CreateFile", SetLastError = true)] | |
| public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, | |
| IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern IntPtr CreateNamedPipe(string lpName, // pipe name | |
| uint dwOpenMode, // pipe open mode | |
| uint dwPipeMode, // pipe-specific modes | |
| uint nMaxInstances, // maximum number of instances | |
| uint nOutBufferSize, // output buffer size | |
| uint nInBufferSize, // input buffer size | |
| uint nDefaultTimeOut, // time-out interval | |
| IntPtr pipeSecurityDescriptor // SD | |
| ); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool DisconnectNamedPipe(IntPtr hHandle); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool ConnectNamedPipe(IntPtr hHandle, // handle to named pipe | |
| IntPtr lpOverlapped // overlapped structure | |
| ); | |
| [DllImport("kernel32.dll", EntryPoint = "PeekNamedPipe", SetLastError = true)] | |
| public static extern bool PeekNamedPipe(IntPtr handle, byte[] buffer, uint nBufferSize, ref uint bytesRead, | |
| ref uint bytesAvail, ref uint bytesLeft); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool ReadFile(IntPtr handle, byte[] buffer, uint toRead, ref uint read, | |
| IntPtr lpOverLapped); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool WriteFile(IntPtr handle, byte[] buffer, uint count, ref uint written, | |
| IntPtr lpOverlapped); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool CloseHandle(IntPtr handle); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool FlushFileBuffers(IntPtr handle); | |
| } | |
| /// <summary> | |
| /// Original version from https://www.pinvoke.net/default.aspx/kernel32/CreateNamedPipe.html | |
| /// | |
| /// NamedPipeStream is an odd little hybrid between the client and server ends of a NamedPipe, | |
| /// and a System.IO.Stream subclass. Basically, it simplifies the unnecessarily horrific process | |
| /// of implementing named pipe support in .Net. (If you doubt this, try it the hard way... we'll wait.) | |
| /// Usage idiom: | |
| /// Server side | |
| /// ----------- | |
| /// 1. Call NamedPipeStream.Create, specify inbound, outbound, or both | |
| /// 2. Call Listen(). This will block until a client connects. Sorry, the alternatives | |
| /// are ugly. Use a thread. | |
| /// 3. Call DataAvailable() in a loop with Read(), Write, ReadLine(), etc. until IsConnected turns false. | |
| /// 4. Call Listen() again to wait for the next client. | |
| /// Client side | |
| /// ----------- | |
| /// 1. Call Open() | |
| /// 2. Call DataAvailable(), Read(), Write(), etc. until you're done, | |
| /// then call Close(); | |
| /// And yes, you can attach TextReader and TextWriter instances to this stream. | |
| /// Server side caveat: | |
| /// The idiom described above only supports a single client at a time. If you need | |
| /// to support multiple clients, multiple calls to Create()/Listen() in separate threads is the | |
| /// recommended approach. | |
| /// There is a test driver class at the end of this file which can be cannibalized for sample usage code. | |
| /// </summary> | |
| public class NamedPipeStream : Stream | |
| { | |
| /// <summary> | |
| /// Type of the pipe | |
| /// </summary> | |
| public enum PeerType | |
| { | |
| /// <summary> | |
| /// The pipe is a client pipe. | |
| /// </summary> | |
| Client = 0, | |
| /// <summary> | |
| /// The pipe is a server pipe. | |
| /// </summary> | |
| Server = 1 | |
| } | |
| /// <summary> | |
| /// Available server modes | |
| /// </summary> | |
| public enum ServerMode | |
| { | |
| /// <summary> | |
| /// Allow only receiving data. | |
| /// </summary> | |
| InboundOnly = 0x00000001, | |
| /// <summary> | |
| /// Allow only send data. | |
| /// </summary> | |
| OutboundOnly = 0x00000002, | |
| /// <summary> | |
| /// Allow to both send and receive data. | |
| /// </summary> | |
| Bidirectional = InboundOnly + OutboundOnly | |
| } | |
| readonly PeerType peerType; | |
| IntPtr handle; | |
| FileAccess mode; | |
| protected NamedPipeStream() | |
| { | |
| handle = IntPtr.Zero; | |
| mode = 0; | |
| peerType = PeerType.Server; | |
| } | |
| /// <summary> | |
| /// Constructs a client named pipe | |
| /// </summary> | |
| /// <param name="pipeName">Full name of the pipe</param> | |
| /// <param name="mode">Pipe access mode</param> | |
| public NamedPipeStream(string pipeName, FileAccess mode) | |
| { | |
| handle = IntPtr.Zero; | |
| peerType = PeerType.Client; | |
| Open(pipeName, mode); | |
| } | |
| /// <summary> | |
| /// Initializes a managed named pipe around the unmanaged pipe handle. | |
| /// </summary> | |
| /// <param name="handle">Pointer to the pipe handle.</param> | |
| /// <param name="mode">Pipe access mode</param> | |
| public NamedPipeStream(IntPtr handle, FileAccess mode) | |
| { | |
| this.handle = handle; | |
| this.mode = mode; | |
| peerType = PeerType.Client; | |
| } | |
| /// <summary> | |
| /// Returns true if client is connected. Should only be called after Listen() succeeds. | |
| /// </summary> | |
| /// <returns></returns> | |
| public bool IsConnected | |
| { | |
| get | |
| { | |
| if (peerType != PeerType.Server) throw new Exception("IsConnected() is only for server-side streams"); | |
| if (Win32.ConnectNamedPipe(handle, IntPtr.Zero)) return false; | |
| return (uint) Marshal.GetLastWin32Error() == Win32.ERROR_PIPE_CONNECTED; | |
| } | |
| } | |
| /// <summary> | |
| /// Returns true if there is data available to read. | |
| /// </summary> | |
| public bool DataAvailable | |
| { | |
| get | |
| { | |
| uint bytesRead = 0, avail = 0, thismsg = 0; | |
| var result = Win32.PeekNamedPipe(handle, null, 0, ref bytesRead, ref avail, ref thismsg); | |
| return result && avail > 0; | |
| } | |
| } | |
| /// <inheritdoc /> | |
| public override bool CanRead => (mode & FileAccess.Read) > 0; | |
| /// <inheritdoc /> | |
| public override bool CanWrite => (mode & FileAccess.Write) > 0; | |
| /// <inheritdoc /> | |
| public override bool CanSeek => false; | |
| /// <inheritdoc /> | |
| public override long Length => throw new NotSupportedException("NamedPipeStream does not support seeking"); | |
| /// <inheritdoc /> | |
| public override long Position | |
| { | |
| get => throw new NotSupportedException("NamedPipeStream does not support seeking"); | |
| set { } | |
| } | |
| /// <summary> | |
| /// Opens the client side of a pipe. | |
| /// </summary> | |
| /// <param name="pipeName">Full path to the pipe, e.g. '\\.\pipe\mypipe'</param> | |
| /// <param name="accessMode">Read,Write, or ReadWrite - must be compatible with server-side creation mode</param> | |
| public void Open(string pipeName, FileAccess accessMode) | |
| { | |
| uint pipeMode = 0; | |
| if ((accessMode & FileAccess.Read) > 0) pipeMode |= Win32.GENERIC_READ; | |
| if ((accessMode & FileAccess.Write) > 0) pipeMode |= Win32.GENERIC_WRITE; | |
| var pipeHandle = Win32.CreateFile(pipeName, pipeMode, 0, IntPtr.Zero, Win32.OPEN_EXISTING, 0, IntPtr.Zero); | |
| if (pipeHandle.ToInt32() == Win32.INVALID_HANDLE_VALUE) | |
| { | |
| var err = Marshal.GetLastWin32Error(); | |
| throw new Win32Exception(err, | |
| $"NamedPipeStream.Open failed, win32 error code {err}, pipe name '{pipeName}' "); | |
| } | |
| mode = accessMode; | |
| handle = pipeHandle; | |
| } | |
| /// <summary> | |
| /// Create a server named pipe instance. | |
| /// </summary> | |
| /// <param name="pipeName">Local name (the part after \\.\pipe\)</param> | |
| /// <param name="mode">Server mode of the pipe</param> | |
| public static NamedPipeStream Create(string pipeName, ServerMode mode) | |
| { | |
| var name = $@"\\.\pipe\{pipeName}"; | |
| var handle = Win32.CreateNamedPipe(name, (uint) mode, Win32.PIPE_TYPE_BYTE | Win32.PIPE_WAIT, | |
| Win32.PIPE_UNLIMITED_INSTANCES, 0, // outBuffer, | |
| 1024, // inBuffer, | |
| Win32.NMPWAIT_WAIT_FOREVER, IntPtr.Zero); | |
| if (handle.ToInt32() == Win32.INVALID_HANDLE_VALUE) | |
| throw new Win32Exception( | |
| $"Error creating named pipe {name}. Internal error: {Marshal.GetLastWin32Error()}"); | |
| // Set members persistently... | |
| var self = new NamedPipeStream {handle = handle}; | |
| switch (mode) | |
| { | |
| case ServerMode.InboundOnly: | |
| self.mode = FileAccess.Read; | |
| break; | |
| case ServerMode.OutboundOnly: | |
| self.mode = FileAccess.Write; | |
| break; | |
| case ServerMode.Bidirectional: | |
| self.mode = FileAccess.ReadWrite; | |
| break; | |
| default: | |
| throw new ArgumentOutOfRangeException(nameof(mode), mode, null); | |
| } | |
| return self; | |
| } | |
| /// <summary> | |
| /// Server only: block until client connects | |
| /// </summary> | |
| /// <returns></returns> | |
| public bool Listen() | |
| { | |
| if (peerType != PeerType.Server) throw new Exception("Listen() is only for server-side streams"); | |
| Win32.DisconnectNamedPipe(handle); | |
| if (Win32.ConnectNamedPipe(handle, IntPtr.Zero)) return true; | |
| return (uint) Marshal.GetLastWin32Error() == Win32.ERROR_PIPE_CONNECTED; | |
| } | |
| /// <summary> | |
| /// Server only: disconnect the pipe. For most applications, you should just call Listen() | |
| /// instead, which automatically does a disconnect of any old connection. | |
| /// </summary> | |
| public void Disconnect() | |
| { | |
| if (peerType != PeerType.Server) throw new Exception("Disconnect() is only for server-side streams"); | |
| Win32.DisconnectNamedPipe(handle); | |
| } | |
| /// <inheritdoc /> | |
| public override void Flush() | |
| { | |
| if (handle == IntPtr.Zero) | |
| throw new ObjectDisposedException("NamedPipeStream", "The stream has already been closed"); | |
| Win32.FlushFileBuffers(handle); | |
| } | |
| /// <inheritdoc /> | |
| public override int Read(byte[] buffer, int offset, int count) | |
| { | |
| if (buffer == null) | |
| throw new ArgumentNullException(nameof(buffer), "The buffer to read into cannot be null"); | |
| if (buffer.Length < offset + count) | |
| throw new ArgumentException("Buffer is not large enough to hold requested data", nameof(buffer)); | |
| if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset cannot be negative"); | |
| if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Count cannot be negative"); | |
| if (!CanRead) throw new NotSupportedException("The stream does not support reading"); | |
| if (handle == IntPtr.Zero) | |
| throw new ObjectDisposedException("NamedPipeStream", "The stream has already been closed"); | |
| // first read the data into an internal buffer since ReadFile cannot read into a buf at | |
| // a specified offset | |
| uint read = 0; | |
| var buf = buffer; | |
| if (offset != 0) buf = new byte[count]; | |
| var f = Win32.ReadFile(handle, buf, (uint) count, ref read, IntPtr.Zero); | |
| if (!f) throw new Win32Exception(Marshal.GetLastWin32Error(), "ReadFile failed"); | |
| if (offset == 0) return (int) read; | |
| for (var x = 0; x < read; x++) buffer[offset + x] = buf[x]; | |
| return (int) read; | |
| } | |
| /// <inheritdoc /> | |
| public override void Close() | |
| { | |
| Win32.CloseHandle(handle); | |
| handle = IntPtr.Zero; | |
| } | |
| /// <inheritdoc /> | |
| public override void SetLength(long length) | |
| { | |
| throw new NotSupportedException("NamedPipeStream doesn't support SetLength"); | |
| } | |
| /// <inheritdoc /> | |
| public override void Write(byte[] buffer, int offset, int count) | |
| { | |
| if (buffer == null) | |
| throw new ArgumentNullException(nameof(buffer), "The buffer to write into cannot be null"); | |
| if (buffer.Length < offset + count) | |
| throw new ArgumentException("Buffer does not contain amount of requested data", nameof(buffer)); | |
| if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset cannot be negative"); | |
| if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Count cannot be negative"); | |
| if (!CanWrite) throw new NotSupportedException("The stream does not support writing"); | |
| if (handle == IntPtr.Zero) | |
| throw new ObjectDisposedException("NamedPipeStream", "The stream has already been closed"); | |
| // copy data to internal buffer to allow writing from a specified offset | |
| if (offset != 0) | |
| { | |
| var buf = new byte[count]; | |
| for (var x = 0; x < count; x++) buf[x] = buffer[offset + x]; | |
| buffer = buf; | |
| } | |
| uint written = 0; | |
| var result = Win32.WriteFile(handle, buffer, (uint) count, ref written, IntPtr.Zero); | |
| if (!result) | |
| { | |
| var err = Marshal.GetLastWin32Error(); | |
| throw new Win32Exception(err, "Writing to the stream failed"); | |
| } | |
| if (written < count) throw new IOException("Unable to write entire buffer to stream"); | |
| } | |
| public override long Seek(long offset, SeekOrigin origin) | |
| { | |
| throw new NotSupportedException("NamedPipeStream doesn't support seeking"); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment