diff --git a/README.md b/README.md index 50a39e9..78468b3 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,7 @@ details. This project makes use of the following third-party libraries: -- A modified version of Twosense's fork of [Named Pipe Wrapper](https://github.com/twosense/named-pipe-wrapper) - for communication between the service and UI program (called `YAMDCC.IPC` in the source files). +- My fork of [Named Pipe Wrapper](https://github.com/Sparronator9999/NamedPipeWrapper) for\ + communication between the service and UI program (called `YAMDCC.IPC` in the source files). - [WinRing0](https://github.com/QCute/WinRing0) for low-level hardware access required to read/write the EC. diff --git a/YAMDCC.GUI/MainWindow.cs b/YAMDCC.GUI/MainWindow.cs index 3ff1aa5..f8dc621 100644 --- a/YAMDCC.GUI/MainWindow.cs +++ b/YAMDCC.GUI/MainWindow.cs @@ -204,12 +204,12 @@ private void OnProcessExit(object sender, EventArgs e) IPCClient.Stop(); } - private void IPC_MessageReceived(NamedPipeConnection connection, ServiceResponse message) + private void IPC_MessageReceived(object sender, PipeMessageEventArgs e) { - string[] args = message.Value.Split(' '); + string[] args = e.Message.Value.Split(' '); if (args.Length == 1) { - switch (message.Response) + switch (e.Message.Response) { case Response.Temp: if (int.TryParse(args[0], out int value)) diff --git a/YAMDCC.IPC/ConnectionFactory.cs b/YAMDCC.IPC/ConnectionFactory.cs new file mode 100644 index 0000000..2338fd3 --- /dev/null +++ b/YAMDCC.IPC/ConnectionFactory.cs @@ -0,0 +1,16 @@ +using System.IO.Pipes; + +namespace YAMDCC.IPC +{ + internal static class ConnectionFactory + { + private static int _lastId; + + internal static NamedPipeConnection CreateConnection(PipeStream pipeStream) + where TRead : class + where TWrite : class + { + return new NamedPipeConnection(++_lastId, $"Client {_lastId}", pipeStream); + } + } +} diff --git a/YAMDCC.IPC/IO/PipeStreamReader.cs b/YAMDCC.IPC/IO/PipeStreamReader.cs index b9325d0..7007913 100644 --- a/YAMDCC.IPC/IO/PipeStreamReader.cs +++ b/YAMDCC.IPC/IO/PipeStreamReader.cs @@ -10,52 +10,74 @@ namespace YAMDCC.IPC.IO { /// /// Wraps a object and reads from it. + /// + /// /// Deserializes binary data sent by a /// into a .NET CLR object specified by . - /// + /// /// - /// Reference type to deserialize data to + /// The reference type to deserialize data to. /// - public class PipeStreamReader where T : class + internal sealed class PipeStreamReader where T : class { /// - /// Gets the underlying PipeStream object. + /// Gets the underlying object. /// - public PipeStream BaseStream { get; private set; } + internal PipeStream BaseStream { get; private set; } /// /// Gets a value indicating whether the pipe is connected or not. /// - public bool IsConnected { get; private set; } + internal bool IsConnected { get; private set; } private readonly BinaryFormatter _binaryFormatter = new BinaryFormatter(); /// - /// Constructs a new object that - /// reads data from the given . + /// Constructs a new object + /// that reads data from the given . /// - /// Pipe to read from - public PipeStreamReader(PipeStream stream) + /// + /// The pipe stream to read from. + /// + internal PipeStreamReader(PipeStream stream) { BaseStream = stream; IsConnected = stream.IsConnected; } - #region Private stream readers - /// - /// Reads the length of the next message (in bytes) from the client. + /// Reads the next object from the pipe. /// + /// + /// This method blocks until an object is + /// sent or the pipe is disconnected. + /// /// - /// Number of bytes of data the client will be sending. + /// The next object read from the pipe, or + /// null if the pipe disconnected. /// - /// - /// The pipe is disconnected, waiting to connect, - /// or the handle has not been set. - /// - /// - /// Any I/O error occurred. - /// + /// + internal T ReadObject() + { + if (typeof(T) == typeof(string)) + { + const int bufferSize = 1024; + byte[] data = new byte[bufferSize]; + BaseStream.Read(data, 0, bufferSize); + string message = Encoding.Unicode.GetString(data).TrimEnd('\0'); + + return (message.Length > 0 ? message : null) as T; + } + int len = ReadLength(); + return len == 0 ? default : ReadObject(len); + } + + /// + /// Reads the length of the next message (in bytes) from the client. + /// + /// Number of bytes of data the client will be sending. + /// + /// private int ReadLength() { const int lensize = sizeof(int); @@ -71,52 +93,15 @@ private int ReadLength() : IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenbuf, 0)); } - /// - /// An object in the graph of type parameter - /// is not marked as serializable. - /// + /// private T ReadObject(int len) { byte[] data = new byte[len]; BaseStream.Read(data, 0, len); using (MemoryStream memoryStream = new MemoryStream(data)) { - return (T) _binaryFormatter.Deserialize(memoryStream); + return (T)_binaryFormatter.Deserialize(memoryStream); } } - - #endregion - - /// - /// Reads the next object from the pipe. This method blocks until an - /// object is sent or the pipe is disconnected. - /// - /// - /// The next object read from the pipe, or - /// null if the pipe disconnected. - /// - /// - /// An object in the graph of type parameter - /// is not marked as serializable. - /// - public T ReadObject() - { - if (typeof(T) == typeof(string)) - { - return (T) ReadString(); - } - int len = ReadLength(); - return len == 0 ? default : ReadObject(len); - } - - private object ReadString() - { - const int bufferSize = 1024; - byte[] data = new byte[bufferSize]; - BaseStream.Read(data, 0, bufferSize); - string message = Encoding.Unicode.GetString(data).TrimEnd('\0'); - - return message.Length > 0 ? message : null; - } } } diff --git a/YAMDCC.IPC/IO/PipeStreamWrapper.cs b/YAMDCC.IPC/IO/PipeStreamWrapper.cs index 6a2e5f2..9208e99 100644 --- a/YAMDCC.IPC/IO/PipeStreamWrapper.cs +++ b/YAMDCC.IPC/IO/PipeStreamWrapper.cs @@ -6,41 +6,44 @@ namespace YAMDCC.IPC.IO { /// - /// Wraps a object to read and write .NET CLR objects. + /// Wraps a object + /// to read and write .NET CLR objects. /// - /// - /// Reference type to read from and write to the pipe + /// + /// The reference type to read from and write to the pipe. /// - public class PipeStreamWrapper : PipeStreamWrapper - where TRdWr : class + internal sealed class PipeStreamWrapper : PipeStreamWrapper + where TReadWrite : class { /// - /// Constructs a new PipeStreamWrapper object that - /// reads from and writes to the given . + /// Constructs a new object + /// that reads from and writes to the given . /// /// - /// Stream to read from and write to + /// The pipe stream to read from and write to. /// - public PipeStreamWrapper(PipeStream stream) : base(stream) { } + public PipeStreamWrapper(PipeStream stream) + : base(stream) { } } /// - /// Wraps a object to read and write .NET CLR objects. + /// Wraps a object + /// to read and write .NET CLR objects. /// - /// - /// Reference type to read from the pipe + /// + /// The reference type to read from the pipe. /// - /// - /// Reference type to write to the pipe + /// + /// The reference type to write to the pipe. /// - public class PipeStreamWrapper - where TRd : class - where TWr : class + internal class PipeStreamWrapper + where TRead : class + where TWrite : class { /// - /// Gets the underlying PipeStream object. + /// Gets the underlying object. /// - public PipeStream BaseStream { get; private set; } + internal PipeStream BaseStream { get; private set; } /// /// Gets a value indicating whether the @@ -48,9 +51,9 @@ public class PipeStreamWrapper /// /// /// true if the - /// object is connected; otherwise, false. + /// object is connected, otherwise false. /// - public bool IsConnected => BaseStream.IsConnected && _reader.IsConnected; + internal bool IsConnected => BaseStream.IsConnected && _reader.IsConnected; /// /// Gets a value indicating whether the @@ -58,61 +61,69 @@ public class PipeStreamWrapper /// /// /// true if the stream supports read - /// operations; otherwise, false. + /// operations, otherwise false. /// - public bool CanRead => BaseStream.CanRead; + internal bool CanRead => BaseStream.CanRead; /// - /// Gets a value indicating whether the - /// current stream supports write operations. + /// Gets a value indicating whether the current + /// stream supports write operations. /// /// /// true if the stream supports write - /// operations; otherwise, false. + /// operation, otherwise false. /// - public bool CanWrite => BaseStream.CanWrite; + internal bool CanWrite => BaseStream.CanWrite; - private readonly PipeStreamReader _reader; - private readonly PipeStreamWriter _writer; + private readonly PipeStreamReader _reader; + private readonly PipeStreamWriter _writer; /// - /// Constructs a new PipeStreamWrapper object that reads - /// from and writes to the given . + /// Constructs a new + /// object that reads from and writes to the given + /// . /// - /// Stream to read from and write to - public PipeStreamWrapper(PipeStream stream) + /// + /// The stream to read from and write to. + /// + internal PipeStreamWrapper(PipeStream stream) { BaseStream = stream; - _reader = new PipeStreamReader(BaseStream); - _writer = new PipeStreamWriter(BaseStream); + _reader = new PipeStreamReader(BaseStream); + _writer = new PipeStreamWriter(BaseStream); } /// - /// Reads the next object from the pipe. This method blocks - /// until an object is sent or the pipe is disconnected. + /// Reads the next object from the pipe. /// + /// + /// This method blocks until an object + /// is sent or the pipe is disconnected. + /// /// /// The next object read from the pipe, or /// null if the pipe disconnected. /// - /// - /// An object in the graph of type parameter - /// is not marked as serializable. - /// - public TRd ReadObject() => _reader.ReadObject(); + /// + internal TRead ReadObject() + { + return _reader.ReadObject(); + } /// /// Writes an object to the pipe. - /// This method blocks until all data is sent. /// + /// + /// This method blocks until all data is sent. + /// /// - /// Object to write to the pipe + /// Tne object to write to the pipe. /// - /// - /// An object in the graph of type parameter - /// is not marked as serializable. - /// - public void WriteObject(TWr obj) => _writer.WriteObject(obj); + /// + internal void WriteObject(TWrite obj) + { + _writer.WriteObject(obj); + } /// /// Waits for the other end of the pipe to read all sent bytes. @@ -126,12 +137,19 @@ public PipeStreamWrapper(PipeStream stream) /// /// The pipe is broken or another I/O error occurred. /// - public void WaitForPipeDrain() => _writer.WaitForPipeDrain(); + internal void WaitForPipeDrain() + { + _writer.WaitForPipeDrain(); + } /// - /// Closes the current stream and releases any resources - /// (such as sockets and file handles) associated with the current stream. + /// Closes the current stream and releases any + /// resources (such as sockets and file handles) + /// associated with the current stream. /// - public void Close() => BaseStream.Close(); + internal void Close() + { + BaseStream.Close(); + } } } diff --git a/YAMDCC.IPC/IO/PipeStreamWriter.cs b/YAMDCC.IPC/IO/PipeStreamWriter.cs index 1b0b632..bd14fbb 100644 --- a/YAMDCC.IPC/IO/PipeStreamWriter.cs +++ b/YAMDCC.IPC/IO/PipeStreamWriter.cs @@ -10,70 +10,47 @@ namespace YAMDCC.IPC.IO { /// /// Wraps a object and writes to it. + /// + /// /// Serializes .NET CLR objects specified by /// into binary form and sends them over the named pipe for a /// to read and deserialize. - /// - /// Reference type to serialize - public class PipeStreamWriter where T : class + /// + /// + /// The reference type to serialize. + /// + internal sealed class PipeStreamWriter where T : class { /// - /// Gets the underlying PipeStream object. + /// Gets the underlying object. /// - public PipeStream BaseStream { get; private set; } + internal PipeStream BaseStream { get; private set; } private readonly BinaryFormatter _binaryFormatter = new BinaryFormatter(); /// - /// Constructs a new PipeStreamWriter object - /// that writes to given . + /// Constructs a new + /// object that writes to given . /// - /// Pipe to write to - public PipeStreamWriter(PipeStream stream) + /// + /// The named pipe to write to. + /// + internal PipeStreamWriter(PipeStream stream) { BaseStream = stream; } - #region Private stream writers - - /// - /// An object in the graph of type parameter - /// is not marked as serializable. - /// - private byte[] Serialize(T obj) - { - using (MemoryStream memoryStream = new MemoryStream()) - { - _binaryFormatter.Serialize(memoryStream, obj); - return memoryStream.ToArray(); - } - } - - private void WriteLength(int len) - { - byte[] lenbuf = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(len)); - BaseStream.Write(lenbuf, 0, lenbuf.Length); - } - - private void WriteObject(byte[] data) => - BaseStream.Write(data, 0, data.Length); - - private void Flush() => BaseStream.Flush(); - - #endregion - /// /// Writes an object to the pipe. - /// This method blocks until all data is sent. /// + /// + /// This method blocks until all data is sent. + /// /// - /// Object to write to the pipe + /// The object to write to the pipe. /// - /// - /// An object in the graph of type parameter - /// is not marked as serializable. - /// - public void WriteObject(T obj) + /// + internal void WriteObject(T obj) { byte[] data; if (typeof(T) == typeof(string)) @@ -85,22 +62,35 @@ public void WriteObject(T obj) data = Serialize(obj); WriteLength(data.Length); } - WriteObject(data); - Flush(); + BaseStream.Write(data, 0, data.Length); + BaseStream.Flush(); } /// /// Waits for the other end of the pipe to read all sent bytes. /// - /// - /// The pipe is closed. - /// - /// - /// The pipe does not support write operations. - /// - /// - /// The pipe is broken or another I/O error occurred. - /// - public void WaitForPipeDrain() => BaseStream.WaitForPipeDrain(); + /// + /// + /// + internal void WaitForPipeDrain() + { + BaseStream.WaitForPipeDrain(); + } + + /// + private byte[] Serialize(T obj) + { + using (MemoryStream memoryStream = new MemoryStream()) + { + _binaryFormatter.Serialize(memoryStream, obj); + return memoryStream.ToArray(); + } + } + + private void WriteLength(int len) + { + byte[] lenbuf = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(len)); + BaseStream.Write(lenbuf, 0, lenbuf.Length); + } } } diff --git a/YAMDCC.IPC/NamedPipeClient.cs b/YAMDCC.IPC/NamedPipeClient.cs index 140c7fe..866f68c 100644 --- a/YAMDCC.IPC/NamedPipeClient.cs +++ b/YAMDCC.IPC/NamedPipeClient.cs @@ -1,81 +1,98 @@ +using YAMDCC.IPC.IO; +using YAMDCC.IPC.Threading; using System; -using System.IO; using System.IO.Pipes; -using System.Runtime.InteropServices; using System.Threading; -using YAMDCC.IPC.IO; -using YAMDCC.IPC.Threading; namespace YAMDCC.IPC { /// /// Wraps a . /// - /// - /// Reference type to read from and write to the named pipe + /// + /// The reference type to read from and write to the named pipe. /// - public class NamedPipeClient : NamedPipeClient where TRdWr : class + public class NamedPipeClient : NamedPipeClient + where TReadWrite : class { /// - /// Constructs a new NamedPipeClient to connect to the - /// specified by - /// . + /// Constructs a new to + /// connect to the specified + /// by . /// - /// Name of the server's pipe + /// + /// The name of the named pipe server. + /// public NamedPipeClient(string pipeName) : base(pipeName) { } } /// /// Wraps a . /// - /// Reference type to read from the named pipe - /// Reference type to write to the named pipe - public class NamedPipeClient - where TRd : class - where TWr : class + /// + /// The reference type to read from the named pipe. + /// + /// + /// The reference type to write to the named pipe. + /// + public class NamedPipeClient : IDisposable + where TRead : class + where TWrite : class { /// - /// Gets or sets whether the client should attempt to reconnect when the pipe breaks - /// due to an error or the other end terminating the connection. - /// Default value is true. + /// Gets or sets whether the client should attempt to reconnect when + /// the pipe breaks due to an error or the other end terminating the + /// connection. /// - public bool AutoReconnect; + /// + /// The default value is true. + /// + public bool AutoReconnect { get; set; } = true; /// - /// Gets or sets how long the client waits between a reconnection attempt. - /// Default value is 0. + /// Gets or sets how long the client + /// waits between a reconnection attempt. /// - public int AutoReconnectDelay; + /// + /// The default value is 0. + /// + public int AutoReconnectDelay { get; set; } /// /// Invoked whenever a message is received from the server. /// - public event ConnectionMessageEventHandler ServerMessage; + public event EventHandler> ServerMessage; /// - /// Invoked when the client disconnects from the server (e.g., the pipe is closed or broken). + /// Invoked when the client disconnects from the server + /// (e.g. when the pipe is closed or broken). /// - public event ConnectionEventHandler Disconnected; + public event EventHandler> Disconnected; /// - /// Invoked whenever an exception is thrown during a read or write operation on the named pipe. + /// Invoked whenever an exception is thrown during + /// a read or write operation on the named pipe. /// - public event PipeExceptionEventHandler Error; + public event EventHandler> Error; private readonly string _pipeName; - private NamedPipeConnection _connection; + private NamedPipeConnection _connection; private readonly AutoResetEvent _connected = new AutoResetEvent(false); private readonly AutoResetEvent _disconnected = new AutoResetEvent(false); private volatile bool _closedExplicitly; + private bool _disposed; + /// - /// Constructs a new to connect to the - /// specified by - /// . + /// Constructs a new to + /// connect to the + /// specified by . /// - /// Name of the server's pipe + /// + /// The name of the named pipe server. + /// public NamedPipeClient(string pipeName) { _pipeName = pipeName; @@ -84,22 +101,20 @@ public NamedPipeClient(string pipeName) /// /// Connects to the named pipe server asynchronously. - /// This method returns immediately, possibly before the connection has been established. /// + /// + /// This method returns immediately, possibly before the connection + /// has been established. Use to + /// wait until the connection to the server is established. + /// public void Start() { _closedExplicitly = false; Worker worker = new Worker(); - worker.Error += OnError; + worker.Error += WorkerOnError; worker.DoWork(ListenSync); } - /// - /// Sends a message to the server over a named pipe. - /// - /// Message to send to the server. - public void PushMessage(TWr message) => _connection?.PushMessage(message); - /// /// Closes the named pipe. /// @@ -109,65 +124,110 @@ public void Stop() _connection?.Close(); } + /// + /// Sends a message to the server over a named pipe. + /// + /// + /// The message to send to the server. + /// + public void PushMessage(TWrite message) + { + _connection?.PushMessage(message); + } + #region Wait for connection/disconnection + /// - /// Blocks the current thread until a connection is established. + /// Blocks the current thread until a connection + /// to the named pipe server is established. /// - public void WaitForConnection() => - _connected.WaitOne(); + public bool WaitForConnection() + { + return _connected.WaitOne(); + } /// - /// Blocks the current thread until a connection is established, - /// using an integer to specify the timeout in milliseconds. + /// Blocks the current thread until a connection to the + /// named pipe server is established, waiting until at + /// most before returning. /// - /// - /// The number of milliseconds to wait, or - /// to wait indefinitely. + /// + /// The timeout, in milliseconds, to wait for the server connection. /// - public void WaitForConnection(int millisecondsTimeout) => - _connected.WaitOne(millisecondsTimeout); + /// + /// true if the server connection was established + /// before the timeout, otherwise false. + /// + public bool WaitForConnection(int timeout) + { + return _connected.WaitOne(timeout); + } /// - /// Blocks the current thread until a connection is established, - /// using a to specify the timeout in milliseconds. + /// Blocks the current thread until a connection to the + /// named pipe server is established, waiting until at + /// most before returning. /// /// - /// A that represents the number of milliseconds to wait. + /// A representing the time + /// (in milliseconds) to wait for the server connection. /// - public void WaitForConnection(TimeSpan timeout) => - _connected.WaitOne(timeout); + /// + /// true if the server connection was established + /// before the timeout, otherwise false. + /// + public bool WaitForConnection(TimeSpan timeout) + { + return _connected.WaitOne(timeout); + } /// - /// Blocks the current thread until the client disconnects. + /// Blocks the current thread until the client + /// disconnects from the named pipe server. /// - public void WaitForDisconnection() => - _disconnected.WaitOne(); + public bool WaitForDisconnection() + { + return _disconnected.WaitOne(); + } /// - /// Blocks the current thread until the client disconnects, - /// using an integer to specify the timeout in milliseconds. + /// Blocks the current thread until the client disconnects + /// from the named pipe server, waiting until at most + /// before returning. /// - /// - /// The number of milliseconds to wait, or - /// to wait indefinitely. + /// + /// The timeout, in milliseconds, to wait for the server to disconnect. /// - public void WaitForDisconnection(int millisecondsTimeout) => - _disconnected.WaitOne(millisecondsTimeout); + /// + /// true if the client disconnected + /// before the timeout, otherwise false. + /// + public bool WaitForDisconnection(int timeout) + { + return _disconnected.WaitOne(timeout); + } /// - /// Blocks the current thread until the client disconnects, - /// using a to specify the timeout in milliseconds. + /// Blocks the current thread until the client disconnects + /// from the named pipe server, waiting until at most + /// before returning. /// /// - /// A that represents the number of milliseconds to wait. + /// A representing the time + /// (in milliseconds) to wait for the server to disconnect. /// - public void WaitForDisconnection(TimeSpan timeout) => - _disconnected.WaitOne(timeout); + /// + /// true if the client disconnected + /// before the timeout, otherwise false. + /// + public bool WaitForDisconnection(TimeSpan timeout) + { + return _disconnected.WaitOne(timeout); + } #endregion #region Private methods - private void ListenSync() { // Get the name of the data pipe that should be used from now on by this NamedPipeClient @@ -179,7 +239,7 @@ private void ListenSync() NamedPipeClientStream dataPipe = PipeClientFactory.CreateAndConnectPipe(dataPipeName); // Create a Connection object for the data pipe - _connection = ConnectionFactory.CreateConnection(dataPipe); + _connection = ConnectionFactory.CreateConnection(dataPipe); _connection.Disconnected += OnDisconnected; _connection.ReceiveMessage += OnReceiveMessage; _connection.Error += ConnectionOnError; @@ -188,10 +248,9 @@ private void ListenSync() _connected.Set(); } - private void OnDisconnected(NamedPipeConnection connection) + private void OnDisconnected(object sender, PipeConnectionEventArgs e) { - if (Disconnected != null) - Disconnected(connection); + Disconnected?.Invoke(sender, e); _disconnected.Set(); @@ -203,81 +262,49 @@ private void OnDisconnected(NamedPipeConnection connection) } } - private void OnReceiveMessage(NamedPipeConnection connection, TRd message) + private void OnReceiveMessage(object sender, PipeMessageEventArgs e) { - if (ServerMessage != null) - ServerMessage(connection, message); + ServerMessage?.Invoke(sender, e); } /// - /// Invoked on the UI thread. + /// Invoked on the UI thread. /// - private void ConnectionOnError(NamedPipeConnection connection, Exception exception) + private void ConnectionOnError(object sender, PipeErrorEventArgs e) { - OnError(exception); + Error?.Invoke(sender, e); } + /// - /// Invoked on the UI thread. + /// Invoked on the UI thread. /// - /// - private void OnError(Exception exception) + private void WorkerOnError(object sender, WorkerErrorEventArgs e) { - if (Error != null) - Error(exception); + Error?.Invoke(sender, new PipeErrorEventArgs(_connection, e.Exception)); } - #endregion - } - internal static class PipeClientFactory - { - [return: MarshalAs(UnmanagedType.Bool)] - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern bool WaitNamedPipe(string name, int timeout); - - public static bool NamedPipeExists(string pipeName) + public void Dispose() { - try - { - bool exists = WaitNamedPipe(pipeName, -1); - if (!exists) - { - int error = Marshal.GetLastWin32Error(); - if (error == 0 || error == 2) - { - return false; - } - } - return true; - } - catch (Exception) - { - return false; - } + Dispose(true); + GC.SuppressFinalize(this); } - public static PipeStreamWrapper Connect(string pipeName) - where TRd : class - where TWr : class + private void Dispose(bool disposing) { - return new PipeStreamWrapper(CreateAndConnectPipe(pipeName)); - } + if (_disposed) + { + return; + } - public static NamedPipeClientStream CreateAndConnectPipe(string pipeName, int timeout = 10) - { - string normalizedPath = Path.GetFullPath(string.Format(@"\\.\pipe\{0}", pipeName)); - while (!NamedPipeExists(normalizedPath)) + if (disposing) { - Thread.Sleep(timeout); + _connected.Dispose(); + _disconnected.Dispose(); } - NamedPipeClientStream pipe = CreatePipe(pipeName); - pipe.Connect(1000); - return pipe; - } - private static NamedPipeClientStream CreatePipe(string pipeName) => - new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, - PipeOptions.Asynchronous | PipeOptions.WriteThrough); + _disposed = true; + } } } diff --git a/YAMDCC.IPC/NamedPipeConnection.cs b/YAMDCC.IPC/NamedPipeConnection.cs index f5c707a..cfa7bed 100644 --- a/YAMDCC.IPC/NamedPipeConnection.cs +++ b/YAMDCC.IPC/NamedPipeConnection.cs @@ -1,37 +1,41 @@ +using YAMDCC.IPC.IO; +using YAMDCC.IPC.Threading; using System; using System.Collections.Generic; using System.IO.Pipes; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Threading; -using YAMDCC.IPC.IO; -using YAMDCC.IPC.Threading; namespace YAMDCC.IPC { /// /// Represents a connection between a named pipe client and server. /// - /// Reference type to read from the named pipe - /// Reference type to write to the named pipe - public sealed class NamedPipeConnection - where TRd : class - where TWr : class + /// + /// The reference type to read from the named pipe. + /// + /// + /// The reference type to write to the named pipe. + /// + public class NamedPipeConnection : IDisposable + where TRead : class + where TWrite : class { /// /// Gets the connection's unique identifier. /// - public readonly int ID; + public int ID { get; } /// /// Gets the connection's name. /// - public readonly string Name; + public string Name { get; } /// /// Gets the connection's handle. /// - public readonly SafeHandle Handle; + public SafeHandle Handle => _streamWrapper.BaseStream.SafePipeHandle; /// /// Gets a value indicating whether the pipe is connected or not. @@ -41,38 +45,58 @@ public sealed class NamedPipeConnection /// /// Invoked when the named pipe connection terminates. /// - public event ConnectionEventHandler Disconnected; + public event EventHandler> Disconnected; /// /// Invoked whenever a message is received from the other end of the pipe. /// - public event ConnectionMessageEventHandler ReceiveMessage; + public event EventHandler> ReceiveMessage; /// /// Invoked when an exception is thrown during any read/write operation over the named pipe. /// - public event ConnectionExceptionEventHandler Error; + public event EventHandler> Error; - private readonly PipeStreamWrapper _streamWrapper; + private readonly PipeStreamWrapper _streamWrapper; private readonly AutoResetEvent _writeSignal = new AutoResetEvent(false); - private readonly Queue _writeQueue = new Queue(); + private readonly Queue _writeQueue = new Queue(); private bool _notifiedSucceeded; + private bool _disposed; + internal NamedPipeConnection(int id, string name, PipeStream serverStream) { ID = id; Name = name; - Handle = serverStream.SafePipeHandle; - _streamWrapper = new PipeStreamWrapper(serverStream); + _streamWrapper = new PipeStreamWrapper(serverStream); } /// - /// Begins reading from and writing to the named pipe on a background thread. - /// This method returns immediately. + /// Adds the specified message to the write queue. /// - public void Open() + /// + /// The message will be written to the named pipe by the + /// background thread at the next available opportunity. + /// + /// + /// The message to write to the named pipe. + /// + public void PushMessage(TWrite message) + { + _writeQueue.Enqueue(message); + _writeSignal.Set(); + } + + /// + /// Begins reading from and writing to the + /// named pipe on a background thread. + /// + /// + /// This method returns immediately. + /// + internal void Open() { Worker readWorker = new Worker(); readWorker.Succeeded += OnSucceeded; @@ -86,21 +110,13 @@ public void Open() } /// - /// Adds the specified to the write queue. - /// The message will be written to the named pipe by the background thread - /// at the next available opportunity. + /// Closes the named pipe connection and + /// underlying . /// - /// - public void PushMessage(TWr message) - { - _writeQueue.Enqueue(message); - _writeSignal.Set(); - } - - /// - /// Closes the named pipe connection and underlying PipeStream. - /// - public void Close() + /// + /// Invoked on the background thread. + /// + internal void Close() { _streamWrapper.Close(); _writeSignal.Set(); @@ -109,48 +125,52 @@ public void Close() /// /// Invoked on the UI thread. /// - private void OnSucceeded() + private void OnSucceeded(object sender, EventArgs e) { // Only notify observers once if (_notifiedSucceeded) - { return; - } _notifiedSucceeded = true; - Disconnected?.Invoke(this); + PipeConnectionEventArgs e2 = new PipeConnectionEventArgs(this); + Disconnected?.Invoke(sender, e2); } /// /// Invoked on the UI thread. /// /// - private void OnError(Exception exception) => - Error?.Invoke(this, exception); + private void OnError(object sender, WorkerErrorEventArgs e) + { + Error?.Invoke(sender, new PipeErrorEventArgs(this, e.Exception)); + } /// /// Invoked on the background thread. /// - /// An object in the graph of type parameter is not marked as serializable. + /// private void ReadPipe() { while (IsConnected && _streamWrapper.CanRead) { - TRd obj = _streamWrapper.ReadObject(); + TRead obj = _streamWrapper.ReadObject(); if (obj == null) { Close(); return; } - ReceiveMessage?.Invoke(this, obj); + PipeMessageEventArgs e = + new PipeMessageEventArgs(this, obj); + + ReceiveMessage?.Invoke(this, e); } } /// /// Invoked on the background thread. /// - /// An object in the graph of type parameter is not marked as serializable. + /// private void WritePipe() { while (IsConnected && _streamWrapper.CanWrite) @@ -163,49 +183,26 @@ private void WritePipe() } } } - } - internal static class ConnectionFactory - { - private static int _lastId; - - public static NamedPipeConnection CreateConnection(PipeStream pipeStream) - where TRd : class - where TWr : class + public void Dispose() { - return new NamedPipeConnection(++_lastId, "Client " + _lastId, pipeStream); + Dispose(true); + GC.SuppressFinalize(this); } - } - /// - /// Handles new connections. - /// - /// The newly established connection - /// Reference type - /// Reference type - public delegate void ConnectionEventHandler(NamedPipeConnection connection) - where TRd : class - where TWr : class; + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } - /// - /// Handles messages received from a named pipe. - /// - /// Reference type - /// Reference type - /// Connection that received the message - /// Message sent by the other end of the pipe - public delegate void ConnectionMessageEventHandler(NamedPipeConnection connection, TRd message) - where TRd : class - where TWr : class; + if (disposing) + { + _writeSignal.Dispose(); + } - /// - /// Handles exceptions thrown during read/write operations. - /// - /// Reference type - /// Reference type - /// Connection that threw the exception - /// The exception that was thrown - public delegate void ConnectionExceptionEventHandler(NamedPipeConnection connection, Exception exception) - where TRd : class - where TWr : class; + _disposed = true; + } + } } diff --git a/YAMDCC.IPC/NamedPipeServer.cs b/YAMDCC.IPC/NamedPipeServer.cs index d9d5a92..e19977a 100644 --- a/YAMDCC.IPC/NamedPipeServer.cs +++ b/YAMDCC.IPC/NamedPipeServer.cs @@ -1,9 +1,9 @@ +using YAMDCC.IPC.IO; +using YAMDCC.IPC.Threading; using System; using System.Collections.Generic; using System.IO.Pipes; using System.Linq; -using YAMDCC.IPC.IO; -using YAMDCC.IPC.Threading; namespace YAMDCC.IPC { @@ -11,125 +11,134 @@ namespace YAMDCC.IPC /// Wraps a and provides /// multiple simultaneous client connection handling. /// - /// - /// Reference type to read from and write to the named pipe + /// + /// The reference type to read from and write to the named pipe. /// - public class NamedPipeServer : Server - where TRdWr : class + public class NamedPipeServer : NamedPipeServer + where TReadWrite : class { /// - /// Constructs a new object - /// that listens for client connections on the given . + /// Constructs a new + /// object that listens for client connections on the given + /// . /// - /// - /// The name of the pipe to listen on. - /// - public NamedPipeServer(string pipeName) : base(pipeName) { } + /// + public NamedPipeServer(string pipeName) + : base(pipeName) { } /// - /// Constructs a new object - /// that listens for client connections on the given , - /// with the given . + /// Constructs a new + /// object that listens for client connections on the given + /// . /// - /// - /// The name of the pipe to listen on. - /// - /// - /// The size of input and output buffer. - /// - /// - /// The object that determines the access control - /// and audit security for the pipe. - /// - public NamedPipeServer(string pipeName, int bufferSize, PipeSecurity security) - : base(pipeName, bufferSize, security) { } + /// + public NamedPipeServer(string pipeName, PipeSecurity security, int bufferSize = 0) + : base(pipeName, security, bufferSize) { } } /// /// Wraps a and provides /// multiple simultaneous client connection handling. /// - /// Reference type to read from the named pipe - /// Reference type to write to the named pipe - public class Server - where TRd : class - where TWr : class + /// + /// The reference type to read from the named pipe. + /// + /// + /// The reference type to write to the named pipe. + /// + public class NamedPipeServer + where TRead : class + where TWrite : class { /// /// Invoked whenever a client connects to the server. /// - public event ConnectionEventHandler ClientConnected; + public event EventHandler> ClientConnected; /// /// Invoked whenever a client disconnects from the server. /// - public event ConnectionEventHandler ClientDisconnected; + public event EventHandler> ClientDisconnected; /// /// Invoked whenever a client sends a message to the server. /// - public event ConnectionMessageEventHandler ClientMessage; + public event EventHandler> ClientMessage; /// - /// Invoked whenever an exception is thrown during a read or write operation. + /// Invoked whenever an exception is thrown + /// during a read or write operation. /// - public event PipeExceptionEventHandler Error; + public event EventHandler> Error; private readonly string _pipeName; private readonly int _bufferSize; private readonly PipeSecurity _security; - private readonly List> _connections = new List>(); + private readonly List> _connections = new List>(); private int _nextPipeId; private volatile bool _shouldKeepRunning; /// - /// Constructs a new NamedPipeServer object that listens for client connections on the given . + /// Constructs a new + /// object that listens for client connections on the given + /// . /// - /// Name of the pipe to listen on - public Server(string pipeName) + /// + /// The name of the pipe to listen on. + /// + public NamedPipeServer(string pipeName) { _pipeName = pipeName; } - /// - /// Constructs a new NamedPipeServer object that listens - /// for client connections on the given . - /// - /// Name of the pipe to listen on - /// Size of input and output buffer - /// And object that determine the access control and audit security for the pipe - public Server(string pipeName, int bufferSize, PipeSecurity security) + /// + /// An object that determine the access control + /// and audit security for the pipe. + /// + /// + /// The size of the input and output buffer. + /// Use 0 for the default buffer size. + /// + /// + public NamedPipeServer(string pipeName, PipeSecurity security, int bufferSize = 0) { _pipeName = pipeName; - _bufferSize = bufferSize; _security = security; + _bufferSize = bufferSize; } /// - /// Begins listening for client connections in a separate background thread. - /// This method returns immediately. + /// Begins listening for client connections + /// in a separate background thread. /// + /// + /// This method returns immediately. + /// public void Start() { _shouldKeepRunning = true; Worker worker = new Worker(); - worker.Error += OnError; + worker.Error += WorkerOnError; worker.DoWork(ListenSync); } /// /// Sends a message to all connected clients asynchronously. - /// This method returns immediately, possibly before the - /// message has been sent to all clients. /// - /// - public void PushMessage(TWr message) + /// + /// This method returns immediately, possibly before + /// the message has been sent to all clients. + /// + /// + /// The message to send to the clients. + /// + public void PushMessage(TWrite message) { lock (_connections) { - foreach (NamedPipeConnection client in _connections) + foreach (NamedPipeConnection client in _connections) { client.PushMessage(message); } @@ -137,17 +146,21 @@ public void PushMessage(TWr message) } /// - /// Sends a message to a specific client asynchronously. - /// This method returns immediately, possibly before the message has been sent to all clients. + /// Sends a message to a specified client asynchronously. /// - /// - /// Specific client ID to send to. - public void PushMessage(TWr message, int targetId) + /// + /// The message to send to the client. + /// + /// + /// The client ID to send the message to. + /// + /// + public void PushMessage(TWrite message, int targetId) { lock (_connections) { // Can we speed this up with Linq or does that add overhead? - foreach (NamedPipeConnection client in _connections) + foreach (NamedPipeConnection client in _connections) { if (client.ID == targetId) { @@ -159,17 +172,27 @@ public void PushMessage(TWr message, int targetId) } /// - /// Sends a message to a specific clients asynchronously. - /// This method returns immediately, possibly before the message has been sent to all clients. + /// Sends a message to the specified clients asynchronously. /// - /// - /// A list of client ID's to send to. - public void PushMessage(TWr message, List targetIds) + /// + /// An array of client IDs to send the message to. + /// + /// + public void PushMessage(TWrite message, int[] targetIds) + { + PushMessage(message, targetIds.ToList()); + } + + /// + /// A list of client IDs to send the message to. + /// + /// + public void PushMessage(TWrite message, List targetIds) { lock (_connections) { // Can we speed this up with Linq or does that add overhead? - foreach (NamedPipeConnection client in _connections) + foreach (NamedPipeConnection client in _connections) { if (targetIds.Contains(client.ID)) { @@ -179,31 +202,18 @@ public void PushMessage(TWr message, List targetIds) } } - /// - /// Sends a message to a specific clients asynchronously. - /// This method returns immediately, possibly before the message has been sent to all clients. - /// - /// - /// An array of client ID's to send to. - public void PushMessage(TWr message, int[] targetIds) - { - PushMessage(message, targetIds.ToList()); - } - - /// - /// Sends a message to a specific client asynchronously. - /// This method returns immediately, possibly before the message has been sent to all clients. - /// - /// - /// Specific client name to send to. - public void PushMessage(TWr message, string targetName) + /// + /// The client name to send the message to. + /// + /// + public void PushMessage(TWrite message, string targetName) { lock (_connections) { // Can we speed this up with Linq or does that add overhead? - foreach (NamedPipeConnection client in _connections) + foreach (NamedPipeConnection client in _connections) { - if (client.Name.Equals(targetName)) + if (client.Name == targetName) { client.PushMessage(message); break; @@ -212,17 +222,15 @@ public void PushMessage(TWr message, string targetName) } } - /// - /// Sends a message to a specific client asynchronously. - /// This method returns immediately, possibly before the message has been sent to all clients. - /// - /// - /// A list of client names to send to. - public void PushMessage(TWr message, List targetNames) + /// + /// A list of client names to send the message to. + /// + /// + public void PushMessage(TWrite message, List targetNames) { lock (_connections) { - foreach (NamedPipeConnection client in _connections) + foreach (NamedPipeConnection client in _connections) { if (targetNames.Contains(client.Name)) { @@ -241,7 +249,7 @@ public void Stop() lock (_connections) { - foreach (NamedPipeConnection client in _connections.ToArray()) + foreach (NamedPipeConnection client in _connections.ToArray()) { client.Close(); } @@ -249,7 +257,7 @@ public void Stop() // If background thread is still listening for a client to connect, // initiate a dummy connection that will allow the thread to exit. - NamedPipeClient dummyClient = new NamedPipeClient(_pipeName); + NamedPipeClient dummyClient = new NamedPipeClient(_pipeName); dummyClient.Start(); dummyClient.WaitForConnection(TimeSpan.FromSeconds(2)); dummyClient.Stop(); @@ -270,7 +278,7 @@ private void WaitForConnection() { NamedPipeServerStream handshakePipe = null; NamedPipeServerStream dataPipe = null; - NamedPipeConnection connection = null; + NamedPipeConnection connection = null; string connectionPipeName = GetNextConnectionPipeName(); @@ -281,39 +289,42 @@ private void WaitForConnection() // Send the client the name of the data pipe to use handshakePipe = CreateAndConnectPipe(); - PipeStreamWrapper handshakeWrapper - = new PipeStreamWrapper(handshakePipe); + PipeStreamWrapper handshakeWrapper = new PipeStreamWrapper(handshakePipe); handshakeWrapper.WriteObject(connectionPipeName); handshakeWrapper.WaitForPipeDrain(); handshakeWrapper.Close(); + // Wait for the client to connect to the data pipe dataPipe.WaitForConnection(); // Add the client's connection to the list of connections - connection = ConnectionFactory.CreateConnection(dataPipe); + connection = ConnectionFactory.CreateConnection(dataPipe); connection.ReceiveMessage += ClientOnReceiveMessage; connection.Disconnected += ClientOnDisconnected; connection.Error += ConnectionOnError; connection.Open(); - lock (_connections) - { - _connections.Add(connection); - } + lock (_connections) { _connections.Add(connection); } + + PipeConnectionEventArgs e = + new PipeConnectionEventArgs(connection); - ClientOnConnected(connection); + ClientOnConnected(this, e); } // Catch the IOException that is raised if the pipe is broken or disconnected. - catch (Exception e) + catch (Exception ex) { - Console.Error.WriteLine("Named pipe is broken or disconnected: {0}", e); + Console.Error.WriteLine($"Named pipe is broken or disconnected: {ex}"); Cleanup(handshakePipe); Cleanup(dataPipe); - ClientOnDisconnected(connection); + PipeConnectionEventArgs e = + new PipeConnectionEventArgs(connection); + + ClientOnDisconnected(this, e); } } @@ -331,44 +342,58 @@ private NamedPipeServerStream CreatePipe(string connectionPipeName) : PipeServerFactory.CreatePipe(connectionPipeName, _bufferSize, _security); } - private void ClientOnConnected(NamedPipeConnection connection) => - ClientConnected?.Invoke(connection); + private void ClientOnConnected(object sender, PipeConnectionEventArgs e) + { + ClientConnected?.Invoke(sender, e); + } - private void ClientOnReceiveMessage(NamedPipeConnection connection, TRd message) => - ClientMessage?.Invoke(connection, message); + private void ClientOnReceiveMessage(object sender, PipeMessageEventArgs e) + { + ClientMessage?.Invoke(sender, e); + } - private void ClientOnDisconnected(NamedPipeConnection connection) + private void ClientOnDisconnected(object sender, PipeConnectionEventArgs e) { - if (connection == null) return; + if (e.Connection == null) + { + return; + } lock (_connections) { - _connections.Remove(connection); + _connections.Remove(e.Connection); } - ClientDisconnected?.Invoke(connection); + ClientDisconnected?.Invoke(sender, e); } /// /// Invoked on the UI thread. /// - private void ConnectionOnError(NamedPipeConnection connection, Exception exception) => - OnError(exception); + private void ConnectionOnError(object sender, PipeErrorEventArgs e) + { + Error?.Invoke(sender, e); + } /// /// Invoked on the UI thread. /// /// - private void OnError(Exception exception) => - Error?.Invoke(exception); + private void WorkerOnError(object sender, WorkerErrorEventArgs e) + { + PipeErrorEventArgs e2 = + new PipeErrorEventArgs(null, e.Exception); + Error?.Invoke(sender, e2); + } - private string GetNextConnectionPipeName() => - $"{_pipeName}_{++_nextPipeId}"; + private string GetNextConnectionPipeName() + { + return $"{_pipeName}_{++_nextPipeId}"; + } private static void Cleanup(NamedPipeServerStream pipe) { if (pipe == null) return; - using (NamedPipeServerStream x = pipe) { x.Close(); diff --git a/YAMDCC.IPC/PipeClientFactory.cs b/YAMDCC.IPC/PipeClientFactory.cs new file mode 100644 index 0000000..4b8634f --- /dev/null +++ b/YAMDCC.IPC/PipeClientFactory.cs @@ -0,0 +1,60 @@ +using YAMDCC.IPC.IO; +using System; +using System.IO; +using System.IO.Pipes; +using System.Runtime.InteropServices; +using System.Threading; + +namespace YAMDCC.IPC +{ + internal static class PipeClientFactory + { + internal static PipeStreamWrapper Connect(string pipeName) + where TRead : class + where TWrite : class + { + return new PipeStreamWrapper(CreateAndConnectPipe(pipeName)); + } + + internal static NamedPipeClientStream CreateAndConnectPipe(string pipeName, int timeout = 10) + { + string normalizedPath = Path.GetFullPath($"\\\\.\\pipe\\{pipeName}"); + while (!NamedPipeExists(normalizedPath)) + { + Thread.Sleep(timeout); + } + NamedPipeClientStream pipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough); + pipe.Connect(1000); + return pipe; + } + + private static bool NamedPipeExists(string pipeName) + { + try + { + bool exists = WaitNamedPipe(pipeName, -1); + if (!exists) + { + int error = Marshal.GetLastWin32Error(); + if (error == 0 || error == 2) + { + return false; + } + } + return true; + } + catch (Exception) + { + return false; + } + } + + [DllImport("kernel32.dll", + CharSet = CharSet.Unicode, + EntryPoint = "WaitNamedPipeW", + SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool WaitNamedPipe(string name, int timeout); + } +} diff --git a/YAMDCC.IPC/PipeConnectionEventArgs.cs b/YAMDCC.IPC/PipeConnectionEventArgs.cs new file mode 100644 index 0000000..12df238 --- /dev/null +++ b/YAMDCC.IPC/PipeConnectionEventArgs.cs @@ -0,0 +1,38 @@ +using System; + +namespace YAMDCC.IPC +{ + /// + /// Provides data for the + /// , + /// , and + /// events. + /// + /// + /// The reference type used when reading from the named pipe. + /// + /// + /// The reference type used when writing to the named pipe. + /// + public class PipeConnectionEventArgs : EventArgs + where TRead : class + where TWrite : class + { + /// + /// The connection that caused the (dis)connection event. + /// + public NamedPipeConnection Connection { get; } + + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The connection that should be associated with the event. + /// + internal PipeConnectionEventArgs(NamedPipeConnection connection) + { + Connection = connection; + } + } +} diff --git a/YAMDCC.IPC/PipeErrorEventArgs.cs b/YAMDCC.IPC/PipeErrorEventArgs.cs new file mode 100644 index 0000000..701eb8b --- /dev/null +++ b/YAMDCC.IPC/PipeErrorEventArgs.cs @@ -0,0 +1,43 @@ +using System; + +namespace YAMDCC.IPC +{ + /// + /// Provides data for the + /// and + /// events. + /// + public class PipeErrorEventArgs : EventArgs + where TRead : class + where TWrite : class + { + public NamedPipeConnection Connection { get; } + + /// + /// The that caused the error. + /// + public Exception Exception { get; } + + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The connection that caused the error. + /// + /// The only time this should be null is if the error was caused + /// by . + /// + /// + /// + /// The exception that caused the error. + /// + internal PipeErrorEventArgs( + NamedPipeConnection connection, + Exception exception) + { + Connection = connection; + Exception = exception; + } + } +} diff --git a/YAMDCC.IPC/PipeExceptionEventHandler.cs b/YAMDCC.IPC/PipeExceptionEventHandler.cs deleted file mode 100644 index dfe91a3..0000000 --- a/YAMDCC.IPC/PipeExceptionEventHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace YAMDCC.IPC -{ - /// - /// Handles exceptions thrown during a read or write operation on a named pipe. - /// - /// Exception that was thrown - public delegate void PipeExceptionEventHandler(Exception exception); -} \ No newline at end of file diff --git a/YAMDCC.IPC/PipeMessageEventArgs.cs b/YAMDCC.IPC/PipeMessageEventArgs.cs new file mode 100644 index 0000000..9715647 --- /dev/null +++ b/YAMDCC.IPC/PipeMessageEventArgs.cs @@ -0,0 +1,39 @@ +namespace YAMDCC.IPC +{ + /// + /// Provides data for the + /// and + /// events. + /// + /// + /// The reference type used when reading from the named pipe. + /// + /// + /// The reference type used when writing to the named pipe. + /// + public class PipeMessageEventArgs : PipeConnectionEventArgs + where TRead : class + where TWrite : class + { + /// + /// The message sent by the other end of the pipe. + /// + public TRead Message { get; } + + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The connection that sent the message. + /// + /// + /// The message sent by the other end of the pipe. + /// + internal PipeMessageEventArgs(NamedPipeConnection connection, TRead message) + : base(connection) + { + Message = message; + } + } +} diff --git a/YAMDCC.IPC/PipeServerFactory.cs b/YAMDCC.IPC/PipeServerFactory.cs index a49888d..cbda749 100644 --- a/YAMDCC.IPC/PipeServerFactory.cs +++ b/YAMDCC.IPC/PipeServerFactory.cs @@ -1,10 +1,10 @@ -using System.IO.Pipes; +using System.IO.Pipes; namespace YAMDCC.IPC { internal static class PipeServerFactory { - public static NamedPipeServerStream CreateAndConnectPipe(string pipeName) + internal static NamedPipeServerStream CreateAndConnectPipe(string pipeName) { NamedPipeServerStream pipe = CreatePipe(pipeName); pipe.WaitForConnection(); @@ -12,13 +12,13 @@ public static NamedPipeServerStream CreateAndConnectPipe(string pipeName) return pipe; } - public static NamedPipeServerStream CreatePipe(string pipeName) + internal static NamedPipeServerStream CreatePipe(string pipeName) { return new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous); } - public static NamedPipeServerStream CreateAndConnectPipe(string pipeName, int bufferSize, PipeSecurity security) + internal static NamedPipeServerStream CreateAndConnectPipe(string pipeName, int bufferSize, PipeSecurity security) { NamedPipeServerStream pipe = CreatePipe(pipeName, bufferSize, security); pipe.WaitForConnection(); @@ -26,7 +26,7 @@ public static NamedPipeServerStream CreateAndConnectPipe(string pipeName, int bu return pipe; } - public static NamedPipeServerStream CreatePipe(string pipeName, int bufferSize, PipeSecurity security) + internal static NamedPipeServerStream CreatePipe(string pipeName, int bufferSize, PipeSecurity security) { return new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, bufferSize, bufferSize, security); diff --git a/YAMDCC.IPC/ServiceCommand.cs b/YAMDCC.IPC/ServiceCommand.cs index f57a1b6..c2eb2e2 100644 --- a/YAMDCC.IPC/ServiceCommand.cs +++ b/YAMDCC.IPC/ServiceCommand.cs @@ -115,14 +115,14 @@ public class ServiceCommand /// /// The to send to the service. /// - public Command Command; + public Command Command { get; set; } /// /// The argument(s) to send to the service with the command. /// The number of parameters for a service command vary depending on the /// specific command sent to the service. /// - public string Arguments; + public string Arguments { get; set; } public ServiceCommand(Command command, string args) { diff --git a/YAMDCC.IPC/ServiceResponse.cs b/YAMDCC.IPC/ServiceResponse.cs index c2eb34f..1655a70 100644 --- a/YAMDCC.IPC/ServiceResponse.cs +++ b/YAMDCC.IPC/ServiceResponse.cs @@ -46,12 +46,12 @@ public class ServiceResponse /// /// The to send to the service. /// - public Response Response; + public Response Response { get; set; } /// /// The value associated with the . /// - public string Value; + public string Value { get; set; } /// /// Initialises a new instance of the diff --git a/YAMDCC.IPC/Threading/Worker.cs b/YAMDCC.IPC/Threading/Worker.cs index 01036cd..16b3010 100644 --- a/YAMDCC.IPC/Threading/Worker.cs +++ b/YAMDCC.IPC/Threading/Worker.cs @@ -13,19 +13,22 @@ internal sealed class Worker ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Default; - public event WorkerSucceededEventHandler Succeeded; - public event WorkerExceptionEventHandler Error; + internal event EventHandler Succeeded; + internal event EventHandler Error; - public Worker() : this(CurrentTaskScheduler) { } + internal Worker() : this(CurrentTaskScheduler) { } - public Worker(TaskScheduler callbackThread) => + internal Worker(TaskScheduler callbackThread) + { _callbackThread = callbackThread; + } - public void DoWork(Action action) => - new Task(DoWorkImpl, action, CancellationToken.None, - TaskCreationOptions.LongRunning).Start(); + internal void DoWork(Action action) + { + new Task(DoWorkImpl, action, CancellationToken.None, TaskCreationOptions.LongRunning).Start(); + } - private void DoWorkImpl(object oAction) + internal void DoWorkImpl(object oAction) { Action action = (Action)oAction; try @@ -39,15 +42,19 @@ private void DoWorkImpl(object oAction) } } - private void Succeed() => Succeeded?.Invoke(); + private void Succeed() + { + Succeeded?.Invoke(this, EventArgs.Empty); + } - private void Fail(Exception exception) => Error?.Invoke(exception); + private void Fail(Exception exception) + { + Error?.Invoke(this, new WorkerErrorEventArgs(exception)); + } - private void Callback(Action action) => - Task.Factory.StartNew(action, CancellationToken.None, - TaskCreationOptions.None, _callbackThread); + private void Callback(Action action) + { + Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _callbackThread); + } } - - internal delegate void WorkerSucceededEventHandler(); - internal delegate void WorkerExceptionEventHandler(Exception exception); } diff --git a/YAMDCC.IPC/Threading/WorkerErrorEventArgs.cs b/YAMDCC.IPC/Threading/WorkerErrorEventArgs.cs new file mode 100644 index 0000000..f0df58a --- /dev/null +++ b/YAMDCC.IPC/Threading/WorkerErrorEventArgs.cs @@ -0,0 +1,29 @@ +using System; + +namespace YAMDCC.IPC.Threading +{ + /// + /// Provides data for the + /// and + /// events. + /// + public class WorkerErrorEventArgs : EventArgs + { + /// + /// The that caused the error. + /// + public Exception Exception { get; } + + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The exception that caused the error. + /// + internal WorkerErrorEventArgs(Exception exception) + { + Exception = exception; + } + } +} diff --git a/YAMDCC.Service/svcFanControl.cs b/YAMDCC.Service/svcFanControl.cs index e206a5c..b69818b 100644 --- a/YAMDCC.Service/svcFanControl.cs +++ b/YAMDCC.Service/svcFanControl.cs @@ -47,7 +47,7 @@ internal sealed partial class svcFanControl : ServiceBase /// /// The named message pipe server that MSI Fan Control connects to. /// - private readonly Server IPCServer; + private readonly NamedPipeServer IPCServer; /// /// The instance to write logs to. @@ -76,7 +76,7 @@ public svcFanControl() //security.AddAccessRule(new PipeAccessRule("Administrators", PipeAccessRights.ReadWrite, AccessControlType.Allow)); security.SetSecurityDescriptorSddlForm("O:BAG:SYD:(A;;GA;;;SY)(A;;GRGW;;;BA)"); - IPCServer = new Server("YAMDCC-Server", 0, security); + IPCServer = new NamedPipeServer("YAMDCC-Server", security); } #region Events @@ -150,38 +150,38 @@ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) return true; } - private void IPCClientConnect(NamedPipeConnection connection) + private void IPCClientConnect(object sender, PipeConnectionEventArgs e) { - connection.ReceiveMessage += IPCClientMessage; - Log.Info(Strings.GetString("ipcConnect"), connection.ID); + e.Connection.ReceiveMessage += IPCClientMessage; + Log.Info(Strings.GetString("ipcConnect"), e.Connection.ID); } - private void IPCClientDisconnect(NamedPipeConnection connection) + private void IPCClientDisconnect(object sender, PipeConnectionEventArgs e) { - connection.ReceiveMessage -= IPCClientMessage; - Log.Info(Strings.GetString("ipcDC"), connection.ID); + e.Connection.ReceiveMessage -= IPCClientMessage; + Log.Info(Strings.GetString("ipcDC"), e.Connection.ID); } - private void IPCClientMessage(NamedPipeConnection connection, ServiceCommand message) + private void IPCClientMessage(object sender, PipeMessageEventArgs e) { int error = 0; - switch (message.Command) + switch (e.Message.Command) { case Command.ReadECByte: - error = ReadECByte(connection.Name, message.Arguments); + error = ReadECByte(e.Connection.Name, e.Message.Arguments); break; case Command.WriteECByte: - error = WriteECByte(connection.Name, message.Arguments); + error = WriteECByte(e.Connection.Name, e.Message.Arguments); break; case Command.GetFanSpeed: - error = GetFanSpeed(connection.Name, message.Arguments); + error = GetFanSpeed(e.Connection.Name, e.Message.Arguments); break; case Command.GetFanRPM: - error = GetFanRPM(connection.Name, message.Arguments); + error = GetFanRPM(e.Connection.Name, e.Message.Arguments); break; case Command.GetTemp: - error = GetTemp(connection.Name, message.Arguments); + error = GetTemp(e.Connection.Name, e.Message.Arguments); break; case Command.ApplyConfig: LoadConf(); @@ -189,17 +189,17 @@ private void IPCClientMessage(NamedPipeConnection