En este tutorial, construiremos un programa para Windows que despliegue una ventana completamente funcional sobre el escritorio.
Puedes bajar un archivo de ejemplo aquí
Los programas de Windows realizan la parte pesada del trabajo de programación a través funciones API para sus GUI (Graphic User Interface = Interface de Usuario Gráfica). Esto beneficia a los usuarios y a los programadores. A los ususarios, porque no tienen que aprender cómo navegar por la GUI de cada nuevo programa, ya que las GUIs de los programas Windows son semejantes. A los programadores, porque tienen ya a su disposición las rutinas GUI, probadas y listas para ser usadas. El lado negativo para los programadores es la creciente complejidad involucrada. Con el fin de crear o de manipular cualquiera de los objetos GUI, tales como ventanas, menúes o iconos, los programadores deben seguir un "récipe" estricto. Pero esto puede ser superado a través de programación modular o siguiendo el paradigma de Progranación orientada a Objetos (OOP = Object Oriented Programming).
Esbozaré los pasos requeridos para crear una ventana sobre el escritorio:
al comienzo de tu archivo .asm. Esta directiva dice a MASM que tu programa hará uso de funciones es esa librería de importación. Si tu programa llama funciones en más de una librería, entonces hay que agregar una línea includelib para cada librería que se vaya a usar. Usando la directiva includelib no tendrás que preocuparte de las librerías de importación en el momento de enlazar. Puedes usar el conmutador del enlazador /LIBPATH para decirle a Link donde están todas las librerías.
Cuando declares prototipos de funciones de la API, estructuras, o constantes en un archivo include, trata de emplear los nombres originales usados en archivos include de Windows, cuidando siempre la diferencia entre mayúsculas y minúsculas. Esto te liberará de dolores de cabeza cuando necesites buscar información sobre algún elemento en la referencia de la API de Win32.
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.DATA
; data inicializada
ClassName db "SimpleWinClass",0
; el nombre de nuestra clase de ventana
AppName db "Our First Window",0
; el nombre de nuestra ventana
.DATA?
; data no inicializada
hInstance HINSTANCE ?
; manejador de instancia
de nuestro programa
CommandLine LPSTR ?
.CODE
;
Aquí comienza nuestro código
start:
invoke GetModuleHandle, NULL
; obtener el manejador de instancia del programa.
;
En Win32, hmodule==hinstance
mov hInstance,eax
invoke GetCommandLine
; Obtener la línea de comando. No hay que llamar esta
función
; si el programa no procesa la
línea de comando
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine,
SW_SHOWDEFAULT
; llamar la función principal
invoke ExitProcess, eax ;
quitar nuestro programa. El código de salida es devuelto en eax desde
WinMain.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ;
crear variables locales en la pila (stack)
LOCAL msg:MSG
LOCAL hwnd:HWND
mov
wc.cbSize,SIZEOF WNDCLASSEX
; Llenar los valores de los miembros de wc
mov wc.style, CS_HREDRAW
or CS_VREDRAW
mov wc.lpfnWndProc,
OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET
ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
; registrar nuestra clase de ventana
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ;
desplegar nuestra ventana en el escritorio
invoke UpdateWindow, hwnd ;
refrescar el area cliente
.WHILE TRUE ;
Introducir en bucle (loop) de mensajes
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ;
Regresar el código de salida en eax
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT,
wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ;
si el usuario cierra nuestra ventana
invoke PostQuitMessage,NULL
; quitar nuestra aplicación
.ELSE
invoke
DefWindowProc,hWnd,uMsg,wParam,lParam ;
Procesar el mensaje por defecto
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Puede parecer desconcertante que un simple programa de Windows requiera tanto código. Pero muchas de estas rutinas son verdaderamente un código *plantilla* que puede copiarse de un archivo de código fuente a otro. O si prefieres, podrías ensamblar algunas de estas rutinas en una librería para ser usadas como rutinas de prólogo o de epílogo. Puedes escribir solamente las rutinas en la función WinMain. En realidad, esto es lo que hacen los compiladores en C: permiten escribir las rutinas en WinMain sin que tengas que preocuparte de otros asuntos ruitinarios y domésticos. Todo lo que hay que hacer es tener una función llamada WinMain, sino los compiladores C no serán capaces de combinar tus rutinas con el prólogo y el epílogo. No exiten estas restricciones con lenguaje ensamblador. Puedes usar otros nombres de funciones en vez de WinMain o no emplear esta función en ninguna parte.
Bueno, ahora prepárate. Esto va a ser largo, un largo tutorial ¡Vamos a analizar este programa hasta la muerte!!
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
Las primeras tres líneas son "necesarias". .386 dice a MASM que intentamos usar en nuestro programa el conjunto de instrucciones para los porcesadores 80386 o superiores. .model flat,stdcall a MASM que nuestro programa usa el modelo de direccionamiento de memoria plana (flat). También usaremos la convención de paso de parámetros stdcall como la convención por defecto del programa.
Lo siguiente constituye el prototipo
para la función WinMain. Ya que llamaremos más tarde a WinMain,
debemos definir su prototipo de función primero para que podamos invocarle.
Debemos incluir windows.inc al comienzo del codigo fuente.
Contiene estructuras y constantes importantes que son usadas por nuestro programa.
El archivo include, windows.inc, es un archivo de texto. Puedes abrirlo con
cualquier editor de texto. Por favor, nota que windows.inc no contiene todas
las estructuras y constantes (todavía). hutch y yo estamos trabajando
en ello. Puedes agregarle elementos nuevos si ellos no están en el archivo.
Nuestro programa llama las funciones API que residen en user32.dll (CreateWindowEx, RegisterWindowClassEx, por ejemplo) y kernel32.dll (ExitProcess), así que debemos enlazar nuestro programa a esas librerías de importación. La próxima cuestión es: ¿cómo podemos saber cuál librería debe ser enlazada con nuestro programa? La respuesta es : debes saber donde residen las funciones API llamadas por el programa. Por ejemplo, si llmas una función API en gdi32.dll, debes enlazarla con gdi32.lib.
Esta es la manera como lo hace MASM. El método de TASM para importar librerías a través del enlace es mucho más simple: sólo hay que enlazar un archivo : import32.lib.
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
Siguen las secciones "DATA".
En .DATA, declaramos dos cadenas de caracteres terminadas en cero (cadenas ASCIIZ): ClassName, que es el nombre de nuestra clase de ventana, y AppName, el nombre de nuestra ventana. Nota que las dos variables están inicializadas.
En .DATA?, son declaradas tres variables: hInstance (manejador [handle] de instancia de nuestro programa), CommandLine (linea de comando de nuestro programa), y CommandShow (estado de nuestro programa en su primera aparición). Los tipos no familiares de datos, HINSTANCE y LPSTR, realmente son nombres nuevos para DWORD. Puedes verificarlo en windows.inc. Nota que todas las variables en la sección .DATA? no están inicializadas, es decir, no tienen que tener ningún valor especíifico al inicio, pero queremos reservarle un espacio para su usarlas en el futuro.
.CODE contiene todas las instrucciones. Tus instrucciones deben residir entre <etiqueta inicio> (start) y terminar en <etiqueta inicio> (end start). El nombre de las etiquetas es importante. Puedes llamarla de la forma que quieras siempre y cuando no violes las convenciones para los nombres de MASM.
Nuestra primera instrucción llama a GetModuleHandle
para recuperar el manejador de instancia de nuestro programa. Bajo Win32, el
manejador de la instancia y el manejador del módulo son una y la misma
cosa. Se puede pensar en el manejador de instancia como el ID de nuestro programa.
Es usado como parámetro en algunas funciones API que nuestro programa
debe llamar, así que generalmente es una buena idea obtenerlo al comienzo
del programa.
Nota: Realmente bajo win32, el manejador de instancia es la dirección lineal de nuestro programa en la memoria.
Al regresar a una función de Win32, el valor regresado, si hay alguno, puede encontrarse en el registro eax. Todos los demás valores son regresados a través de variables pasadas en la lista de parámetros de la función que va a ser llamada.
Cuando se llama a una función Win32, casi siempre preservará los registros de segmento y los registros ebx, edi, esi y ebp. Al contrario, los registros eax, ecx y edx son considerados como registros dinámicos y siempre sus valores son indeterminados e impredecibles cuando retorna una función Win32.
Nota: No esperes que los valores de eax, ecx, edx sean preservados durante las llamadas a una función API.
La línea inferior establece que: cuando se llama a una fucnión API, se espera que regrese el valor en eax. Si cualquiera de las funciones que creamos es llamada por Windows, también debe seguir la siguiente regla: preservar y restablecer los valores de los registros de segmentos ebx, edi, esi y ebp cuando la función regrese, sino el programa se quebrará (crash) de inmediato, esto incluye el procedimiento de ventana y las funciones callback de ventanas.
La llamada a GetCommandLine es inecesaria si el programa no procesa la línea de comando. En este ejemplo muestro como llamarla en caso que sea necesario en un programa.
Lo siguiente es la llamada a WinMain. Aquí recibe cuatro parámetros: el manejador de instancia de nuestro programa, el manejador de instancia de la instancia previa del programa, la línea de comando y el estado de la ventana en su primera aparición. Bajo Win32, no hay instancia previa. Cada programa está aislado en su espacio de direcciones, así que el valor de hPrevInst siempre es 0. Esto es uno de los restos de los días de Win16 cuando todas las instancias de un programa corrían en el mismo espacio de direcciones y una instancia necesitaba saber si era la primera. En win16, si hPrevInst es NULL entonces es la primera instancia.
Nota: Esta función no tiene que ser declarada como WinMain. En realidad, hay completa libertad a este respecto. Ni siquiera hay que usar siempre una función equivalente a WinMain. Se puede pegar el código dentro de la función WinMain inmediatamente después de GetCommandLine y el programa funcionará perfectamente.
Al regresar de WinMain, eax tiene el código de salida. Pasamos el código de salida como parámetro de ExitProcess, que terminará nuestra aplicación.
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
La línea de arriba forma la declaración de la función WinMain. Nota que los pares parámetro:tipo que siguen a la directiva PROC. Son parámetros queWinMain recibe desde la instrucción que hace la llamada [caller]. Puedes referirte a estos parámetros por nombre en vez de a través de la manipulación de la pila. Además, MASM generará los códigos de prólogo y epílogo para la función. Así que no tenemos que preocuparnos del marco de la pila cuando la función entre (enter) y salga (exit).
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
La directiva LOCAL localiza memoria de la pila para las variables locales usadas en la función. El conjunto de directivas LOCAL debe estar ubicado inmediatamente abajo de la directiva PROC.
La directiva LOCAL es seguida inmediatamente por <nombre de la variable local>:<tipo de variable>. Así que LOCAL wc:WNDCLASSEX le dice a MASM que localize memoria de la pila con un espacio equivalente al tamaño de la estructura WNDCLASSEX para la variable llamada wc. Podemos hacer referencia a wc en nuestro código sin ninguna dificultad en la manipulación de la pila. Creo que esto es realmente una bendición. El aspecto negativo de esto es que las variables locales no pueden ser usadas fuera de la función porque ellas son creadas para ser destruidas inmediatamante cuando la función retorna a la rutina desde la cual fue llamada. Otra contrapartida es que no se pueden inicializar variables locales automáticamente porque ellas son localizadas dinámicamente en la memoria de la pila cuando la función es introducida (entered). Hay que asignarlas manualmente con los valores deseados después de las directivas LOCAL.
mov
wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW
or CS_VREDRAW
mov wc.lpfnWndProc,
OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET
ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
Las líneas intimidantes de arriba son realmente comprensibles en cuanto concepto. Toma varias líneas de instrucciones realizar la operación ahí implicada. El concepto detrás de todas estas líneas es el de clase de ventana (window class). Una clase de ventana no es más que un anteproyecto o especificación de una ventana. Define algunas de las características importantes de una ventana tales como un icono, su cursor, la función que se resposabiliza de ella, su color etc. Una ventana se crea a partir de una clase de ventana. Este es una especie de concepto orientado a objeto. Si se quiere crear más de una ventana con las mismas características, lo razonable es almacenar todas estas características en un solo lugar y referirse a ellas cuando sea necesario. Este esquema salva gran cantidad de memoria evitando duplicación de código. Hay que recordar que Windows fue diseñado cuando los chips de memoria eran prohibitivos ya que una computadora tenía apenas 1 MB de memoria. Windows debía ser muy eficiente al usar recursos de memorias escasos. El punto es: si defines tu propia ventana, debes llenar las características de tu ventana en una estructura WNDCLASS o WNDCLASSEX y llamar a RegisterClass o RegisterClassEx antes de crear la ventana. Sólo hay que registrar la clase de ventana una vez para cada tipo de ventana que se quiera crear desde una clase de ventana.
Windows tiene varias clases de ventanas predefinidas, tales como botón (button) y caja de edición (edit box). Para estas ventanas (o controles), no tienes que registrar una clase de venana, sólo hay que llamara a CreateWindowEx con el nombre de la clase predefinido.
El miembro más importante en WNDCLASSEX es lpfnWndProc. lpfn se concibe como un puntero largo a una función. Bajo Win32, no hay puntero "cercano" o "lejano" pointer, sino sólo puntero, debido al nuevo modelo de memoria FLAT. Pero esto también es otro de los restos de los días de Win16. Cada clase de ventana debe estar asociada con la función llmada procedimiento de ventana. El procedimiento de ventana es la función responsable por el manejo de mensajes de todas las ventanas creadas a partir de la clase de ventana asociada. Windows enviará mensajes al procedimiento de ventana para notificarle sobre eventos importantes concernientes a la ventana de la cual el procedimiento es responsable, tal como el uso del teclado o la entrada del ratón. Le toca al procedimiento de ventana responder inteligentemante a cada evento que recibe la ventana. Seguro que pasarás bastante tiempo escribiendo manejadores de evento en el procedimiento de ventana.
Describo abajo los miembros de WNDCLASSEX:
WNDCLASSEX STRUCT DWORD
cbSize
DWORD ?
style
DWORD ?
lpfnWndProc
DWORD ?
cbClsExtra
DWORD ?
cbWndExtra
DWORD ?
hInstance
DWORD ?
hIcon
DWORD ?
hCursor
DWORD ?
hbrBackground DWORD
?
lpszMenuName DWORD
?
lpszClassName DWORD
?
hIconSm
DWORD ?
WNDCLASSEX ENDS
cbSize: Tamaño
de la estructura WNDCLASSEX en bytes. Podemos usar el operador SIZEOF para obtener
este valor.
style: El estilo para
las ventanas creadas a partir de esta clase. Se pueden combinar varios tipos
de estilo combinando el operador "or".
lpfnWndProc: La dirección
del procedimiento de ventana responsable para las ventanas creadas a partir
de esta clase.
cbClsExtra: Especifica
el número de bytes extra para localizar la siguiente estructura de clase
de ventana. El sistema operativo inicializa los bytes poniéndolos en
cero. Puedes almacenar aquí datos específcos de la clase de ventana.
cbWndExtra: :
Especifica el número de bytes extra para localizar the
window instance. El sistema operativo
inicializa los bytes poniéndolos en cero.
Si una aplicación usa la estructura WNDCLASS para registrar un cuadro
de diálogo creado al usar la directiva CLASS en el archivo de recurso,
debe poner este miembro en DLGWINDOWEXTRA.
hInstance: Manejador
de instancia del módulo.
hIcon: Manejador [handle]
del icono. Se obtiene llamando a LoadIcon.
hCursor: Manejador
del cursor. Se obtiene llamando
a LoadCursor.
hbrBackground: Color
de fondo de la ventana creada a partir de esta clase.
lpszMenuName: Manejador
del menú por defecto para la
ventana creada a partir de esta clase.
lpszClassName: Nombre
para esta clase de ventana.
hIconSm: Manejador
del icono pequeño asociado con la clse de ventana. Si este miembro es
NULL, el sistema busca el recurso de icono especificado por el miembro hIcon
para un icono de tamaño apropiado para ser usado como icono pequeño.
invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
Después de registrar la clase de ventana, podemos llamar a CreateWindowEx para crear nuestra ventana basada en la clase de ventana propuesta. Nota que hay 12 parámetros para esta función.
CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD ,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD
Veamos la descripción detallada de cada parámetro:
dwExStyle: Estilos extra de ventana. Es el nuevo parámetro agregado a la antigua función CreateWindow. Aquí puedes poner estilos nuevos para Windows 95 y NT. Puedes especificar tu estilo de ventana ordinario en dwStyle pero si quieres algunos estilos especiales tales como "topmost window" (nventana en el tope), debes especificarlos aquí. Puedes usar NULL si no quieres usar estilos de ventana extra.
lpClassName: (Requerido). Dirección de la cadena ASCIIZ que contiene el nombre de la clase de ventana que quieres usar como plantilla para esta ventana. La Clase puede ser una clase registrada por tí mismo o una clase de ventana predefinida. Como se estableció arriba, todas las ventanas que creas deben estar basadas en una clase de ventana.
lpWindowName: Dirección de la cadena ASCIIZ que contiene el nombre de la ventana. Será mostrada en la barra de título de la ventana. Si este parámetro es NULL, la barra de título de la ventana aparecería en blanco.
dwStyle: Estilos de la ventana. Aquí puedes especificar la apariencia de la ventana. Pasar NULL pero la ventana no tendrá el menú de sistema, ni botones minimizar-maximizar, y tampoco el botón cerrar-ventana. La ventana no sería de mucha utilidad. Necesitarás presionarAlt+F4 para cerrarla. El estilo de ventana más común es WS_OVERLAPPEDWINDOW. UN estilo de ventana sólo es una bandera de un bit. Así que puedes combinar varios estilos usando el operador "or" para alcanzar la apariencia deseada de la ventana. El estilo WS_OVERLAPPEDWINDOW es realmante la combinación de muchos estilos de ventana comunes empleando este método.
X,Y: La coordenada de la esquina izquierda superior de la vetana. Noramlamente este valor debería ser CW_USEDEFAULT, es decir, deseas que Windows decida por tí dónde poner la ventana en el escritorio.
nWidth, nHeight: El ancho y el alto de la ventana en pixeles. También puedes usar CW_USEDEFAULT para dejar que Windows elija por tí el ancho y la altura apropiada.
hWndParent: El manejador [handle] de la ventana padre de la ventana (si existe). Este parámetro dice a Windows si esta ventana es una hija (subordinada) de alguna otra ventana y, si lo es, cual ventana es el padre. Nota que no se trata del tipo de interrelación padre-hija de la MDI (Multiple Document Interface = Interface de Documento Múltiple). Las ventanas hijas no están restringidas a ocupar el área cliente de la ventana padre. Esta interrelación es únicamente para uso interno de Windows. Si la ventana padre es destruida, todas las ventanas hijas serán destruidas automáticamente. Es realmente simple. Como en nuestro ejemplo sólo hay una ventana, especificamos este parámetro como NULL.
hMenu: Manejador del menú de la ventana. NULL si la clase menú va a ser usada. Observa el miembro de la estructura WNDCLASSEX, lpszMenuName. lpszMenuName especifica el menú *por defecto* para la clase de ventana. Toda ventana creada a partir de esta clase de ventana tendrá por defecto el mismo menú, a menos que especifiques un nuevo menú *imponiéndolo* a una ventana específica a través de su parámetro hMenu. hMenu es realmente un parámetro de doble propósito. En caso de que la ventana que quieras crear sea de un tipo predefinido (como un control), tal control no puede ser propietario de un menú. hMenu es usado entonces más bien como un ID de control. Windows puede decidir si hMenu es realmente un manejador de menú o un ID de control revisando el parámetro lpClassName. Si es el nombre de una clase de ventana predefinida, hMenu es un ID de control, sino entonces es el manejador del menú de la ventana.
hInstance: El manejador de instancia para el módulo del programa que crea la ventana.
lpParam: Puntero opcional a la estructura pasada a la ventana. Es usada por la ventana MDI para pasar los datos CLIENTCREATESTRUCT. Normalmente, este valor es puesto en NULL, que significa que ningún dato es pasado vía CreateWindow(). La ventana puede recibir el valor de este parámetro llamando a la función GetWindowLong.
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
El regreso satisfactorio de CreateWindowEx, devuelve el manejador de ventana en eax. Debemos conservar este valor para usarlo luego. La ventana que creamos no es desplegada inmediatamente. Debes llamar a ShowWindow con el manejador de ventana y el *estado de despliegue* deseado para la ventana y desplegarla sobre el monitor. Luego puedes llamara a UpdateWindow con el fin de que tu ventana vuelva a dibujarse sobre el area cliente. Esta función es útil cuando se desea actualizar el contenido del area cliente. Aunque puedes omitir esta llamada.
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
En este momento nuestra ventana está desplegada en la pantalla. Pero no puede recibir entrada del mundo exterior. Así que tendremos que *informarle* de los eventos relevantes. Hacemos esto con un bucle de mensajes. Sólo hay un bucle de mensaje para cada módulo. Este bucle de mensaje chequea continuamente los mensajes de Windows llamando a GetMessage. GetMessage pasa a Windows un puntero a una estructura MSG. Esta estructura MSG será llenada con información sobre el mensaje que Windows quiere enviar a una ventana en el módulo. La función GetMessage no regresará hasta que haya un mensaje para una ventana en el módulo. Durante ese tiempo, Windows puede darle el control a otros programas. Esto es lo que forma el esquema de multitareas cooperativas de la plataforma Win16. GetMessage regresa FALSE si el mensaje WM_QUIT es recibido en el bucle de mensaje, lo cual terminará el programa y cerrará la aplicación.
TranslateMessage es una útil función que toma la entrada desde el teclado y genera un nuevo mensaje (WM_CHAR) que es colocado en la cola de mensajes. El mensaje WM_CHAR viene acompañado del valor ASCII de la tecla presionada, el cual es más fácil de manipular que los códigos brutos de lectura de teclado. Se puede omitir esta llamada si el programa no procesa los golpes de tecla.
DispatchMessage envía los datos del mensaje al procedimiento de ventana responsable por la ventana específica a la cual va dirigido el mensaje.
mov eax,msg.wParam
ret
WinMain endp
Si termina el bucle de mensaje, el código de salida es almacenado en el miembro wParam de la estructura MSG. Puedes almacenar el código de salida en eax para regresarlo a Windows. Para estos momentos, Windows no usa el valor regresado, pero es mejor hacerlo por si acaso y para jugar con las reglas.
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
Este es nuestro procedimiento de ventana. No tienes que llamarlo necesariamente WndProc. El primer parámetro, hWnd, es el manejador de la ventana hacia el cual el mensaje está destinado. uMsg es el mensaje. Nota que uMsg no es una estructura MSG. Realmente sólo es un número. Windows define cientos de mensajes, muchos de los cuales carecen de interés para nuestro programa. Windows enviará un mensaje apropiado a la ventana en caso de que ocurra algo relevante a la ventana. El procedimiento de ventana recibe el mensage y recciona a él inteligentemente. wParam y lParam sólo son parámetros extra a ser utiizados por algunos mensajes. Algunos mensajes envían datos junto con ellos en adición al mensaje propiamente dicho. Estos datos son pasados al procedimiento de ventana por medio de lParam y wParam.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
Aquí viene la parte crucial. Es donde reside gran parte de la inteligencia de los programas. El código que responde a cada mensaje de Windows está en el procedimiento de ventana. El código debe chequear el mensaje de Windows para ver si hay un mensaje que sea de interés. Si lo es, se hace algo que se desee en respuesta a ese mensaje y luego se regresa cero en eax. Si no es así, debe llamarse a DefWindowProc, pasando todos los parámetros recibidos para su procesamiento por defecto. DefWindowProc es una función de la API que procesa los mensajes en los que tu programa no está interesado.
El único mensaje a que DEBES responder es WM_DESTROY. Este mensaje es enviado a tu procedimiento de ventana cada vez que la ventana se va a cerrar. En el momento que tu procedimiento de diálogo reciba este mensaje, tu ventana estará ya removida del escritorio. Este mensaje sólo es una notificación de que tu ventana ha sido destruida y de que debes prepararte para regresar a Windows. En respuesta a esto, puedes ejecutar alguna tarea doméstica antes de regresar a Windows. No queda más opción que quitar cuando la ventana llega a este estado. Si quieres tener la oportunidad de detener el usuario cuando éste intente cerrar la ventana, debes procesar el mensaje WM_CLOSE.
Ahora, regresando a WM_DESTROY, después de ejecutar la tareas domésticas, debes llamar al PostQuitMessage que enviará el mensaje WM_QUIT de vuelta a tu módulo. WM_QUIT hará que GetMessage regrese el valor NULL en eax, el cual terminará el bucle de mensajes y devolverá el control a Windows. Puedes enviar el mensaje WM_DESTROY a tu propio procedimiento de ventana llamando a la función DestroyWindow.
[Iczelion's Win32 Assembly HomePage]
n u M I T_o r's Programming Page
Este tutorial, original de Iczelion, ha sido traducido por: n u M I T_o r [ kUT ]