[ contenidos | #winprog ]

Comprender el Bucle de Mensajes

Entender el Bucle de Mensajes y la estructura de envío de mensajes es escencial para escribir hasta el programa mas trivial. Ahora que hemos visto un poco sobre manejo de mensajes deberíamos mirar en profundidad todo este proceso porque las cosas se tornarán mas confusas si no entendemos por qué las cosas suceden de la forma en que lo hacen.

¿Que es un Mensaje?

Un mensaje es un valor entero. Si observas en tus archivos de cabecera (lo cual es una buena práctica cuando investigamos el funcionamiento de las API`s) puedes encontrar cosas como esta:

#define WM_INITDIALOG                   0x0110
#define WM_COMMAND                      0x0111

#define WM_LBUTTONDOWN                  0x0201
...y asi siguiendo. Los mensajes son usados para comunicar la mayoría de las cosas en windows, al menos en los niveles básicos. Si quieres que una ventana o control (el cual es una ventana especializada) haga algo, debes enviarle un mensaje. Si otra ventana quiere que vos hagas algo, entoces te envía un mensaje. Si ocurre un evento, como cuando el usuario mueve el mouse, presiona el teclado, etc... entonces el sistema la envía un mensaje a la ventana afectada. Dicha ventana recibe el mensaje y actúa adecuadamente.

Cada mensaje en windows puede tener hasta dos parámetros, wparam y lparam. Originalmente wparam tenía 16 bits y lparam tenía 32 bits, pero en Win32 ambos son de 32 bits. No todos los mensajes usan estos parámetros y cada mensaje los usa de manera diferente. Por ejemplo, el mensaje WM_CLOSE no usa ninguno de los dos y por lo tanto deberias ignorarlos. El mensaje WM_COMMAND usa ambos, wparam contiene dos valores HIWORD(wparam) es la notificación del mensaje (si se aplica) y LOWORD(wparam) es el ID del control o menú que envió el mensaje. lparam es el HWND (Windows Handle) del control que envió el mensaje o nulo (NULL) si el mensaje no proviene de un control.

HIWORD( ) y LOWORD( ) son macros definidas por windows que simplemente retornan la palabra superior (High Word) y la palabra inferior (Low Word), respectivamente, de un valor de 32 bits. En Win32 una palabra es un valor de 16 bits, haciendo un DWord (palabra doble) un valor de 32 bits.

Para enviar un mensaje puedes usar PostMessage( ) o SendMessage( ). PostMessage( ) pone el mensaje en la Cola de Mensajes y retorna inmediatamente. Esto significa que una vez que la llamada a PostMessage( ) se completa el mensaje puede o no puede haber sido procesado aún. SendMessage( ) envía el mensaje directamente a windows y no retorna hasta que windows haya finalizado de procesarlo. Si quisieramos cerrar una ventana, podríamos enviarle a un mensaje WM_CLOSE de la siguiente manera: PostMessage(hWnd,WM_CLOSE,0,0). Lo cual podría tener el mismo efecto que hacer click en el boton cerrar de la ventana. Observa que wparam y lparam son ambos 0. Esto es debido a que, como mencionamos, no se usan con el mensaje WM_CLOSE.

Diálogos

Una vez que comiences a usar cajas de diálogo necesitarás enviar mensajes a los controles para poder comunicarte con ellos. Puedes hacer esto primero através de GetDlgItem( ) (utilizando el ID como parámetro) para obtener el handle al control y luego usar SendMessage( ). O puedes usar SendDlgItemMsg( ), el cual combina ambos pasos. Como parámetros utilizamos el handle de la ventana y un ID de un hijo y SendDlgItemMsg( ) retorna el handle del hijo y le envía el mensaje. Ambas formas de enviar mensajes funcionan bien en todas las ventanas, no solamente en cajas de diálogo.

¿Que es la Cola de Mensajes?

Digamos que te encuentras ocupado manejando el mensaje WM_PAINT y repentinamente el usuario ingresa un gran cantidad de datos por el teclado. ¿Que debería suceder?. ¿Deberías interrumpir el procesamiento de WM_PAINT (dejar de dibujar la pantalla) para atender el teclado, o deberías simplemente descartar lo que el usuario ingresó por el teclado?. Error!, obviamente ninguna de éstas opciones son rasonables. Para esto tenemos la Cola de Mensajes. Cada vez que se recibe un mensaje, dicho mensaje es encolado en la cola de mensajes y cada vez que se quiere procesar un mensaje dicho mensaje es removido de la cola (valga la redundancia). Esto asegura que no se pierdan mensajes, si estás procesando uno, entonces los otros serán encolados hasta que los recuperes de la cola.

¿Que es el Bucle de Mensajes?

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
}
  1. El Bucle de Mensajes llama a GetMessage( ), el cual busca en tu cola de mensajes. Si la cola de mensajes está vacía, tu programa se detiene y espera hasta que haya un mensaje (se bloquea).
  2. Cuando ocurre un evento, causando que un mensaje sea puesto en la cola (por ejemplo el sistema registra un click del mouse) GetMessage( ) retorna un valor positivo indicando que hay un mensaje para ser procesado y que ha llenado los campos de la estructura MSG que le pasamos.
  3. Tomamos el mensaje (en la variable Msg) y se lo pasamos a TransalateMessage( ), éste hace un proceso adicional traduciendo mensajes con teclas virtuales, en mensajes con caracteres. Este paso puede ser opcional, pero ciertas cosas no funcionarán si no lo utilizamos.
  4. Una vez que finalizamos de traducir el mensaje, se lo pasamos DispatchMessage( ). Lo que DispatchMessage( ) hace es tomar el mensaje, chequear a que ventana está destinado y buscar el Window Procedure de dicha ventana. Luego llama a dicho procedimiento enviando como parámetro el handle de la ventana, el mensaje, wparam y lparam.
  5. En el Windows Procedure chequeamos el mensaje y sus parámetros y luego hacemos lo que desamos con ellos. Si no estamos manejando dicho mensaje, por lo menos siempre llamamos a DefWindowProc( ) el cual realizará las acciones "por default" (lo cual significa que a veces no hace nada).
  6. Una vez que hallas terminado de procesar el mensaje el Window Procedure retorna, DispatchMessage() retorna y regresamos al comienzo del bucle.

Este es un concepto muy importante para los programas de windows. El Windows Procedure no es mágicamente llamado por el sistema, de hecho lo llamamos indirectamente através de DispatchMessage( ). Si deseas, puedes usar GetWindowLong( ) en el handle de la ventana a la cual está destinado el mensaje para buscar el Window Procedure de la misma y llamarlo directamente!

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
    fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}

He intentado esto con el código del ejemplo anterior y funciona. Sin embargo hay varios aspectos como la traducción UNICODE/ANSI, entre otros, que este método no tendrá en cuenta y probablemente haga fallar hasta la aplicación mas trivial. Por lo tanto hazlo como prueba pero no lo hagas en el código real.

Observa que hemos usado GetWindowLong( ) para recuperar el Window Procedure asociado con la ventana, pero ¿por que no lo llamamos directamente?. Bien, nuestro bucle de mensajes es responsable de TODAS las ventanas en nuestro programa, esto incluye cosas como botones, listas, etc.. que tienen su propio Window Procedure asociado, por lo tanto necesitamos asegurarnos que llamamos al procedimiento correcto para una cierta ventana. Debido a que más de una ventana puede usar el mismo Window Procedure, usamos el primer parámetro (el handle de la ventana) para decirle al Window Procedure a que ventana está destinado el mensaje.

Como puedes ver tu aplicación pasa la mayor parte del tiempo dando vueltas y vueltas en el bucle de mensajes donde alegremente envías mensajes a las felices ventanas que los procesarán. ¿Pero que haces cuando quieres que tu programa salga? Debido a que estamos usando un bucle while( ) si GetMessage( ) retornara FALSE (i.e 0), el bucle podría finalizar y de esta manera podríamos alcanzar el final de nuestro WinMain( ), es decir salir del programa. Esto es exactamente lo que PostQuitMessage() realiza; pone un WM_QUIT en la cola y GetMessage( ) en vez de retornar un valor positivo, llena la estrucura MSG y retorna 0. Hasta este punto el campo wparam de la variable Msg contiene el valor que le pasamos en PostQuitMessage( ) y podemos ignorarlo o retornarlo al WinMain( ) donde se usará como valor de salida cuando el programa finalice.

IMPORTANTE: GetMessage( ) retornará -1 cuando encuentre un error. Asegúrate de recordar esto o te sorprenderá en algún momento... Aún cuando GetMessage( ) está definido para retornar un BOOL (Booleano), éste puede retornar valores distinos de TRUE o FALSE, debido a que BOOL está definido como UINT (unsigned int). Los siguientes son ejemplos de código que puede parecer que funciona pero no procesarán ciertas condiciones correctamente.

    while(GetMessage(&Msg, NULL, 0, 0))
    while(GetMessage(&Msg, NULL, 0, 0) != 0)
    while(GetMessage(&Msg, NULL, 0, 0) == TRUE)
Los segmentos de código anterior están mal! Quizás has notado que he usado el primero de ellos en lo que va del tutorial, asi como lo mencioné, este funciona bien siempre y cuando GetMessage( ) no falle y cuando tu código es correcto, no lo hace. Sin embargo no he tenido en cuenta que si estás leyendo este tutorial tu código probablemente no sea correcto en todo momento y GetMessage( ) puede fallar en algún punto. Ya lo he corregido pero perdóname si he olvidado algunos puntos.

    while(GetMessage(&Msg, NULL, 0, 0) > 0)

Deberían usarse este o códigos que tengam siempre el mismo efecto. Espero que ahora tengas un mejor entendimiento del bucle de mensajes, si no es así, no te asustes, las cosas tendrán mas sentido después que hayan sido usadas un par de veces.


Copyright © 1998-2003, Brook Miles (theForger). All rights reserved.

Versión en Español: Federico Pizarro - 2003