Overview
.NET is great platform, it speeds up the development process, you deal with your application logic and in most cases you don't need to know that there is whole Windows operating system down there. However sometimes you do need to program against Windows without the .NET assistance. As a Windows developer, you need to keep all your weapons ready, be it .NET, COM or C++ with the native Win32 API. In this article I am going to show how to deal with Windows Message based communication protocol within .NET application.
Windows Messages
In traditional User32.dll based Windows application the Windows Messages mechanism serves as a communication channel among many parties. The Windows Operating System uses this mechanism to ask the Window to draw itself, to inform about environmental change or even to communicate a logoff event. The application itself uses this mechanism to create an event driven design model with a single thread or to communicate between threads. Other applications can use this mechanism with an application defined message (the WM_APP message range) to trigger a logical event, or even to pass memory buffer using the WM_COPYDATA.
Windows In Windows Are Based On An Object Oriented Mechanism
One of the impressive thing about the windows mechanism of Windows, considering its age and the fact that it was implemented with the C language in mind, is that is based on the Object Oriented paradigm. Each window is belong to a Window Class. The class is the factory of a window. The class defines the window's properties as well as the window's behavior. The properties are set using one of the predefined window class member such as the window's menu, or cursor. The behavior is defined by specifying the windows procedure address. The window procedure handles the incoming Windows Messages, which are in fact the methods of the window. Changing the Windows attributes can be made using the SetWindowLong/Ptr. Creating type fields (static) and instance fields can be made by allocating user-defined memory area for each Window class (static) and Window instance (dynamic) using the cbClsExtra and cbWndExtra fields of the WNDCLASS structure. Subclassing a Windows class can be achieved by changing the window procedure address to point to a custom function that handles the Windows Messages before it calls the original window procedure. You can subclass a Window instance or Window class (global subclassing).
Windows Do Not Run
In first look, it seems that that message communication is based on one window sending messages to another window, however windows do not run, threads do. Windows makes a correlation between threads and windows. The thread that calls CreateWindow/Ex is the thread that receives the Windows Messages related to this Window. In a common implementation of a Windows application, the main thread creates it's main and child windows, and then starts a message loop, also known as message pump, pumping messages that has been sent to any of it's windows, and dispatching this messages to the correct Window Procedure – the code that handle Windows Messages on behalf of the target Window.
Using Windows Messages Not For Painting The Window
Passing Windows Messages is one of the most easier inter process communication (IPC) mechanism, especially if the two parties are already implementing the Windows message handling boilerplate to handle their UI. One of the compelling feature of the mechanism is that it serves as a base for thread function call dispatching, enables a cross thread context call. For example in COM Single Threaded Apartment (STA), COM uses Windows Messages to dispatch function calls to the thread that created the component, much the same as dispatching messages to the windows created by the thread. This ensures that all the code of the COM component will be executed in a single thread- the idea behind the STA model.
Message Only Window
There are cases that you need to communicate using Windows Messages with another application or component, but your application is not based on the traditional user32.dll Windows. This happens more and more in modern native and manage applications that are based on Console or DirectX, for example a .NET WPF based application. In such cases there is no need to create a window to handle these messages, since we do not care about painting the window or handling resize messages, we do not want to pay the cost of such a mechanism and we do not want to handle the hiding of such a window. For that in Windows 2000 Microsoft has added a new type of window type, the message-only-window. Creating such a window is very easy, all you need to do is to provide the HWND_MESSAGE as the window's parent. You can do that in the CreateWindow/Ex or by using the SetParent function. You can use any predefined Window class, for example "STATIC", or "Message", as long as you subclass the window procedure to handle the coming Windows Messages.
Building A Message-Only-Window Client For .NET Applications
For handling system event, the .NET framework provides the Microsoft.Win32.SystemEvents class. However this class is made to receive Windows system event messages and can't be extended to receive custom user messages. You can always use Windows Form window to handle windows messages, however you will need to add reference to System.Windows.Forms.dll even if you don't use Windows Forms, and you will need to hide the form. A second approach, which I took, is to use C++/CLI assembly to create a message-only-window proxy. In this mechanism a native code handles the windows messages dispatching boilerplate. All is left for the managed code is to register a callback delegate on the message received event and ask the mechanism to start the message loop in the context of the thread that should handle the coming windows messages.
The Design
The NativeMessageOnlyWindow is a native class that handles the windows messages receiving, dispatching and handling. The MessageOnlyWindow is a managed C++/CLI wrapper (adapter) class that serves as the .NET interface for the mechanism. To bridge between the two, the MessageOnlyWindow class instantiates the native instance of the NativeMessageOnlyWindow and keeps it as a member that will be deleted on dispose. The managed MessageOnlyWindow class implements the IMessageCallback interface. The native NativeMessageOnlyWindow gets and holds a gcroot<IMessageCallback ^> to the MessageOnlyWindow managed class in the constructor. When the NativeMessageOnlyWindow receives Windows messages it calls to the OnMessageReceivedCallback method of the IMessageCallback interface. To get a better understanding on calling from native to .NET see this blog post.

To start pumping messages, the .NET client calls the MessageOblyWindow instance StartMessageLoop method. The call has to be made by the thread that created the Window that need to be handled. This call will not return until a call to StopMessageLoop will be made from any arbitrary thread.
The MessagePump.h header file
1:
2:
3: // MessagePump.h
4:
5: #pragma once
6: #include <Windows.h>
7: #include <hash_map>
8: #include <vcclr.h>
9:
10: using namespace System;
11: using namespace std;
12:
13: namespace MessagePump
14: { 15: //Callback wrapper
16: public interface class IMessageCallback
17: { 18: //get called each time a new message is arrived to the message only window
19: IntPtr OnMessageReceivedCallback(IntPtr hWnd, UINT message, WPARAM wParam, LPARAM lParam);
20: };
21:
22: //Serves as a Windows Message Loop manager for the message only window
23: class NativeMessageOnlyWindow
24: { 25: private:
26: HWND m_hWnd;
27: bool m_bStop;
28:
29: //This map helps in correlating the hWnd we get in the WndProc to the
30: //NativeMessageOnlyWindow instance of that Window
31: static hash_map<HWND, NativeMessageOnlyWindow *> s_windowsMap;
32:
33: //Enables calling back to .NET interface method
34: gcroot<IMessageCallback ^> _messageCallback;
35:
36: //To stop the message loop, we need to call PostQuitMessage in the context of the
37: //message loop thread. To do that we are sending this message
38: static const unsigned int WM_APPSTOP = WM_APP + 242;
39:
40: //Create the message only window
41: void Create();
42:
43: //The WndProc for the message only window
44: static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
45:
46:
47: public:
48: NativeMessageOnlyWindow(const gcroot<IMessageCallback ^> &&callback) : m_hWnd(0),
49: m_bStop(false), _messageCallback(callback) {} 50:
51: //Stop the message loop, the actual stop happens asynchronously, since
52: //we use PostQuitMessage
53: void StopMessageLoop();
54:
55: //A blocking call, the message loop is in the context of the calling thread
56: void StartMessageLoop();
57:
58: //Return the message only window handle
59: HWND GetWindowHandle() const { return m_hWnd; } 60:
61: //Stop the message loop and destroy the instance
62: ~NativeMessageOnlyWindow();
63: };
64:
65: ///<summary>
66: /// Represents a Windows Message that encapsulate in .NET type the WndProc arguments
67: ///</summary>
68: public value class WindowsMessage
69: { 70: private:
71: IntPtr m_hwnd;
72: unsigned int m_uMessage;
73: UIntPtr m_wParam;
74: IntPtr m_lParam;
75:
76: public:
77: /// <summary>
78: /// Construct a new WindowsMessage class that holds the WndProc arguments
79: /// </summary>
80: /// <param name="hWnd">The target windows handle</param>
81: /// <param name="message">The message number</param>
82: /// <param name="wParam">the wParam</param>
83: /// <param name="lParam">the lParam</param>
84: WindowsMessage(IntPtr hWnd, UINT message, WPARAM wParam, LPARAM lParam) :
85: m_hwnd(hWnd), m_uMessage(message), m_wParam(UIntPtr(wParam)), m_lParam(IntPtr(lParam)) {} 86:
87: property IntPtr HWnd { IntPtr get() { return m_hwnd; }} 88: property unsigned int Message { unsigned int get() { return m_uMessage; }} 89: property UIntPtr WParam { UIntPtr get() { return m_wParam; }} 90: property IntPtr LParam { IntPtr get() { return m_lParam; }} 91: };
92:
93: ///<summary>
94: ///Use this class to create a message only window
95: ///</summary>
96: ///<remarks>
97: ///With this class you can receive Windows Messages without the need to use a WinForm instance.
98: ///Message only windows are lightweight mechanisms that serves only for receiving windows messages.
99: ///</remarks>
100: public ref class MessageOnlyWindow : public IMessageCallback
101: { 102: private:
103: NativeMessageOnlyWindow *m_pNativeMessageOnlyWindow;
104:
105: protected:
106: /// <summary>
107: /// This callback functions receives the raw WndProc arguments for any message that has been
108: /// sent to the message-only-window
109: /// </summary>
110: /// <param name="hWnd">The target windows handle</param>
111: /// <param name="message">The message number</param>
112: /// <param name="wParam">the wParam</param>
113: /// <param name="lParam">the lParam</param>
114: /// <returns>The method should return a value for the DefWndProc, usually <see cref="IntPtr::Zero"/>
115: /// is suitable</returns>
116: /// <remarks>When overriding this method, call the base class implementation to fire the
117: /// <see cref="MessageReceived"/> event.</remarks>
118: virtual IntPtr OnMessageReceivedCallback(IntPtr hWnd, UINT message, WPARAM wParam, LPARAM lParam)
119: = IMessageCallback::OnMessageReceivedCallback;
120:
121: public:
122: ///<summary>
123: ///Constructor for the message only window instance
124: ///</summary>
125: ///<remarks>
126: ///With this instance you can receive Windows Messages without the need to use a WinForm instance.
127: ///Register a callback on the <see cref="MessageReceived"/> event and call the
128: ///<see cref="StartMessageLoop"/> to start receiving windows messages.
129: /// Call the <see cref="StopMessageLoop"/> to stop receiving windows messages.
130: /// Dispose the instance whenever you do not need it anymore
131: ///</remarks>
132: MessageOnlyWindow();
133:
134: /// <summary>
135: /// Fires an event that represents a single windows message that has been sent to
136: /// the message-only-window
137: /// </summary>
138: event Func<WindowsMessage, IntPtr> ^MessageReceived;
139:
140: /// <summary>
141: /// Ask the message pump thread to stop.
142: /// </summary>
143: /// <remarks>
144: /// The message pump thread is the thread that called the <see cref="StartMessageLoop"/>.
145: /// The stop operations is asynchronously, This call trigger a WM_QUIT message that will
146: /// eventually stop the loop
147: /// </remarks>
148: void StopMessageLoop() { m_pNativeMessageOnlyWindow->StopMessageLoop(); } 149:
150: /// <summary>
151: /// Start the message loop in the context of the calling thread
152: /// </summary>
153: /// <remarks>
154: /// Call this method in the context of the thread that triggered the Windows Messages source.
155: /// This is a blocking call, the message loop is in the context of the calling thread.
156: /// This method will be return only after a call to the <see cref="StopMessageLoop"/>.
157: /// </remarks>
158: void StartMessageLoop() { m_pNativeMessageOnlyWindow->StartMessageLoop(); } 159:
160: /// <summary>
161: /// The message-only-window handle
162: /// </summary>
163: property IntPtr WindowHandle { IntPtr get() 164: { return IntPtr(m_pNativeMessageOnlyWindow->GetWindowHandle()); }} 165:
166: /// <summary>
167: /// clear native resources
168: /// </summary>
169: ~MessageOnlyWindow() { delete m_pNativeMessageOnlyWindow; } 170: };
171: }
172:
The MessagePump.cpp code file
1:
2: //
3: // Messagepump.cpp
4:
5: #include "stdafx.h"
6:
7: #include "MessagePump.h"
8:
9: using namespace System;
10: using namespace System::Runtime::InteropServices;
11:
12:
13: namespace MessagePump
14: { 15: MessageOnlyWindow::MessageOnlyWindow()
16: { 17: m_pNativeMessageOnlyWindow = new NativeMessageOnlyWindow(gcroot<IMessageCallback ^>(this));
18: }
19:
20: IntPtr MessageOnlyWindow::OnMessageReceivedCallback(IntPtr hWnd, UINT message, WPARAM wParam, LPARAM lParam)
21: { 22: return MessageReceived(WindowsMessage(hWnd, message, wParam, lParam));
23: }
24:
25: hash_map<HWND, NativeMessageOnlyWindow *> NativeMessageOnlyWindow::s_windowsMap;
26:
27: void NativeMessageOnlyWindow::Create()
28: { 29: //Create a Window that can only receive messages
30: m_hWnd = CreateWindow(L"Message", L"", 0, 0, 0, 0, 0, HWND_MESSAGE , nullptr, GetModuleHandle(0), nullptr);
31: if (m_hWnd == nullptr)
32: Marshal::ThrowExceptionForHR(GetLastError());
33:
34: //Set the Window's WndProc to our Windows Procedure
35: auto result = SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc);
36: if (result == 0)
37: Marshal::ThrowExceptionForHR(GetLastError());
38:
39: //Keep the correlation between the hWnd and the MessageOnlyWindow instance
40: s_windowsMap[m_hWnd] = this;
41: }
42:
43: void NativeMessageOnlyWindow::StopMessageLoop()
44: { 45: //Two stop marks
46:
47: m_bStop = true;
48: SendMessage(m_hWnd, WM_APPSTOP, 0, 0);
49: }
50:
51:
52: void NativeMessageOnlyWindow::StartMessageLoop()
53: { 54: m_bStop = false;
55: //Create the Windows
56: Create();
57:
58: MSG msg;
59:
60: //The traditional message loop
61: while (GetMessage(&msg, NULL, 0, 0))
62: { 63: TranslateMessage(&msg);
64: DispatchMessage(&msg);
65: }
66: }
67:
68: NativeMessageOnlyWindow::~NativeMessageOnlyWindow()
69: { 70: if (m_hWnd == nullptr)
71: return;
72: StopMessageLoop();
73: }
74:
75: LRESULT CALLBACK NativeMessageOnlyWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
76: { 77: //Find the correlated NativeMessageOnlyWindow instance
78: auto iter = s_windowsMap.find(hWnd);
79: if (iter == s_windowsMap.end()) //instance not found
80: { 81: OutputDebugString(L"NativeMessageOnlyWindow, a message can't be dispatched, HWDN not found.");
82: return DefWindowProc(hWnd, message, wParam, lParam);
83: }
84:
85: auto This = iter->second;
86:
87: if (message == WM_APPSTOP && This->m_bStop) //Is Stop
88: { 89: PostQuitMessage(0); //Send WM_QUIT to stop the message loop
90: s_windowsMap.erase(iter); //Remove the NativeMessageOnlyWindow instance from the hash map
91: This->m_hWnd = nullptr;
92:
93: return DefWindowProc(hWnd, message, wParam, lParam);
94: }
95:
96: //Trigger the callback call to the managed wrapper instance
97: return reinterpret_cast<LRESULT>(
98: This->_messageCallback->OnMessageReceivedCallback(IntPtr(hWnd), message, wParam, lParam).ToPointer());
99: }
100: }
101:
1:
2: using System;
3: using System.Runtime.InteropServices;
4: using System.Collections.Generic;
5: using System.Linq;
6: using System.Threading;
7: using System.Threading.Tasks;
8: using MessagePump;
9: using Microsoft.VisualStudio.TestTools.UnitTesting;
10:
11: namespace TestMessagePump
12: { 13: /// <summary>
14: /// Summary description for UnitTest1
15: /// </summary>
16: [TestClass]
17: public class UnitTestMessagePump
18: { 19: /// <summary>
20: ///Gets or sets the test context which provides
21: ///information about and functionality for the current test run.
22: ///</summary>
23: public TestContext TestContext { get; set; } 24:
25: [DllImport("user32.dll", CharSet = CharSet.Auto)] 26: static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, UIntPtr wParam, IntPtr lParam);
27:
28:
29: [TestMethod]
30: public void TestMethodSendingMessages()
31: { 32: var messagePump = new MessageOnlyWindow();
33: var messageList = new List<WindowsMessage>();
34: var loopHasStarted = new ManualResetEventSlim(false);
35: // ReSharper disable InconsistentNaming
36: const uint WM_APP = 0x8000;
37: // ReSharper restore InconsistentNaming
38:
39: messagePump.MessageReceived += message =>
40: { 41: lock (messageList)
42: { 43: messageList.Add(message);
44: }
45: loopHasStarted.Set();
46: return IntPtr.Zero;
47: };
48: bool loopHasStopped = false;
49:
50: Task.Factory.StartNew(() =>
51: { 52: messagePump.StartMessageLoop();
53: loopHasStopped = true;
54: });
55:
56: Thread.Sleep(1000);
57: SendMessage(messagePump.WindowHandle, WM_APP, UIntPtr.Zero, IntPtr.Zero);
58: var result = loopHasStarted.Wait(1000);
59: Assert.AreNotEqual(result, false, "Timeout, a message hasn't arrived");
60:
61: SendMessage(messagePump.WindowHandle, WM_APP + 1, UIntPtr.Zero, IntPtr.Zero);
62: SendMessage(messagePump.WindowHandle, WM_APP + 2, UIntPtr.Zero, IntPtr.Zero);
63:
64: Assert.AreNotEqual(loopHasStopped, true,
65: "The message Loop should not be stopped until we call the StopMessageLoop");
66:
67: messagePump.StopMessageLoop();
68: Thread.Sleep(3000);
69: Assert.AreEqual(loopHasStopped, true,
70: "The message Loop should stopped since we have called the StopMessageLoop");
71:
72: var numberOfAppMessages =
73: messageList.Count(
74: message =>
75: message.Message == WM_APP || message.Message == WM_APP + 1 || message.Message == WM_APP + 2);
76:
77: Assert.AreEqual(numberOfAppMessages, 3, "There should be three WM_APP + x Messages in the list");
78:
79: }
80: }
81: }
82:
Download the VS 2010 solution from here.
Enjoy!