C++: Capturar Windows Messages de una aplicación MFC sin usar MFC

Supongamos que necesitamos que nuestra dll C++ se comunique con una biblioteca externa que use MFC y Windows Messages para enviar datos y notificaciones. La forma más sencilla de establecer esa comunicación sería escribiendo una aplicación MFC con un diálogo (una clase que herede de CDialog) que maneje los mensajes generados por la biblioteca externa. Pero supongamos que nuestra dll C++ está en una capa de bajo nivel y no queremos meter una interfaz de usuario ahí. Al menos, no abiertamente.

No hay forma de evitar los Windows Messages, así que tendremos que usar, mínimamente, la API Win32 nativa. Entonces, ¿Cómo capturamos Windows Messages sin mostrar una ventana?

Antes de seguir, debemos saber lo siguiente:

  1. Para capturar Windows Messages, hay que crear una ventana (asociada a un HWND) y usar su message loop
  2. Hay uno y sólo un message loop por thread. Esto implica que la ventana que atrape los mensajes y su message loop deben vivir en el mismo thread que el código que genera mensajes.
  3. Una ventana puede ser creada como Message-Only window, en la jerga MSDN. Y eso es lo que haremos

Una vez que se sabe lo anterior, es fácil resolver el problema inicial. El código:

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