1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
|
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
namespace Sessions
{
public delegate void SessionNotifiedEventHandler(object objectReceivedFromCallingProcess);
public class SessionManager : IDisposable
{
#region Private fields
/// <summary>
/// Name of the active session
/// </summary>
private string _sessionName;
/// <summary>
/// String to identify the current assembly
/// This string will be attached to the session window name
/// to make it unique. This id could also be used to identify
/// groups of sessions.
/// </summary>
private readonly string _appendToSessionNametoEnsureItsUnique;
/// <summary>
/// The native Window to communicate across processes using
/// WM_COPYDATA
/// </summary>
private CommunicationWindow _communicator;
#endregion
#region Singleton implementation - This object exists only once per process
/// <summary>
/// Holds SessionManager instance
/// </summary>
private static SessionManager _instance;
/// <summary>
/// Private Constructor to allow initialization only from within this class
/// </summary>
private SessionManager()
{
_sessionName = "";
// This could be any string, I use the Assembly GUID here.
// It will be trimmed from the window title later to read the session name
_appendToSessionNametoEnsureItsUnique = GetAssemblyGUID();
}
/// <summary>
/// Creates the SessionManager Instance if required
/// and returns it (or the existing one)
/// </summary>
private static SessionManager Instance
{
get
{
if (_instance == null)
_instance = new SessionManager();
return _instance;
}
}
#endregion
#region Methods - These methods will be used by the static Interface methods
/// <summary>
/// Starts a new Session with the given name
/// by creating a (hidden) session window
/// </summary>
/// <param name="sessionName">Name of the session</param>
private void StartNew(string sessionName)
{
_sessionName = sessionName;
// Initialize the Native Window
// The window will be invisibly running in background, only to be found by the findwindow method
_communicator = new CommunicationWindow(_sessionName + _appendToSessionNametoEnsureItsUnique);
}
/// <summary>
/// Shuts the session down by closing and destroying the native window
/// </summary>
private void Shutdown()
{
if (_communicator != null)
_communicator.DestroyHandle();
}
/// <summary>
/// Searches for the Session Window
/// </summary>
/// <param name="sessionName"></param>
/// <returns></returns>
private bool IsActive(string sessionName)
{
IntPtr handle = FindWindow(IntPtr.Zero, sessionName + _appendToSessionNametoEnsureItsUnique);
if (handle != IntPtr.Zero)
{
return true;
}
return false;
}
/// <summary>
/// Notifies another session about the launch of a new process.
/// The target session should now act as it was called.
/// The calling Session will exit immediately after calling.
/// </summary>
/// <param name="sessionName">Name of the session to send the notification to</param>
/// <param name="attachment">Attachment to be contained within the message. Usually command line string</param>
/// <returns>true if the message was sent, false if the target session was not found</returns>
private bool NotifySession(string sessionName, object attachment)
{
// Search for the window of the session to notify
IntPtr hWnd = FindWindow(IntPtr.Zero, sessionName + _appendToSessionNametoEnsureItsUnique);
if (hWnd != IntPtr.Zero)
{
// This is the Trick! Use a pinned handle to have a global pointer to the byte array.
// The pinned pointer allows us to access the memory location from another process
GCHandle bufferHandle = new GCHandle();
try
{
byte[] buffer;
COPYDATASTRUCT dataStructForMessage = new COPYDATASTRUCT();
if (attachment != null)
{
buffer = MakeBytes(attachment);
bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); // here we pin the buffer
dataStructForMessage.dwData = 0;
dataStructForMessage.cbData = buffer.Length;
//get the address of the pinned buffer
dataStructForMessage.lpData = bufferHandle.AddrOfPinnedObject();
}
GCHandle dataStructHandle = GCHandle.Alloc(dataStructForMessage, GCHandleType.Pinned); // the COPYDATASTRUCT must also be pinned!!
try
{
// Send the message to the target process and attach the structure
SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, dataStructHandle.AddrOfPinnedObject());
return true;
}
finally
{
dataStructHandle.Free(); // Free the handle to let the memory cleanup do its magic!
}
}
finally
{
if (bufferHandle.IsAllocated)
bufferHandle.Free(); // Free second handle too!
}
}
return false;
}
/// <summary>
/// Enumerates all Windows of the system and returns a list of those
/// ending with the string defined in _appendToSessionNametoEnsureItsUnique
/// (without _appendToSessionNametoEnsureItsUnique)
/// </summary>
/// <returns>List of Session Names</returns>
private List<string> GetListOfSessionWidows()
{
List<string> windowNames = new List<string>();
// Enumeration with nested delegate function for API callback
EnumWindows(delegate(IntPtr hWnd, IntPtr lParam)
{
string windowText = GetWindowText(hWnd);
if (windowText.EndsWith(_appendToSessionNametoEnsureItsUnique))
windowNames.Add(windowText.Replace(_appendToSessionNametoEnsureItsUnique, ""));
return 1;
}, new IntPtr(0));
return windowNames;
}
#endregion
#region Static Interface Methods - Use these to call from the outside
/// <summary>
/// Occurs when another process was launched targetting the same session name.
/// The other process will exit after this event was handled.
/// </summary>
public static event SessionNotifiedEventHandler Notified;
/// <summary>
/// Starts the Session.
/// </summary>
/// <param name="sessionName">Name of the session.</param>
public static void SessionStart(string sessionName)
{
Instance.StartNew(sessionName);
}
/// <summary>
/// Shuts the Session down.
/// </summary>
public static void SessionShutdown()
{
Instance.Shutdown();
}
/// <summary>
/// Determinse if a session with the given name is already active.
/// </summary>
/// <param name="sessionName">Name of the session.</param>
/// <returns>true if a session name is already used by another session</returns>
public static bool SessionIsActice(string sessionName)
{
return Instance.IsActive(sessionName);
}
/// <summary>
/// Notifies the active session and attaches an object to be sent.
/// </summary>
/// <param name="sessionName">Name of the session.</param>
/// <param name="attachment">The attachment.</param>
/// <returns>true if the message was sent, false if the target session was not found</returns>
public static bool NotifyActiveSession(string sessionName, object attachment)
{
return Instance.NotifySession(sessionName, attachment);
}
/// <summary>
/// Gets all active session names.
/// </summary>
/// <returns>List of names of running sessions</returns>
public static List<string> GetAllActiveSessionNames()
{
return Instance.GetListOfSessionWidows();
}
#endregion
#region Windows API Methods
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindow(IntPtr zeroOnly, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
private delegate int EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, int nMsg, IntPtr wParam, IntPtr lParam);
const short WM_COPYDATA = 74;
struct COPYDATASTRUCT
{
public int dwData;
public int cbData;
public IntPtr lpData;
}
#endregion
#region NativeWindow used to send and receive Inter Process Messages
/// <summary>
/// The CommunicationWindow class is used to identify the current session
/// from any process by using FindWindow. The Window Name will be the
/// Session id followed by a GUID to make sure the window name is unique.
/// </summary>
private sealed class CommunicationWindow : NativeWindow
{
public CommunicationWindow(string sessionname)
{
CreateParams cp = new CreateParams();
cp.Caption = sessionname;
CreateHandle(cp);
}
/// <summary>
/// Override WndProc to receive Messages from other Processes
///
/// Messages to catch are WM_COPYDATA messages. the lParam Value
/// points to a structure containing the commandline used to open the session
/// </summary>
/// <param name="m">The Message received</param>
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_COPYDATA)
{
// Get the Copydata structure from the pointer in lParam
COPYDATASTRUCT dataStructFromMessage = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));
// This will be the object encapsulated in the message
object receivedObject = null;
if (dataStructFromMessage.cbData > 0 && dataStructFromMessage.lpData != IntPtr.Zero)
{
// Copy the bytes in the struct to our local buffer
byte[] buffer = new byte[dataStructFromMessage.cbData];
Marshal.Copy(dataStructFromMessage.lpData, buffer, 0, buffer.Length);
//deserialize the buffer to a new object
receivedObject = MakeObject(buffer);
}
// Raise event
if (Notified != null) // if evenHandlers are connected
Notified(receivedObject);
}
else
base.WndProc(ref m);
}
}
#endregion
#region Tool functions
/// <summary>
/// Determines the GUID of the current assembly
/// </summary>
/// <returns>GUID as string</returns>
private string GetAssemblyGUID()
{
string id = "";
foreach (object attr in Assembly.GetExecutingAssembly().GetCustomAttributes(true))
{
if (attr is GuidAttribute)
id = ((GuidAttribute)attr).Value;
}
return id;
}
/// <summary>
/// Gets the window text.
/// </summary>
/// <param name="hWnd">The handle to the window.</param>
/// <returns>Caption of the window</returns>
private string GetWindowText(IntPtr hWnd)
{
StringBuilder sb = new StringBuilder(GetWindowTextLength(hWnd) + 1);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
/// <summary>
/// Deserializes an object in memory.
/// </summary>
/// <param name="buffer">Byte array of the object received from an external process</param>
/// <returns>Deserialized object. Requires casting into its correct type.</returns>
private static object MakeObject(byte[] buffer)
{
using (MemoryStream stream = new MemoryStream(buffer))
{
return new BinaryFormatter().Deserialize(stream);
}
}
/// <summary>
/// Serializes an object in memory into a byte array.
/// This can be used to attach any object to the COPYDATASTRUCT
/// structure and send it to another process.the receivubg process
/// must know which type to expect to make this work.
/// </summary>
/// <param name="obj">An object to serialize</param>
/// <returns>A byte array containing the object.</returns>
private static byte[] MakeBytes(Object obj)
{
using (MemoryStream stream = new MemoryStream())
{
new BinaryFormatter().Serialize(stream, obj);
return stream.ToArray();
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
Shutdown();
}
#endregion
}
}
|