View difference between Paste ID: cgLrUd79 and b3MzQbSM
SHOW: | | - or go back to the newest paste.
1
using System;
2
using System.IO;
3
using System.Runtime.InteropServices;
4
using System.Threading;
5
using Microsoft.Win32.SafeHandles;
6
7
namespace wyDay.Controls
8
{
9
	/// <summary>Allow pipe communication between a server and a client.</summary>
10
	public class PipeClient : IDisposable
11
	{
12
		private object _lock = new object();
13
14
		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
15
		static extern SafeFileHandle CreateFile(
16
			string pipeName,
17
			uint dwDesiredAccess,
18
			uint dwShareMode,
19
			IntPtr lpSecurityAttributes,
20
			uint dwCreationDisposition,
21
			uint dwFlagsAndAttributes,
22
			IntPtr hTemplate);
23
24
		/// <summary>Handles messages received from a server pipe</summary>
25
		/// <param name="message">The byte message received</param>
26
		public delegate void MessageReceivedHandler(byte[] message);
27
28
		/// <summary>Event is called whenever a message is received from the server pipe</summary>
29
		public event MessageReceivedHandler MessageReceived;
30
31
		/// <summary>Handles server disconnected messages</summary>
32
		public delegate void ServerDisconnectedHandler();
33
34
		/// <summary>Event is called when the server pipe is severed.</summary>
35
		public event ServerDisconnectedHandler ServerDisconnected;
36
37
		const int BUFFER_SIZE = 4096;
38
39
		FileStream stream;
40
		SafeFileHandle handle;
41
		Thread readThread;
42
43
		/// <summary>Gets if this client connected to a server pipe.</summary>
44
		public bool Connected { get; private set; }
45
46
		/// <summary>The pipe this client is connected to.</summary>
47
		public string PipeName { get; private set; }
48
49
		#region Dispose
50
51
		/// <summary>Indicates whether this instance is disposed.</summary>
52
		bool isDisposed;
53
54
		/// <summary>
55
		/// Finalizes an instance of the <see cref="PipeClient"/> class.
56
		/// Releases unmanaged resources and performs other cleanup operations before the
57
		/// <see cref="PipeClient"/> is reclaimed by garbage collection.
58
		/// </summary>
59
		~PipeClient()
60
		{
61
			Dispose(false);
62
		}
63
64
		/// <summary>Releases unmanaged and - optionally - managed resources.</summary>
65
		/// <param name="isManaged">Result: <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
66
		protected virtual void Dispose(bool isManaged)
67
		{
68
			if (isManaged)
69
			{
70
				lock (_lock)
71
				{
72
					if (!isDisposed)
73
					{
74
75
						// dispose managed resources
76
						Disconnect();
77
78
79
						// free unmanaged resources
80
						// Set large fields to null.
81
82
						// Instance is disposed
83
						isDisposed = true;
84
					}
85
				}
86
			}
87
			// Instance is disposed
88
			isDisposed = true;
89
		}
90
91
		/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
92
		public void Dispose()
93
		{
94
			Dispose(true);
95
			GC.SuppressFinalize(this);
96
		}
97
98
		#endregion
99
100
		/// <summary>Connects to the server with a pipename.</summary>
101
		/// <param name="pipename">The name of the pipe to connect to.</param>
102
		public void Connect(string pipename)
103
		{
104
			lock (_lock)
105
			{
106
				if (isDisposed) return;
107
				if (Connected)
108
					throw new Exception("Already connected to pipe server.");
109
110
				PipeName = pipename;
111
112
				handle =
113
					CreateFile(
114
						PipeName,
115
						0xC0000000, // GENERIC_READ | GENERIC_WRITE = 0x80000000 | 0x40000000
116
						0,
117
						IntPtr.Zero,
118
						3, // OPEN_EXISTING
119
						0x40000000, // FILE_FLAG_OVERLAPPED
120
						IntPtr.Zero);
121
122
				// could not create handle - server probably not running
123
				if (handle.IsInvalid)
124
				{
125
					handle = null;
126
					return;
127
				}
128
129
				Connected = true;
130
131
				// start listening for messages
132
				readThread = new Thread(Read) { IsBackground = true };
133
				readThread.Start();
134
			}
135
		}
136
137
		/// <summary>Disconnects from the server.</summary>
138
		public void Disconnect()
139
		{
140
			lock (_lock)
141
			{
142
				if (isDisposed) return;
143
144
				if (!Connected)
145
					return;
146
147
				// we're no longer connected to the server
148
				Connected = false;
149
				PipeName = null;
150
151
				// clean up resources
152
				if (stream != null)
153
					stream.Dispose();
154
155
				// If Connected == true then handle is non-null.
156
				// Thus, just dispose it.
157
				handle.Dispose();
158
159
				stream = null;
160
				handle = null;
161
			}
162
		}
163
164
		void Read()
165
		{
166
			lock (_lock)
167
			{
168
				if (isDisposed) return;
169
170
				stream = new FileStream(handle, FileAccess.ReadWrite, BUFFER_SIZE, true);
171
172
			}
173
174
			byte[] readBuffer = new byte[BUFFER_SIZE];
175
176
			while (true)
177
			{
178
				int bytesRead = 0;
179
180
				using (MemoryStream ms = new MemoryStream())
181
				{
182
					try
183
					{
184
						// read the total stream length
185
						int totalSize = stream.Read(readBuffer, 0, 4);
186
187
						// client has disconnected
188
						if (totalSize == 0)
189
							break;
190
191
						totalSize = BitConverter.ToInt32(readBuffer, 0);
192
193
						do
194
						{
195
							int numBytes = stream.Read(readBuffer, 0, Math.Min(totalSize - bytesRead, BUFFER_SIZE));
196
197
							ms.Write(readBuffer, 0, numBytes);
198
199
							bytesRead += numBytes;
200
201
						} while (bytesRead < totalSize);
202
					}
203
					catch
204
					{
205
						//read error has occurred
206
						break;
207
					}
208
209
					//client has disconnected
210
					if (bytesRead == 0)
211
						break;
212
213
					//get the event to execute while locked, but execute it outside the lock to prevent deadlock
214
					MessageReceivedHandler eventToExecute;
215
					lock (_lock)
216
					{
217
						if (isDisposed) return;
218
						eventToExecute = MessageReceived;
219
					}
220
					//fire message received event
221
					if (eventToExecute != null)
222
						eventToExecute(ms.ToArray());
223
224
				}
225
			}
226
227
			bool isLocked = false;
228
			try
229
			{
230
				Monitor.Enter(_lock);
231
				isLocked = true;
232
				if (isDisposed) return;
233
234
				// if connected, then the disconnection was
235
				// caused by a server terminating, otherwise it was from
236
				// a call to Disconnect()
237
				if (Connected)
238
				{
239
					//clean up resource
240
					stream.Dispose();
241
					handle.Dispose();
242
243
					stream = null;
244
					handle = null;
245
246
					// we're no longer connected to the server
247
					Connected = false;
248
					PipeName = null;
249
250
					//get the event to execute while locked, but execute it outside the lock to prevent deadlock
251
					var eventToExecute = ServerDisconnected;
252-
					Monitor.Exit(isLocked);
252+
253
					isLocked = false;
254
255
					if (eventToExecute != null)
256
						eventToExecute();
257
				}
258
			}
259
			finally
260
			{
261
				if (isLocked)
262
				{
263
					Monitor.Exit(_lock);
264
				}
265
			}
266
		}
267
268
		/// <summary>Sends a message to the server.</summary>
269
		/// <param name="message">The message to send.</param>
270
		/// <returns>True if the message is sent successfully - false otherwise.</returns>
271
		public bool SendMessage(byte[] message)
272
		{
273
			lock (_lock)
274
			{
275
				if (isDisposed) return false;
276
277
				if (stream == null) return false;
278
279
				try
280
				{
281
					// write the entire stream length
282
					stream.Write(BitConverter.GetBytes(message.Length), 0, 4);
283
284
					stream.Write(message, 0, message.Length);
285
					stream.Flush();
286
					return true;
287
				}
288
				catch
289
				{
290
					return false;
291
				}
292
			}
293
		}
294
	}
295
}