|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis article demonstrates a quick and easy-to-use implementation for cross-AppDomain communication in .NET by leveraging Windows native messaging. The BackgroundSo, why not use .NET Remoting? Well, in the past I've personally found this extremely tedious to set up and configure. The other issue is the lack of helpful error reporting when things go wrong, invariably with permissions. Don't get me wrong, I'm not opposed to .NET Remoting. It has a lot more functionality than my own implementation, and of course is not limited to communication on a single box. However, for same-box communications, it doesn't need to be that complex. So, why not leverage Windows Messaging? After all, this is the mechanism that unmanaged applications use for exactly this purpose. Well now, there's an idea… If you've never heard of them, Windows messages are low-level communications used by the Windows operating system to broadcast information regarding user-input, system changes and other events that applications running on the system can react to. For instance, application repaints are triggered by the The XDMessaging LibraryThe Example: Sending a Message// Send shutdown message a channel named commands
XDBroadcast.SendToChannel("commands", "shutdown");
Example: Listening on a Channel// Create our listener instance
XDListener listener = new XDListener();
// Register channels to listen on
listener.RegisterChannel("events");
listener.RegisterChannel("status");
listener.RegisterChannel("commands");
// Stop listening on a specific channel
listener.UnRegisterChannel("status");
Example: Handling the Messages// Attach an event handler to our instance
listener.MessageReceived+=XDMessageHandler(this.listener_MessageReceived);
// process the message
private void listener_MessageReceived(object sender, XDMessageEventArgs e)
{
// e.DataGram.Message is the message
// e.DataGram.Channel is the channel name
switch(e.DataGram.Message)
{
case "shutdown":
this.Close();
break;
}
}
The Messenger DemoTo see the demo, you will need to launch multiple instances of the Messenger.exe application. The demo application serves no practical purpose other than to demonstrate the use of the How It WorksThe library makes use of a Windows system message of the type [StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
[DllImport("user32", CharSet = CharSet.Auto)]
public extern static int SendMessage(IntPtr hwnd, int wMsg,
int wParam, ref COPYDATASTRUCT lParam);
The // Serialize our raw string data into a binary stream
BinaryFormatter b = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
b.Serialize(stream, raw);
stream.Flush();
int dataSize = (int)stream.Length;
// Create byte array and transfer the stream data
byte[] bytes = new byte[dataSize];
stream.Seek(0, SeekOrigin.Begin);
stream.Read(bytes, 0, dataSize);
stream.Close();
// Allocate a memory address for our byte array
IntPtr ptrData = Marshal.AllocCoTaskMem(dataSize);
// Copy the byte data into this memory address
Marshal.Copy(bytes, 0, ptrData, dataSize);
With the To overcome this, we use some native Window properties and our The // NativeWindow override to filter our WM_COPYDATA packet
protected override void WndProc(ref Message msg)
{
// We must process all the system messages and propagate them
base.WndProc(ref msg);
// If our message
if (msg.Msg == Win32.WM_COPYDATA)
{
// msg.LParam contains a pointer to the COPYDATASTRUCT struct
Win32.COPYDATASTRUCT dataStruct =
(Win32.COPYDATASTRUCT)Marshal.PtrToStructure(
msg.LParam , typeof(Win32.COPYDATASTRUCT));
// Create a byte array to hold the data
byte[] bytes = new byte[this.dataStruct.cbData];
// Make a copy of the original data referenced by
// the COPYDATASTRUCT struct
Marshal.Copy(this.dataStruct.lpData, bytes, 0,
this.dataStruct.cbData);
// Deserialize the data back into a string
MemoryStream stream = new MemoryStream(bytes);
BinaryFormatter b = new BinaryFormatter();
// This is the message sent from the other application
string rawmessage = (string)b.Deserialize(stream);
// do something with our message
}
}
Note that because the message is stored in memory whilst it's broadcast to other applications, we must remember to free that memory after the message has been sent. Each Window receiving the data will make its own copy, so the original data can be destroyed safely as soon as the messages have been sent. This is necessary because the data is stored in unmanaged memory and would otherwise result in a memory leak. // Free the memory referenced by the given pointer
Marshal.FreeCoTaskMem(lpData);
Below are the other PInvoke methods used for setting and removing Window properties, as well as enumerating the desktop Windows. // Delegate used during window enumeration
public delegate int EnumWindowsProc(IntPtr hwnd, int lParam);
// The Win32 API used to enumerate children of the desktop window
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hwndParent,
EnumWindowsProc lpEnumFunc, IntPtr lParam);
// The API used to look for a named property on a window
[DllImport("user32", CharSet = CharSet.Auto)]
public extern static int GetProp(IntPtr hwnd, string lpString);
// The API used to set a named property on a window, and
// hence register a messaging channel
[DllImport("user32", CharSet = CharSet.Auto)]
public extern static int SetProp(IntPtr hwnd, string lpString, int hData);
// The API used to remove a property from a window, and hence unregister a
// messaging channel
[DllImport("user32", CharSet = CharSet.Auto)]
public extern static int RemoveProp(IntPtr hwnd, string lpString);
Further Reading
History
| ||||||||||||||||||||