Click here to Skip to main content
Click here to Skip to main content

.NET Interprocess Communication

By , 14 Dec 2009
 
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

  • Feb 2007: Initial release
  • 25th Feb, 2007: Added VB port to demo
  • 29th May, 2007: Article edited and posted to the main CodeProject.com article base
  • 2nd June, 2008: Improved threading support to avoid application hanging

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

TheCodeKing
Architect
United Kingdom United Kingdom
Member
Mike Carlisle - Technical Architect with over 10 years experience in a wide range of technologies.
 
@TheCodeKing

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionAnother choice - NamedPipememberpajopajo26 Jun '12 - 18:28 
AnswerRe: Another choice - NamedPipememberpajopajo26 Jun '12 - 19:32 
GeneralRe: Another choice - NamedPipememberTheCodeKing26 Jun '12 - 21:46 
GeneralMy vote of 5memberFilip D'haene14 May '12 - 19:12 
GeneralMy vote of 5memberURVISH SUTHAR from Ahmadabad Gujarat, India2 Nov '11 - 19:20 
QuestionHow to handle handle inptr to fix invalid access to memeory locationmemberAmit kumar pathak31 Mar '11 - 19:56 
Generalinteresting articlememberJon Smith 037414 Dec '09 - 6:17 
NewsXDMessaging-2.0 now supports Windows Services [modified]memberTheCodeKing12 Dec '09 - 11:53 
QuestionWinform is not able receive broadcast message from Windows Sevice, anything missing?memberavnkkishore7 Dec '09 - 22:37 
AnswerRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberTheCodeKing7 Dec '09 - 22:46 
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberavnkkishore8 Dec '09 - 2:54 
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberTheCodeKing8 Dec '09 - 3:04 
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberavnkkishore8 Dec '09 - 5:18 
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberTheCodeKing8 Dec '09 - 11:46 
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberavnkkishore8 Dec '09 - 22:15 
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberTheCodeKing11 Dec '09 - 14:55 
GeneralRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberbart.burkhardt7 Mar '10 - 23:36 
AnswerRe: Winform is not able receive broadcast message from Windows Sevice, anything missing?memberTheCodeKing12 Dec '09 - 11:55 
GeneralReally Interesting articlememberbilo8116 Nov '09 - 7:05 
GeneralRe: Really Interesting articlememberTheCodeKing13 Dec '09 - 2:03 
QuestionMessaging from IIS?memberSenor Plankton3 Jun '09 - 0:09 
AnswerRe: Messaging from IIS?memberTheCodeKing13 Dec '09 - 1:58 
GeneralWnd procmemberkurtsune7 Apr '09 - 22:58 
GeneralBroken linkmemberMember 603819618 Mar '09 - 22:19 
GeneralRe: Broken linkmemberTheCodeKing20 Mar '09 - 3:43 
GeneralAlternative to Cross AppDomain Communicationmembercwfong27 Dec '08 - 3:54 
GeneralRe: Alternative to Cross AppDomain CommunicationmemberTheCodeKing13 Dec '09 - 2:08 
GeneralExecution stuck at SendMessage method callmemberSuneel_Sarode2 Jun '08 - 22:25 
GeneralRe: Execution stuck at SendMessage method call [modified]memberTheCodeKing3 Jun '08 - 9:32 
GeneralScalability questionmemberMember 386108529 Apr '08 - 4:38 
AnswerRe: Scalability questionmemberTheCodeKing30 Apr '08 - 10:50 
GeneralRe: Scalability questionmemberMember 386108530 Apr '08 - 21:03 
GeneralA great concept but .. [modified]memberWytek Szymanski15 Jun '07 - 18:17 
GeneralRe: A great concept but ..memberRenniePet8 Dec '07 - 12:13 
GeneralRe: A great concept but ..memberTheCodeKing13 Dec '09 - 2:16 
GeneralExcellentmembermerlin98130 May '07 - 3:19 
Questionreading deallocated memory?memberrhguera29 May '07 - 23:56 
AnswerRe: reading deallocated memory? [modified]memberbeelineuk30 May '07 - 0:02 
GeneralRe: reading deallocated memory?memberrhguera30 May '07 - 0:15 
GeneralExcellent!...But does not work for IPC (inter-process communication)memberHomey1112 Apr '07 - 2:53 
GeneralRe: Excellent!...But does not work for IPC (inter-process communication)memberTheCodeKing12 Apr '07 - 3:22 
GeneralRe: Excellent!...But does not work for IPC (inter-process communication)memberHomey1112 Apr '07 - 5:40 
GeneralRe: Excellent!...But does not work for IPC (inter-process communication)memberTheCodeKing12 Apr '07 - 6:03 
GeneralRe: Excellent!...But does not work for IPC (inter-process communication)memberHomey1112 Apr '07 - 6:35 
GeneralRe: Excellent!...But does not work for IPC (inter-process communication)memberTheCodeKing12 Apr '07 - 8:24 
GeneralRe: Excellent!...But does not work for IPC (inter-process communication)memberHomey1112 Apr '07 - 10:23 
GeneralRe: Excellent!...But does not work for IPC (inter-process communication)membermav.northwind29 May '07 - 18:51 
QuestionCallbacks?membermmfranke9 Apr '07 - 8:33 
AnswerRe: Callbacks?membermmfranke9 Apr '07 - 8:57 
GeneralRe: Callbacks?memberTheCodeKing10 Apr '07 - 10:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 14 Dec 2009
Article Copyright 2007 by TheCodeKing
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid