[ 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