C++: Capture Windows Messages from an MFC app without using MFC

Let's say you need your C++ dll to communicate with a third party dll which uses MFC and Windows Messages to send data and notifications. The easiest way out would be to write an MFC application with a dialog which handled those messages. But let's say your C++ dll is at a very low layer and you don't want GUI code there. At least not openly.

Well, there is no avoiding the Windows Messages, so we'll be using, at least, the Win32 unmanaged API. So how do we capture Windows Messages without showing a window?

Some basic things you should know first:

  1. To capture Windows Messages, you must create a window (associated with a HWND) and use its message loop
  2. There is one message loop per thread. This implies that the message-handling window must live in the same thread as the message-generating code
  3. A window can be created as a Message-Only window, in MSDN jargon. And that's what we'll do

Once I learned all of the above, it was easy to do. Here's some code:

void CSomeDllWrapper::StartMessageLoop(){
   m_hMessageLoopThread = ::CreateThread(
      NULL, 0, CSomeDllWrapper::MessageLoopThread,
      this, 0 , &m_MessageLoopThreadID
   );
}

DWORD WINAPI CSomeDllWrapper::MessageLoopThread( void * pParams ){
   HWND hwnd;
   MSG msg;
   WNDCLASSEX wincl;
   const string windowClass = "SOME_DLL_WRAPPER_MESSAGE_WINDOW";

   // Window class
   wincl.hInstance = GetModuleHandle(NULL);
   wincl.lpszClassName = windowClass.c_str();
   wincl.lpfnWndProc = ::DefWindowProc;
   wincl.style = CS_DBLCLKS;
   wincl.cbSize = sizeof (WNDCLASSEX);
   wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
   wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
   wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
   wincl.lpszMenuName = NULL;
   wincl.cbClsExtra = 0;
   wincl.cbWndExtra = 0;
   wincl.hbrBackground = GetSysColorBrush(COLOR_BACKGROUND);
   if(!RegisterClassEx(&wincl)){
      HandleRegistratioFailure();
   }

   hwnd = CreateWindowEx(
      0,   //Default ExStyle
      windowClass.c_str(), //Window class
      "SomeDllWrapper", //Window Title
      WS_OVERLAPPEDWINDOW, //Default Style
      CW_USEDEFAULT,  //Let Windows decide position
      CW_USEDEFAULT,          
      10,   //Width
      10,   //Height
      HWND_MESSAGE,  //Message-only window
      NULL,   //No Menu
      GetModuleHandle(NULL), //Handle to application
      NULL   //Window creation data
   );

   CSomeDllWrapper* pThis = reinterpret_cast<CSomeDllWrapper*>( pParams );
   pThis->InitSomeLibrary(); //This makes the library start firing messages
   //Start message loop
   while(TRUE == GetMessage(&msg, NULL, 0, 0)){
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return msg.wParam;
}

void CSomeDllWrapper::InitSomeLibrary(){
    //... Library-specific init code
    
    //This is how my dll associates its Windows messages, yours might differ
    void *customerData = this;
    ::RegisterCallbackFunc( CSomeDllWrapper::MessageProc, customerData );

    //... More Library-specific init code
}

void __stdcall CSomeDllWrapper::MessageProc(int msgType, LPSTR pMsg, void* pCustomerData ){
   CSomeDllWrapper* pSomeDllObject = reinterpret_cast<CSomeDllWrapper*>( pCustomerData );

   switch (msgType) {
      case IDC_SOME_MSG:
         //Do stuff
         break;
         //You get the rest
      }
   }
}

Comments

Popular posts from this blog

VB.NET: Raise base class events from a derived class

Apache Kafka - I - High level architecture and concepts

Upgrading Lodash from 3.x to 4.x