Click here to Skip to main content
Email Password   helpLost your password?
Sample image

Introduction

This article demonstrates a quick and easy-to-use implementation for cross-AppDomain communication in .NET by leveraging Windows native messaging. The XDMessaging library is based on code developed to aid rapid development for a recent Vista project that required a lot of cross-AppDomain communication in a locked-down environment. It proved to be extremely useful in a number of scenarios where .NET Remoting would have been impractical, if not impossible, and actually solved more problems than I could have imagined due to its simplicity. The library is intended to send messages between multiple applications in a same-box scenario. For example, a task-tray application might want to communicate with or monitor a separate desktop application. The library does not implement cross-domain communication across a network, for which case .NET Remoting is sufficient.

Update: XDMessaging 2.0 is now available here, and introduces support for Windows services and console applications.

Background

So, 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 WM_PAINT message. As well as system messages, unmanaged applications may also define custom Windows messages and use them to communicate with other windows. These usually take the form of WM_USER messages. If you have Spy++ installed (Visual Studio Tools), you can monitor all of the messages that a window receives in real-time.

The XDMessaging Library

The XDMessaging library provides an easy-to-use, zero-configuration solution to same-box cross-AppDomain communications. It provides a simple API for sending and receiving targeted string messages across application boundaries. The library allows the use of user-defined pseudo 'channels' through which messages may be sent and received. Any application can send a message to any channel, but it must register as a listener with the channel in order to receive. In this way, developers can quickly and programmatically devise how best their applications can communicate with each other and work in harmony.

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 Demo

To 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 XDMessaging library. It shows how messages may be passed to multiple instances of a desktop application across application boundaries. The application uses two arbitrary channels, named Status and UserMessage. Window events such as onClosing and onLoad are broadcast as messages on the Status channel (displayed in green) and user messages are broadcast on the UserMessage channel (displayed in blue). By checking or unchecking the options, you may toggle which channel messages the window will listen for.

How It Works

The library makes use of a Windows system message of the type WM_COPYDATA. This system message allows data to be passed between multiple applications by carrying a pointer to the data we wish to copy, in this case a string. This is sent to other windows using the SendMessage Win32 API with PInvoke.

[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 COPYDATASTRUCT struct contains information regarding the message data that we want to transfer to another application. This is referenced by the members lpData and dwData. lpData is a pointer to the string data that is stored in memory. dwData is the size of the transfer data. The cdData member is not used in our case. In order to pass the data, we must first allocate the message string to an address in memory and get a pointer to this data. To do this, we use the Marshal API as follows:

// 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 string data now in memory, referenced by our ptrData pointer in the above code, we can create our COPYDATASTRUCT instance and populate the lpData and dwData members accordingly. So, now that we've wrapped our message up as a COPYDATASTRUCT object, we're ready to send this to another Window using the SendMessage API. However, to do this we first need to know which applications should receive the data, i.e. which are listening and on the correct channel. Also, what if the application doesn't have a Window handle to send our message to?

To overcome this, we use some native Window properties and our XDListener class. When an instance of the class is invoked, it creates a hidden window on the desktop which acts as listener for all Windows messages. This is done by extending the NativeWindow class of System.Windows.Forms. By overriding the WndProc method, this allows us to filter Windows messages and look for our WM_COPYDATA message containing the message data.

The XDListener class also makes use of Window properties to create property flags indicating which channels the instance is listening on, and therefore which messages it should receive. When a message is broadcast, it enumerates all of the desktop Windows using the EnumChildWindows Win32 API. It looks for a flag (property name) on the window that represents the channel name. If found, the Windows WM_COPYDATA message is sent to that window. Once there, it will be caught and processed by the XDListener instance that owns the hidden window. To read the message data, we use the lParam of the native Windows message in order to expand the COPYDATASTRUCT instance. From this, we can locate and restore the original string message which was stored in memory earlier.

// 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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalinteresting article
Jon Smith 0374
7:17 14 Dec '09  
interesting article
NewsXDMessaging-2.0 now supports Windows Services [modified]
TheCodeKing
12:53 12 Dec '09  
I thought I'd spend some time on this as a lot of people seem to be looking for a solution that works from within a Windows Service. As I mentioned previously Windows Messaging is not really suited to this scenario as Windows Messaging requires a message pump and a window to send and receive messages.

In keeping with the original aim of the library to provide a zero configuration mechanism for inter-process cross appDomain communication I've come up with a solution based of file IO streams. I also looked at few alternatives such as MailSlots and NamedPipes but none of them met the requirements. I'll explain my thinking in a future article.

For now there's an upgraded version of the XDMessaging-2.0 library below which now includes 2 modes. WindowsMessaging and IOStream. Only IOStream is supported from with a Windows Service and also works for Forms applications. WindowsMessaging would still be the preferred mechanism for performant messaging between forms apps.


// To use IOStream
IXDBroadcast broadcast = XDBroadcast.CreateBroadcast(XDTransportMode.IOStream);
IXDListener listener = XDListener.CreateListener(XDTransportMode.IOStream);
listener.MessageReceived += XDMessageHandler(this.listener_MessageReceived);
...

// To use WindowsMessaging
IXDBroadcast broadcast = XDBroadcast.CreateBroadcast(XDTransportMode.WindowsMessaging);
IXDListener listener = XDListener.CreateListener(XDTransportMode.WindowsMessaging);
listener.MessageReceived += XDMessageHandler(this.listener_MessageReceived);

...


Download XDmessaging-2.0-zip here

Mike


QuestionWinform is not able receive broadcast message from Windows Sevice, anything missing?
avnkkishore
23:37 7 Dec '09  
Hi,

I am having two apps, one is Windows Service which broadcasts the message, where the second Windows Form application receives that and does some processing. The Broadcast message from Windows Service is not received by Windows Form application Frown. When I tested the same scenario with a Console Application, my Winfroms app is working fine. Just wondering is there anything I am missing to run in Windows Service.

I am working in .Net Framework 3.5 on Windows XP environment.

Thanks in advance.

Regards,
Kishore
AnswerRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
TheCodeKing
23:46 7 Dec '09  
You will need to set the "Allow Service to interact with desktop" setting of the windows service otherwise the service will not be able to send or receive windows messages.


GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
avnkkishore
3:54 8 Dec '09  
Thanks for the reply. I did the service interactive by doing the following code in 'OnCommitted' event of my ProjectInstaller.

// Here is where we set the bit on the value in the registry.
// Grab the subkey to our service
RegistryKey ckey = Registry.LocalMachine.OpenSubKey(
   @"SYSTEM\CurrentControlSet\Services\" + this.serviceInstaller1.ServiceName, true);

// Good to always do error checking!
if (ckey != null)
{
      // Ok now lets make sure the "Type" value is there,
      //and then do our bitwise operation on it.
      if (ckey.GetValue("Type") != null)
      {
            ckey.SetValue("Type", ((int)ckey.GetValue("Type") | 256));
      }
}

And I have observed that the Service's Registry value changing with the above code. But still my Winform is not receiving broadcast messages  Frown.

Any help is highly appreciated.

Thanks,
Kishore
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
TheCodeKing
4:04 8 Dec '09  
What do you see if you right click on your service in the services console and navigate to the "Log On" tab? The "Allow service to interact with desktop" should be checked.

If it is and that still doesn't work take a look at the comment further down by Wytek Szymanski entitled "A great concept but ..". He kindly posted a code fix for getting the messaging working from within a service, though I haven't had a chance to look at this myself yet or try it out.


GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
avnkkishore
6:18 8 Dec '09  
I am seeing the 'Allow service to interact with desktop' checked in the Service Properties.

I tried to modify the XDListener as given in the 'A great concept, but...' thread, but its expecting Win32 class to have MSG property, GetMessage and DispatchMessage methods which I am not able to find.

I got struck at this point. Updating XDListener means, making Windows Forms application listening to the BoradCast messages sent from the Windows Service. Please let me know whether my understanding is correct.

Thanks,
Kishore
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
TheCodeKing
12:46 8 Dec '09  
You'd need to add these methods to Win32. When I get some time I'll try this out myself. Hope that helps.

       
public const uint WM_QUIT = 0x0012;

[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32", CharSet = CharSet.Auto)]
public static extern bool PostThreadMessage(uint threadId, uint msg, UIntPtr wParam, IntPtr lParam);

[DllImport("user32", CharSet = CharSet.Auto)]
public static extern IntPtr DispatchMessage([In] ref MSG lpmsg);

[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public UInt32 message;
public IntPtr wParam;
public IntPtr lParam;
public UInt32 time;
public POINT pt;
}

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
int x;
int y;
}



GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
avnkkishore
23:15 8 Dec '09  
Quickly I will describe what I have done.

I have updated Win32 class with the code, XDListener class (as per thread) and referenced in my Windows Forms Application. Following is code that I have written in my windows Forms' Form_Load() method

      listener = new XDListener();
listener.MessageReceived += new XDListener.XDMessageHandler(listener_MessageReceived);
listener.RegisterChannel("Status");
listener.Start();


My Windows Service contains the non-modified version of XDMessaging Library as I am not using XDListener class in that. Following is single line of code I am using in OnStart() and OnStop() events of my service.
      XDBroadcast.SendToChannel("Status", "Service Started");


Am I doing correctly or any changes are required.

Thanks,
Kishore
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
TheCodeKing
15:55 11 Dec '09  
The problem is that to send the message the service needs to get a handle to the desktop and enumerate all of the child windows looking for windows that have registered to receive the data.

Firstly the service is unable to get a handle to the user desktop unless "allow service to interact with desktop" is set. Secondly it looks like when running as a service the process is restricted in which windows it can access and does not therefore enumerate all of the children. This is why the message is not sent as it cannot see the client window. The library is not really designed for use within a windows service as it was built on windows messaging which requires a window and a message pump to function.

You would be better off using technologies like sockets, named pipes or mailslots for the cross process communication in your service. I'd recommend taking a look at WCF to see if you can find something that fits your needs.


AnswerRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?
TheCodeKing
12:55 12 Dec '09  
I've posted a new version of XDMessaging which now supports an additional mode that works from within Windows Services. See the post "XDMessaging-2.0 now supports Windows Services" in the comments section.


GeneralReally Interesting article
bilo81
8:05 16 Nov '09  
I really like the article, 5 stars for me.
Another approach could be to use sockets of course (maybe using Datagram Socket) and create a library so that the client use the sockets transparently, without knowing it.
This is a really good insight into PInvoke and using unmanaged code in C# though, and I really appreciate your approach.
Keep up the good work
GeneralRe: Really Interesting article
TheCodeKing
3:03 13 Dec '09  
Thanks for your interest.

The problem with sockets and even NamedPipes is that they require a client and server approach. The server must be created first before a client can connect. In effect each has to know about the other beforehand. The idea behind this library is that any process can broadcast a message to any listener in a more disconnected fashion. With XDMessaging library there is no server and no client as such, which can be an advantage for a quick and easy messaging platform.


GeneralMessaging from IIS?
Senor Plankton
1:09 3 Jun '09  
This is a really useful utility - thanks! Big Grin

I thought it would be really handy for monitoring/profiling web services but I hit a snag. Although it works fine when running the web service on the VS dev webserver, it doesn't when it's run on the local IIS. There must be another boundary issue specific to the IIS process that's stopping it. Unfortunately I don't have time to investigate at the moment, so I'll have to revert to the old write-to-a-file method!

If there was a way to get this working in IIS it would be perfect!

(As a simple test, create a new web service in VS, and put something like:

TheCodeKing.Net.Messaging.XDBroadcast.SendToChannel("Status", "Hello World " & Now.Ticks)

in the HelloWorld sample method.)
GeneralRe: Messaging from IIS?
TheCodeKing
2:58 13 Dec '09  
Version 2.0 now works from within IIS, see the new release included in the comments.


GeneralWnd proc
kurtsune
23:58 7 Apr '09  
This code works in C#:
        protected override void WndProc(ref Message msg)
{
base.WndProc(ref msg);
if (msg.Msg == Win32.WM_COPYDATA)
{
if (MessageReceived == null) return;
DataGram dataGram = DataGram.FromPointer(msg.LParam);
MessageReceived(this, new XDMessageEventArgs(dataGram));
}
}


This code does not work in VB.NET:

       Protected Overloads Overrides Sub WndProc(ByRef msg As Message)
MyBase.WndProc(msg)
If msg.Msg = im.Win32.WM_COPYDATA Then
If MessageReceived Is Nothing Then 'ERROR see below
Return
End If
Dim dataGram__1 As Sune2.Messaging.DataGram = Sune2.Messaging.DataGram.FromPointer(msg.LParam)
RaiseEvent MessageReceived(Me, New MessageEventArgs(dataGram__1))
End If
End Sub

ERROR: 'Public Event MessageReceived(sender As Object, e As MessageEventArgs)' is an event, and cannot be called directly.

I would be very grateful if you could explain how to do this in VB.

/k
GeneralBroken link
Member 6038196
23:19 18 Mar '09  
The last link, "Boundaries: Processes and Application Domains" is broken, it should point to: http://msdn.microsoft.com/en-us/library/kt21t9h7(VS.80).aspx
GeneralRe: Broken link
TheCodeKing
4:43 20 Mar '09  
Thanks, I've requested that link is updated Smile


GeneralAlternative to Cross AppDomain Communication
cwfong
4:54 27 Dec '08  
One simpler approach is to use AppDomain's CreateInstanceFromAndUnwrap method to create proxy to another app domain, given that you have control over the app domains.
GeneralRe: Alternative to Cross AppDomain Communication
TheCodeKing
3:08 13 Dec '09  
Unfortunately this does not help with inter-process communication.


GeneralExecution stuck at SendMessage method call
Suneel_Sarode
23:25 2 Jun '08  
Hi,

I am testing few solutions for communication between two instances of an executable and I wasn't recieveing messages send across. I debugged and found that execution is hung at SendMessage method call. I am just trying to import the XDMessaging library and use it directly for testing purposes.

Any suggestions or points as what am I doing wrong?

Thanks,
Suneel
GeneralRe: Execution stuck at SendMessage method call [modified]
TheCodeKing
10:32 3 Jun '08  
Hi,

The SendMessage Win32 call the library uses is synchronous, so if the call doesn't return it hangs. For some reason this happens when the listener is created outside of the main thread.

There are a number of solutions depending on your specific situation, but the simplist solution is to create the listener on the main thread as in the example. You could also concider using an asynchronous Win32 call as an alternative, however I specifically use synchronous in order that I can free the memory after all the listeners have copied the data. If it's a asynchronous call then I have no way of knowing when it's safe to do so after the message is sent.

[Edited]
I've updated the version of the library on my site to use SendMessageTimeout API instead, which prevents application hanging (if the SendMessage does not return after 1 second or the thread is blocked, then the process continues). I'd be interested to know if this resolves your issue. It should certainly prevent hanging, but may still mean the threaded window doesn't receive the message.

I've requested that the article is updated on codeproject, but this may take a while to get actioned. In the mean time it's available here

Mike


modified on Tuesday, June 3, 2008 5:39 PM

GeneralScalability question
Member 3861085
5:38 29 Apr '08  
Hi,

This article is great, and it might be exactly what I'm looking for. I need toi write a win service that does some quite intensive tasks, and a monitor app, that, when launched, receive messages from the service and update some status
However, the issue is the service app might have to send thousands of messages in a very short period of time (will be a multi-threaded app with a thread pool).
Does this library was tested for scalability? What are its performances in this situation? Can it handle reliable thousands of messages send by multiple threads?

Thank you
AnswerRe: Scalability question
TheCodeKing
11:50 30 Apr '08  
Hi,

I haven't performed any scalability testing on the library myself, but Windows is built on Windows Messages so handling large volumes shouldn't be an issue. That said, if performance is a consideration then there are a few areas where you should consider re-factoring when building your solution.

1) The library iterates all of the windows on the desktop looking for a Windows property which indicates the window is acting as a listener for a 'channel'. This isn't the most efficient way of sending messages, and it would be much more efficient to send a message directly to another Window if you can more easily identify it.

2) The library copies data between processes, which requires synchronous messaging. If you don't need to send data between domains, it would be much better to use the asynchronous messaging APIs for the communication to prevent blocking threads.

3) The synchronous Win32 messaging calls used by this library can cause hanging in cross-threading scenarios within a single appdomain. This is because the SendMessage API waits for the message to be processed before it returns, and in some situations with threading this can fail. You may want to consider using a different Win32 API, however it depends of whether you intend to copy data as to whether this is workable. The issue only occurs when the listener is created on seperate thread.

Certainly I would recommend using Windows Messaging for performant cross-domain communication, but consider which Win32 API you use with care.

Good luck,

Mike


GeneralRe: Scalability question
Member 3861085
22:03 30 Apr '08  
Thanks for answers Smile .

Best regards


Last Updated 14 Dec 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010