DCSIMG
December 2011 - Posts - class Alon : public CPP, public Architecture

class Alon : public CPP, public Architecture

The smart virtual home of Alon Fliess

Syndication

December 2011 - Posts

 

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.

image

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!