Está en la página 1de 192

Tutorial 1: Lo bsico

En este tutorial asumo que el lector sabe como usar MASM. Si no ests familiarizado con MASM, descarga win32asm.exe y estudia el texto dentro del paquete antes de seguir con este tutorial. Bueno. Ahora ests listo. Ok vamos !

Teora:
Los programas de Win32 corren en modo protegido, disponible desde el 80286. Pero ahora el 80286 es historia. Asi que ahora debemos interesarnos en el 80386 y sus descendientes. Windows corre cada programa de 32 bits en espacios virtuales de memoria separados. Eso significa que cada programa de Win32 tiene sus propios 4 GB de memoria en el espacio de direcciones. Como sea, esto no significa que cada programa de Win32 tenga 4GB de memora fsica, sino que el programa puede direccionar cualquier direccin en ese rango. Windows har cualquier cosa que sea necesaria para hacer que la memoria y las referencias del programas sean vlidas. Por supuesto, el programa debe adherirse a las reglas impuestas por Windows, si no, causar un error de proteccin general. Cada programa est solo en su espacio de direcciones. Esto contrasta con la situacin en Win16. Todos los programas de Win16 podan *verse* unos a otros. No es lo mismo en Win32. Esto reduce la posibilidad de que un programa escriba sobre el cdigo/datos de otros programas. El modelo de la memoria es tambin drsticamente diferente al de los antiguos das del mundo de 16-bits. Bajo Win32, ya no necesitamos meternos nunca ms con el modelo o los segmentos de memoria! Hay un solo tipo de modelo de memoria: El modelo plano (flat). Ahora, no hay solamente segmentos de 64k. La memoria es un largo y continuo espacio de 4GB. Eso tambin significa que no tendrs que jugar mas con los registros de los segmentos. Ahora puedes usar cualquier registro de segmento para direccionar a cualquier punto en el espacio de la memora. Eso es una GENIAL ayuda para los programadores. Esto hace la programacin de ensamblador para Win32 tan fcil como C. Cuando programas bajo Win32, debes tener en cuenta unas cuantas reglas importantes. Una es que Windows usa esi ,edi ebp y ebx internamente y no espera que los valores en esos registros tambin. Asi que recuerda esta regla primero: si usas cualquiera de estos cuatro registros en tu funcin callback, nunca olvides restaurarlos antes de regresar el control a Windows. Una funcin callback es una funcin escrita por t que Windows llama cuando algn evento especfico se produce. El ejemplo mas obvio es el procedimiento de ventana. Esto no significa que no puedas usar estos cuatro registros; s puedes. Solo asegrate de restaurarlos antes de pasarle el control a Windows.

Contenido:
Aqu hay un esqueleto de un programa. Si no entiendes algo de los cdigos, que no cunda el pnico. Los explicar cada uno de ellos mas abajo. .386 .MODEL Flat, STDCALL .DATA

<Tu data (informacin) inicializada> ...... .DATA? <Tu data NO inicializada> ...... .CONST <Tus constantes> ...... .CODE <Etiqueta> <Tu cdigo> ..... end <Etiqueta>

Sip, eso es todo, vamos a analizar el programa esqueleto.

.386
Esto es una directiva para el ensamblador, que le dice que vamos a usar el conjunto de instrucciones del 80386. Tambin puedes usar .486,.586 pero es mas seguro ponerle .386. Hay actualmente dos formas casi idnticas para cada modelo de CPU. .386/.386p, .486/.486p. Esas versiones de "p" son necesarias cuando tu programa usa instrucciones privilegiadas. Las instrucciones privilegiadas son las instrucciones reservadas por la CPU/sistema operativo cuando estn en modo protegido. Solamente pueden ser usadas por un cdigo privilegiado, asi como los virtual device drivers (controladores de dispositivos virtuales = VXD).

.MODEL FLAT, STDCALL


.MODEL es una directiva para el ensamblador que especifca el modelo de memoria de tu programa. Bajo Win32, hay un solo tipo de memoria, la PLANA(FLAT). STDCALL Indica al ensamblador la convencin de paso de los parmetros. La convencin de paso de parmetros especifca la orden que debe seguirse para pasar parmetros, izquierda-a-derecha o derecha-aizquierda, y tambin equilibrar la pila despus de una llamada (call) Bajo Win16, hay dos tipos de convenciones para las llamadas a funciones: C y PASCAL La convencin para pasar parmetros de derecha a izquierda en cada llamada (call), es decir, el parmetro de ms a la derecha es empujado primero a la pila. La rutina que hace la llamada es la responsable de equilibrar el marco de la pila despus de la llamada (call). Por ejemplo, si vamos a llamar a una funcin con nombre foo(int primera_parte, int segunda_parte, int tercera_parte) en lenguaje C la convencin sera as en asm: push [tercera_parte] ; Empuja el tercer parmetro push [segunda_parte] ; Seguido por el segundo push [primera_parte] ; Y aqui se pone de primero call foo add sp, 12 ; La llamada equilibra el marco de la pila La convencin de llamadas en PASCAL es totalmente al revs de la convencin de C. Pasa los parmetros de izquierda a derecha y la rutina que llama es responsable de equilibrarse despus de la llamada.

El sistema Win16 adopta la convencin de PASCAL porque produce cdigos ms pequeos. La convencin de C es eficiente cuando no sabes cuntos parmetros sern pasados a la funcin como es el caso de wsprintf(). En el caso de wsprintf(), no hay manera de determinar cuntos parmetros sern empujados por esta funcin a la pila, as que no se puede hacer el balanceo de la pila. STDCALL es la convencin hbrida entre C y PASCAL. Pasa parmetros de derecha a izquierda, pero la llamada es la responsable por el balance de la pila despus de la llamada. La plataforma de Win32 usa el modelo STDCALL exclusivamente. Excepto en un caso: wsprintf(). Debes usar la convencin de llamada C con la funcin wsprintf().

.DATA .DATA? .CONST .CODE


Estas 4 directivas son las llamadas 'secciones'. No tienes segmentos en Win32, recuerdas?, pero puedes dividir todo el espacio de direcciones en secciones lgicas. El comienzo de una seccin demuestra el fin de la otra seccin previa. Hay dos grupos de secciones: data y code. Las secciones Data estn divididas en tres categoras: .DATA Esta seccin contiene la informacin inicializada de tu programa. .DATA? Esta seccin contiene la informacin no inicializada de tu programa. A veces quieres solamente prelocalizar alguna memoria pero no quieres iniciarla. Ese es el propsito de esta seccin. La ventaja de la informacin no inicializada es que no toma espacio en el ejecutable. Por ejemplo, si tu localizas 10,000 bytes en tu seccin .DATA?, tu ejecutable no se infla 10,000 bytes. Su tamao se mantiene muy homogneo. Tu slo le dices al ensamblador cunto espacio necesitas cuando el programa se carga en la memoria, eso es todo. .CONST Esta seccin contiene declaraciones de constantes usadas por tu programa. Las constantes nunca pueden ser modificadas en tu programa. Slo son *constantes*

No tienes que usar las tres secciones en tu programa. Declara solo la(s) seccin(es) que quieres usar. Existe solo una seccin para el cdigo (code):.CODE. Aqu es donde tu cdigo reside. <etiqueta> end <etiqueta> Donde est la <etiqueta> se puede usar cualquier nombre como etiqueta para especificar la extensin de tu cdigo.. Ambas etiquetas deben ser idnticas. Todos tus cdigos deben residir entre <etiqueta> y end <etiqueta>

En este tutorial, crearemos un programa de Windows completamente funcional que despliega un cuadro con el mensaje "Win32 assembly is great!".

Baja el ejemplo aqu.

Teora:
Windows tiene preparado una gran cantidad de recursos para sus programas. En el centro de esta concepcin se ubica la API (Application Programming Interface = Interface de Programacin de Aplicaciones) de Windows. La API de Windows es una enorme coleccin de funciones muy tiles que residen en el propio sistema Windows, listas para ser usadas por cualquier programa de Windows. Estas funciones estn almacenadas en varias libreras de enlace dinmico [dynamic-linked libraries (DLL)] tales como kernel32.dll, user32.dll y gdi32.dll. Kernel32.dll contiene las funciones de la API que tienen que ver con el manejo de memoria y de los procesos. User32.dll controla los aspectos de la interface de usuario de tu programa. Gdi32.dll es la responsable de las operaciones grficas. Adems de estas "tres funcones principales", hay otras DLLs que nuestros programas pueden emplear, siempre y cuando tengas la suficiente informacin sobre las funciones de la API que te interesan. Los programas de Windows se enlazan dinmicamente a estas DLLs, es decir, las rutinas de las funciones de la API no estn incluidas en el archivo ejecutable del programa de Windows. Con el fin de que tu programa pueda encontrar en tiempo de ejecucin las funciones de la API deseadas, tienes que meter esa informacin dentro del archivo ejecutable. Esta informacin se encuentra dentro de archivos .LIB. Debes enlazar tus programas con las libreras de importacin correctas o no sern capaces de localizar las funciones de la API. Cuando un programa de Windows es cargado en la memoria, Windows lee la informacin almacenada en el programa. Esa informacin incluye el nombre de las funciones que el programa usa y las DLLs donde residen esas funciones. Cuando Windows encuentra esa informacin en el programa, cargar las DLLs y ejecutar direcciones de funciones fijadas en el programa de manera que las llamadas transfieran el control a la funcin correcta. Hay dos categoras de funciones de la API: Una para ANSI y la otra para Unicode. Los nombres de las funciones de la API para ANSI terminan con "A", por ejemplo, MessageBoxA. Los de Unicode terminan con "W" [para Wide Char (caracter ancho), pienso]. Windows 95 soporta ANSI y Windows NT Unicode. Generalmente estamos familiarizados con las cadenas ANSI, que son arreglos de caracteres terminados en NULL. Un caracter ANSI tiene un tamao de 1 byte. Si bien el cdigo ANSI es suficiente para los lenguajes europeos, en cambio no puede manejar algunos lenguajes orientales que tienen millares de caracteres nicos. Esa es la razn por la cual apareci UNICODE. Un caracter UNICODE tiene un tamao de 2 bytes, haciendo posible tener 65536 caracteres nicos en las cadenas. Sin embargo, la mayora de las veces, usars un archivo include que puede determinar y seleccionar las funciones de la API apropiadas para tu plataforma. Slo referencia los nombres de las funciones de la API sin su sufijo.

Ejemplo:
Presentar abajo el esqueleto del programa. Luego lo completaremos. .386.model flat, stdcall .data .code start: end start Lsa ejecucin se inicia inmediatamente despus de la etiqueta especificada despus de la directiva end. En el esqueleto de arriba, la ejecucin iniciar en la primera instruccin

inmediatamante debajo de la etiqueta start. Le ejecucin proceder instruccin por instruccin hasta encontrar algunas instrucciones de control de flujo tales como jmp, jne, je, ret etc. Esas instrucciones redirijen el fujo de ejecucin a algunas otras instrucciones. Cuando el programa necesite salir a Windows, deber llamar a una fucnin de la API, ExitProcess. ExitProcess proto uExitCode:DWORD A la lnea anterior la llamamos prototipo de la funcin. Un prototipo de funcin define los atributos de una funcin para que el ensamblador/enlazador pueda tipear y chequear para t. El formato de un prototipo de funcin es: FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,... En pocas palabras, el nombre de la funcin seguido por la palabra clave PROTO y luego por la lista de tipos de datos de los parmetros, separados por comas. En el ejemplo de arriba de ExitProcess, se define ExitProcess como una funcin que toma slo un parmetro del tipo DWORD. Los prototipos de funciones son muy tiles cuando usas sintaxis de llamadas de alto nivel, como invoke. Puedes pensar en invoke como una llamada simple con chequeo-tipeo. Por ejemplo, si haces: call ExitProcess sin meter en la pila un valor dword, MASM no ser capaz de cachar ese error para t. Slo lo notars luego cuando tu programa se guinde o se quiebre. Pero si usas: invoke ExitProcess El enlazador te informar que olvidaste meter a la pila un valor dword y gracias a esto evitars el error. Recomiendo que uses invoke en vez de call. La sintaxis de invoke es como sigue: INVOKE expresin [,argumentos] expresin puede ser el nombre de una funcin o el nombre de un puntero de funcin. Los parmetros de la funcin estn separados por comas. Muchos de los prototipos de funciones para las funciones de la API se conservan en archivos include. Si usas el MASM32 de hutch, la encontrars en la carpeta MASM32/include. Los archivos include tienen extensin .inc y los prototipos de funcin para las funciones en una DLL se almacenan en archivos .inc con el mismo nombre que la DLL. Por ejemplo, ExitProcess es exportado por kernel32.lib de manera que el prototipo de funcin para ExitProcess est almacenado en kernel32.inc. Tambin puedes crear prototipos para tus propias funciones. A travs de mis ejemplos, usar windows.inc de hutch que puedes bajar desde http://win32asm.cjb.net Ahora regresemos a ExitProcess, el parmetro uExitCode es el valor que quieres que el programa regrese a Windows despus de que el programa termina. Puedes llamara ExitProcess de esta manera: invoke ExitProcess, 0 Pon esa lnea inmediatamente abajo de la etiqueta de inicio, y obtendrs un programa win32 que saldr inmediatamente a Windows, pero no obstante ser un programa vlido.

.386 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data .code invoke ExitProcess,0 start: end start la opcin casemap:none dice a MASM que haga a las etiquetas sensibles a mayusculasminusculas, as que ExitProcess y exitprocess son diferentes. Nota una nueva directiva, include. Esta directiva es seguida por el nombre de un archivo que quieres insertar en el lugar donde se encuentra la directiva. En el ejemplo de arriba, cuando MASM procese la lnea include \masm32\include\windows.inc, abrir windows.inc que est en el directorio \MASM32\include y procesar el contenido de windows.inc como si pegaras ah el contenido de windows.inc. windows.inc de hutch contiene definiciones de constantes y estructuras que necesitas en la programacin de win32. No contiene ningn prototipo de funcin. windows.inc es totalmente incomprensible. hutch y yo tratamos de poner tantas constantes y estructuras como sea posible pero quedan todava muchas por incluir. Ser constantemente actualizada. Chequea el homepage de hutch o el mo por actualizaciones. De windows.inc, tus programas obtendrn las definiciones de constantes y estructuras. Pero para los prototipos de la funciones, necesitars incluir otros archivos include. Puedes generar a partir de libreras de importacin los archivos include que contienen slo prototipos de funciones. Esbozar ahora los pasos para generar los archivos include: 1. Baja las libreras para el paquete MASM32 del homepage de hutch o del mo. Contiene una coleccin de libreras de importacin que necesitas para programar para win32. tambin baja la utilidad l2inc. 2. Desempaca (unzip) ambos paquetes dentro del mismo archivo. Si instalaste MASM32, desempcalos dentro del directorio MASM32\Lib

3. Corre l2inca.exe con los siguientes conmutadores (switches):


l2inca /M *.lib l2inca.exe extraer informacin de las libreras de importacin y crear archivos include llenos de prototipos de funciones.

4. Mueve tus archivos include a tu carpeta de archivos de este tipo. Los usuarios de
MASM32 debern moverlos a la carpeta MASM32\include. En nuestro ejemplo, llamamos la funcin exportada por kernel32.dll, as que necesitamos incluir los prototipos de las funciones de kernel32.dll. Ese archivo es kernel32.inc. Si lo abres con un editor de texto, vers que est lleno de prototipos de funciones de kernel32.dll. Si no incluyes kernel32.inc, puedes llamar todava a call ExitProcess pero slo con una sintaxis simple de llamada. No podrs usar invoke para "invocar" la funcin. El punto aqu es: para invocar una funcin, tienes que poner el prototipo de la funcin en alguna parte del cdigo fuente. En el ejemplo anterior, si incluyes kernel32.inc, puedes definir los prototipos de funciones para ExitProcess en cualquier parte del cdigo fuente antes del comando invoke para que trabaje.

Los archivos include estn ah para liberarte del trabajo que significa escribirlas a cada momento que necesites llamar funciones con el comando invoke. Ahora encontramos una nueva directiva, includelib. includelib no trabaja como include. Es la nica menera que tiene tu programa de decirle al ensamblador que importe las libreras que necesita. Cuando el ensamblador ve la directiva includelib, pone un comando del enlazador dentro del mismo archivo objeto que genera, de manera que el enlazador sepa cules libreras necesita importar tu programa que deben ser enlazadas. Sin embargo, no ests obligado a usar includelib. Puedes especificar los nombres de las libreras de importacin en la lnea de comando del enlazador pero, creme, es tedioso y la lnea de comando slo soporta 128 caracteres. Ahora salvamos el ejemplo bajo el nombre msgbox.asm. Asumiendo que ml.exe est un tu entorno ("path"), ensamblamos msgbox.asm con: ml /c /coff /Cp msgbox.asm /coff dice a MASM que cree un archivo objeto en formato COFF. MASM usa una variacin de COFF (Common Object File Format = Formato de Archivo Objeto Comn) que es usado bajo Unix como su propio formato de archivo objeto y ejecutable. /Cp dice a MASM que preserve la sensibilidad a la diferencia entre maysculas y minsculas de los identificadores usados. Si usas el paquete MASM32 de hutch, puedes poner "option casemap:none" en la cabeza del cdigo fuente, justo debajo de la directiva .model para alcanzar el mismo efecto. Despus de haber ensamblado satisfactoriamente msgbox.asm, obtendrs msgbox.obj. msgbox.obj es un archivo objeto. Un archivo objeto est a slo un paso del archivo ejecutable. Contiene las instrucciones/datos en forma binaria. Slo necesitas que el enlazador fije algunas direcciones en l. Entonces enlacemos: link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj /SUBSYSTEM:WINDOWS informa a Link qu tipo de ejecutable es este programa /LIBPATH:<path to import library> dice a Link dnde se encuentran las libreras de importacin. Si usas MASM32, estarn en el archivo MASM32\lib. Link lee en el archivo objeto y lo fija con las direcciones de las libreras de importacin. Cuando el proceso termina obtienes msgbox.exe. Obtienes msgbox.exe. Vamos, crrelo. Encontrars que no hace nada. Bien, todava no hemos puesto nada interesante en l. Sin embargo, es un programa de Windows. Y mira su tamao! En mi PC, es de 1,536 bytes. Vamos a ponerle ahora una caja de mensaje [Dialog Box]. Su prototipo de funcin es: MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD hwnd es el manejador de la ventana padre. Puedes pensar en el manejador como un nmero que representa la ventana a la cual te refieres. Su valor no es tan importante para t. Slo recuerda que representa la ventana. Cuando quieres hacer algo con la ventana, debes referirte a ella por su manjador. /c dice a MASM que slo ensamble, que no invoque a link.exe. Muchas veces, no querrs llamar automticamente a link.exe ya que quizs tengas que ejecutar algunas otras tareas antes de llamar a link.exe.

lpText es el puntero al texto que quieres desplegar en el rea cliente de la caja de mensaje. En realidad, un puntero es la direccin de lago: Puntero a cadena de texto==Direccin de esa cadena. lpCaption es un puntero al encabezado de la caja de mensaje uType especifica el icono, el nmero y el tipo de botones de la caja de mensajes Modifiquemos msgbox.asm para que incluya la caja de mensaje.

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib include \masm32\include\user32.inc includelib \masm32\lib\user32.lib .data MsgBoxCaption db "Iczelion Tutorial No.2",0 MsgBoxText db "Win32 Assembly is Great!",0 .code start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK invoke ExitProcess, NULL end start Ensmblalo y crrelo. Vers un cuadro de mensaje desplegando el texto "Win32 Assembly is Great!". Veamos de nuevo el cdigo fuente. Definimos dos cadenas terminadas en cero en la seccin .data. Recuerda que toda cadena ANSI en Windows debe terminar en NULL (0 hexadecimal). Usamos dos constantes, NULL y MB_OK. Esas constantes estn documentadas en windows.inc. As que nos referiremos a ellas por nombres y no por valores. Esto facilita la lectura de nuestro cdigo fuente. El operador addr es usado para pasar la direccin de una etiqueta a la funcin. Es vlido slo en el contexto de la directiva invoke. No puedes usarla para asignar la direccin de un registro/variable, por ejemplo. En vez de esto, puedes usar offset en el ejemplo anterior. Sin embargo hay algunas diferencias entre los dos:

1. addr no puede manejar referencias delante de ella mientras que offset si puede. Por
ejemplo, si la etiqueta est definida en una parte ms adelante del cdigo fuente que la directiva invoke, entonces addr no trabajar. invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK ...... MsgBoxCaption db "Iczelion Tutorial No.2",0 MsgBoxText db "Win32 Assembly is Great!",0 MASM reportar error.Si usas offset en vez de addr en el recorte de cdigo de arriba, MASM lo ensamblar felizmente.

2. addr puede manejar variables locales mientras que offset no puede. Una variable local
es un espacio reservado en algn lugar de la pila. No conocers su direccin durante el tiempo de ejecucin. offset es interpretado por el ensamblador durante el tiempo de ensamblaje. As que es natural que offset no trabaje para variables locales. addr puede manejar variables locales debido a que el ensamblador chequea primero si la variable referida por addr es global o local. Si es una variable global, pone la direccin de la variable dentro del archivo objeto. En este aspecto, trabaja como offset. Si la variable es local, genera uina secuencia de instrucciones como la siguiente antes de llamar a la funcin: lea eax, LocalVar push eax

Puesto que "lea" puede determinar la direccin de una etiqueta en tiempo de ejecucin, esto trabaja bien.

En este tutorial, construiremos un programa para Windows que despliegue una ventana completamente funcional sobre el escritorio. Puedes bajar un archivo de ejemplo aqu Teora: Los programas de Windows realizan la parte pesada del trabajo de programacin a travs funciones API para sus GUI (Graphic User Interface = Interface de Usuario Grfica). Esto beneficia a los usuarios y a los programadores. A los ususarios, porque no tienen que aprender cmo 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 disposicin 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, menes o iconos, los programadores deben seguir un "rcipe" estricto. Pero esto puede ser superado a travs de programacin modular o siguiendo el paradigma de Progranacin orientada a Objetos (OOP = Object Oriented Programming). Esbozar los pasos requeridos para crear una ventana sobre el escritorio: 1. Obtener el manejador [handle] de instancia del programa (requerido) 2. Obtener la lnea de comando (no se requiere a menos que el programa vaya a procesar la lnea de comando) 3. Registrar la clase de ventana (requerido, al menos que vayan a usarse tipos de ventana predefinidos, eg. MessageBox o una caja o de dilogo) 4. Crear la ventana (requerido) 5. Mostrar la ventana en el escritorio (requerido al menos que se quiera mostrar la ventana inmediatamente) 6. Refrescar el rea cliente de la ventana 7. Introducir un bucle (loop) infinito, que cheque los mensajes de Windows 8. Si llega un mensaje, es procesado por una funcin especial, que es responsable por la ventana 9. Quitar el programa si el usuario cierra la ventana

Como puedes ver, la estructura de un programa de Windows es ms compleja que la de un programa de DOS, ya que el mundo de Windows es totalmente diferente al mundo de DOS. Los programas de Windows deben ser capaces de coexistir pacficamente uno junto a otro. Por eso deben seguir reglas estrictas. T (o Usted), como programador, debes ser ms estricto con tus estilos y hbitos de programacin. Contenido: Abajo est el cdigo fuente de nuestro programa de ventana simple. Antes de entrar en los sangrientos detalles de la programacin Win32 ASM, adelantar algunos puntos delicados que facilitarn la programacin.

Se deberan poner todas las constantes, estructuras, y prototipos de funciones de Windows en un archivo include e incluirlo al comienzo de nuestro archivo .asm. Esto nos ahorrar esfuerzos y gran cantidad de tipeo. Generalmente, el archivo include ms completo para MASM es windows.inc de hutch, el cual puedes bajar desde su pgina o mi pgina. Puedes definir tus propias constantes y estructuras pero deberas ponerlas en un archivo include separado. Usa la directiva includelib para especificar la librera de importacin usada en tu programa. Por ejemplo, si tu programa llama a MessageBox, deberas poner la lnea: includelib user32.lib al comienzo de tu archivo .asm. Esta directiva dice a MASM que tu programa har uso de funciones es esa librera de importacin. Si tu programa llama funciones en ms de una librera, entonces hay que agregar una lnea includelib para cada librera que se vaya a usar. Usando la directiva includelib no tendrs que preocuparte de las libreras de importacin en el momento de enlazar. Puedes usar el conmutador del enlazador /LIBPATH para decirle a Link donde estn todas las libreras. 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 maysculas y minsculas. Esto te liberar de dolores de cabeza cuando necesites buscar informacin sobre algn elemento en la referencia de la API de Win32.

Usa un archivo makefile para automatizar el proceso de ensamblaje. Esto te liberar de tener que tipear ms de la cuenta.

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib ; llamadas a las funciones en user32.lib y kernel32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .DATA ; data inicializada ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 ; el nombre de nuestra clase de ventana ; el nombre de nuestra ventana

.DATA? ; data no inicializada hInstance HINSTANCE ? ; manejador de instancia de nuestro programa CommandLine LPSTR ? .CODE ; Aqu comienza nuestro cdigo start: invoke GetModuleHandle, NULL ; obtener el manejador de instancia del programa. ; En Win32, hmodule==hinstance mov hInstance,eax invoke GetCommandLine mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT

; Obtener la lnea de comando. No hay que llamar esta funcin ; si el programa no procesa la lnea de comando ; llamar la funcin

principal invoke ExitProcess


WinMain.

; quitar nuestro programa. El cdigo de salida es devuelto en eax desde

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWOR D 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 cdigo 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 aplicacin .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Procesar el mensaje por defecto ret .ENDIF xor eax,eax ret WndProc endp end start Anlisis: Puede parecer desconcertante que un simple programa de Windows requiera tanto cdigo. Pero muchas de estas rutinas son verdaderamente un cdigo *plantilla* que puede copiarse de un archivo de cdigo fuente a otro. O si prefieres, podras ensamblar algunas de estas rutinas en una librera para ser usadas como rutinas de prlogo o de eplogo. Puedes escribir solamente las rutinas en la funcin 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 domsticos. Todo lo que hay que

hacer es tener una funcin llamada WinMain, sino los compiladores C no sern capaces de combinar tus rutinas con el prlogo y el eplogo. No exiten estas restricciones con lenguaje ensamblador. Puedes usar otros nombres de funciones en vez de WinMain o no emplear esta funcin en ninguna parte. Bueno, ahora preprate. Esto va a ser largo, un largo tutorial Vamos a analizar este programa hasta la muerte!! .386 .model flat,stdcall option casemap:none 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 lneas 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). Tambin usaremos la convencin de paso de parmetros stdcall como la convencin por defecto del programa. Lo siguiente constituye el prototipo para la funcin WinMain. Ya que llamaremos ms tarde a WinMain, debemos definir su prototipo de funcin 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 (todava). hutch y yo estamos trabajando en ello. Puedes agregarle elementos nuevos si ellos no estn 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 libreras de importacin. La prxima cuestin es: cmo podemos saber cul librera 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 funcin API en gdi32.dll, debes enlazarla con gdi32.lib. Esta es la manera como lo hace MASM. El mtodo de TASM para importar libreras a travs del enlace es mucho ms simple: slo hay que enlazar un archivo : import32.lib. .DATA ClassName db "SimpleWinClass",0 AppName db "Our First Window",0

.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 estn 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 aparicin). 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 seccin .DATA? no estn inicializadas, es decir, no tienen que tener ningn valor especifico al inicio, pero queremos reservarle un espacio para su usarlas en el futuro. .CODE start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax ..... end start .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 instruccin llama a GetModuleHandle para recuperar el manejador de instancia de nuestro programa. Bajo Win32, el manejador de la instancia y el manejador del mdulo son una y la misma cosa. Se puede pensar en el manejador de instancia como el ID de nuestro programa. Es usado como parmetro 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 direccin lineal de nuestro programa en la memoria. Al regresar a una funcin de Win32, el valor regresado, si hay alguno, puede encontrarse en el registro eax. Todos los dems valores son regresados a travs de variables pasadas en la lista de parmetros de la funcin que va a ser llamada.

Cuando se llama a una funcin 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 dinmicos y siempre sus valores son indeterminados e impredecibles cuando retorna una funcin Win32. Nota: No esperes que los valores de eax, ecx, edx sean preservados durante las llamadas a una funcin API. La lnea inferior establece que: cuando se llama a una fucnin API, se espera que regrese el valor en eax. Si cualquiera de las funciones que creamos es llamada por Windows, tambin debe seguir la siguiente regla: preservar y restablecer los valores de los registros de segmentos ebx, edi, esi y ebp cuando la funcin 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 lnea 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 parmetros: el manejador de instancia de nuestro programa, el manejador de instancia de la instancia previa del programa, la lnea de comando y el estado de la ventana en su primera aparicin. 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 das de Win16 cuando todas las instancias de un programa corran 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 funcin no tiene que ser declarada como WinMain. En realidad, hay completa libertad a este respecto. Ni siquiera hay que usar siempre una funcin equivalente a WinMain. Se puede pegar el cdigo dentro de la funcin WinMain inmediatamente despus de GetCommandLine y el programa funcionar perfectamente. Al regresar de WinMain, eax tiene el cdigo de salida. Pasamos el cdigo de salida como parmetro de ExitProcess, que terminar nuestra aplicacin.
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

La lnea de arriba forma la declaracin de la funcin WinMain. Nota que los pares parmetro:tipo que siguen a la directiva PROC. Son parmetros queWinMain recibe desde la instruccin que hace la llamada [caller]. Puedes referirte a estos parmetros por nombre en vez de a travs de la manipulacin de la pila. Adems, MASM generar los cdigos de prlogo y eplogo para la funcin. As que no tenemos que preocuparnos del marco de la pila cuando la funcin 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 funcin. 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 tamao de la estructura WNDCLASSEX para la variable llamada wc. Podemos hacer referencia a wc en nuestro cdigo sin ninguna dificultad en la manipulacin de la pila. Creo que esto es realmente una bendicin. El aspecto negativo de esto es que las variables locales no pueden ser usadas fuera de la funcin porque ellas son creadas para ser destruidas inmediatamante cuando la funcin retorna a la rutina desde la cual fue llamada. Otra contrapartida es que no se pueden inicializar variables locales automticamente porque ellas son localizadas dinmicamente en la memoria de la pila cuando la funcin es introducida (entered). Hay que asignarlas manualmente con los valores deseados despus 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 lneas intimidantes de arriba son realmente comprensibles en cuanto concepto. Toma varias lneas de instrucciones realizar la operacin ah implicada. El concepto detrs de todas estas lneas es el de clase de ventana (window class). Una clase de ventana no es ms que un anteproyecto o especificacin de una ventana. Define algunas de las caractersticas importantes de una ventana tales como un icono, su cursor, la funcin 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 ms de una ventana con las mismas caractersticas, lo razonable es almacenar todas estas caractersticas en un solo lugar y referirse a ellas cuando sea necesario. Este esquema salva gran cantidad de memoria evitando duplicacin de cdigo. Hay que recordar que Windows fue diseado cuando los chips de memoria eran prohibitivos ya que una computadora tena apenas 1 MB de memoria. Windows deba ser muy eficiente al usar recursos de memorias escasos. El punto es: si defines tu propia ventana, debes llenar las caractersticas de tu ventana en una estructura WNDCLASS o WNDCLASSEX y llamar a RegisterClass o RegisterClassEx antes de crear la ventana. Slo 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 botn (button) y caja de edicin (edit box). Para estas ventanas (o controles), no tienes que registrar una clase de venana, slo hay que llamara a CreateWindowEx con el nombre de la clase predefinido. El miembro ms importante en WNDCLASSEX es lpfnWndProc. lpfn se concibe como un puntero largo a una funcin. Bajo Win32, no hay puntero "cercano" o "lejano" pointer, sino slo puntero, debido al nuevo modelo de memoria FLAT. Pero esto tambin es otro de los restos de los das de Win16. Cada clase de ventana debe estar asociada con la funcin llmada procedimiento de ventana. El procedimiento de ventana es la funcin 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 ratn. Le toca al procedimiento de ventana responder inteligentemante a cada evento que recibe la ventana. Seguro que pasars 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: Tamao 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 direccin del procedimiento de ventana responsable para las ventanas creadas a partir de esta clase. cbClsExtra: Especifica el nmero de bytes extra para localizar la siguiente estructura de clase de ventana. El sistema operativo inicializa los bytes ponindolos en cero. Puedes almacenar aqu datos especfcos de la clase de ventana. cbWndExtra: : Especifica el nmero de bytes extra para localizar the window instance. El sistema operativo inicializa los bytes ponindolos en cero. Si una aplicacin usa la estructura WNDCLASS para registrar un cuadro de dilogo creado al usar la directiva CLASS en el archivo de recurso, debe poner este miembro en DLGWINDOWEXTRA. hInstance: Manejador de instancia del mdulo. 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 pequeo 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 tamao apropiado para ser usado como icono pequeo. invoke CreateWindowEx, NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\ hInst,\ NULL Despus 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 parmetros para esta funcin. 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 descripcin detallada de cada parmetro: dwExStyle: Estilos extra de ventana. Es el nuevo parmetro agregado a la antigua funcin 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). Direccin 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: Direccin de la cadena ASCIIZ que contiene el nombre de la ventana. Ser mostrada en la barra de ttulo de la ventana. Si este parmetro es NULL, la barra de ttulo de la ventana aparecera 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 minimizarmaximizar, y tampoco el botn cerrar-ventana. La ventana no sera de mucha utilidad. Necesitars presionarAlt+F4 para cerrarla. El estilo de ventana ms comn es WS_OVERLAPPEDWINDOW. UN estilo de ventana slo 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 combinacin de muchos estilos de ventana comunes empleando este mtodo. X,Y: La coordenada de la esquina izquierda superior de la vetana. Noramlamente este valor debera ser CW_USEDEFAULT, es decir, deseas que Windows decida por t dnde poner la ventana en el escritorio. nWidth, nHeight: El ancho y el alto de la ventana en pixeles. Tambin 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 parmetro 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 interrelacin padre-hija de la MDI (Multiple Document Interface = Interface de Documento Mltiple). Las ventanas hijas no estn restringidas a ocupar el rea cliente de la ventana padre. Esta interrelacin es nicamente para uso interno de Windows. Si la ventana padre es destruida, todas las ventanas hijas sern destruidas automticamente. Es realmente simple. Como en nuestro ejemplo slo hay una ventana, especificamos este parmetro 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 *imponindolo* a una ventana especfica a travs de su parmetro hMenu. hMenu es realmente un parmetro de doble propsito. 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 ms bien como un ID de control. Windows puede decidir si hMenu es realmente un manejador de men o un ID de control revisando el parmetro 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 mdulo 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 ningn dato es pasado va CreateWindow(). La ventana puede recibir el valor de este parmetro llamando a la funcin 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 funcin 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. Slo hay un bucle de mensaje para cada mdulo. 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 informacin sobre el mensaje que Windows quiere enviar a una ventana en el mdulo. La funcin GetMessage no regresar hasta que haya un mensaje para una ventana en el mdulo. 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 aplicacin. TranslateMessage es una til funcin 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 acompaado del valor ASCII de la tecla presionada, el cual es ms fcil de manipular que los cdigos brutos de lectura de teclado. Se puede omitir esta llamada si el programa no procesa los golpes de tecla. DispatchMessage enva los datos del mensaje al procedimiento de ventana responsable por la ventana especfica a la cual va dirigido el mensaje. mov eax,msg.wParam ret WinMain endp

Si termina el bucle de mensaje, el cdigo de salida es almacenado en el miembro wParam de la estructura MSG. Puedes almacenar el cdigo 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 parmetro, 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 slo es un nmero. Windows define cientos de mensajes, muchos de los cuales carecen de inters 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 slo son parmetros extra a ser utiizados por algunos mensajes. Algunos mensajes envan datos junto con ellos en adicin 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 cdigo que responde a cada mensaje de Windows est en el procedimiento de ventana. El cdigo debe chequear el mensaje de Windows para ver si hay un mensaje que sea de inters. 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 parmetros recibidos para su procesamiento por defecto. DefWindowProc es una funcin 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 dilogo reciba este mensaje, tu ventana estar ya removida del escritorio. Este mensaje slo es una notificacin de que tu ventana ha sido destruida y de que debes prepararte para regresar a Windows. En respuesta a esto, puedes ejecutar alguna tarea domstica antes de regresar a Windows. No queda ms opcin 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, despus de ejecutar la tareas domsticas, debes llamar al PostQuitMessage que enviar el mensaje WM_QUIT de vuelta a tu mdulo. 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 funcin DestroyWindow.
En este tutorial, aprenderemos como "pintar" texto en el rea cliente de una ventana. Tambin aprenderemos sobre el contexto del dispositivo. Puedes bajar el cdigo fuente desde aqu.

Teora:
El texto en Windows es un tipo de objeto GUI. Cada carcter est compuesto por numerosos pixeles o puntos (dots) que estn amontonados dentro de un patrones distintos. Por eso hablamos de "pintar" en vez de "escribir". Normalmente, pintas texto en tu propia area cliente (relamente, puedes tambin pintar fuera del area cliente, pero eso es otra historia). En Windows, poner texto en la pantalla es algo radicalmente distinto a DOS. En DOS, puedes pensar en la pantalla como una dimensin de 80x25. Pero en Windows, la pantalla es compartida por varios programas. Algunas reglas deben ser reforzadas para evitar que los programas escriban sobre la pantalla de otros. Windows asegura esto limitando el rea para pintar de cada ventana a su rea cliente solamente. El tamao del area cliente de una ventana tampoco es constante. El usuario puede cambiarla en cualquier momento. As que hay que determinar dinmicamente las dimensiones del rea cliente de las ventanas. Antes de que puedas pintar algo sobre el rea cliente, debes pedir permiso a Windows. Eso es correcto, ya no tienes el control total sobre el monitor como lo tenas con DOS. Debes pedir permiso a Windows para pintar tu propia area cliente. Windows determinar el tamao de tu rea cliente, de la fuente, los colores y otros atributos GDI y regresar un manejador del contexto de dispositivo a tu programa. Luego puedes emplear tu contexto de dispositivo como un pasaporte para pintar tu rea cliente. Qu es un contexto de dispositivo? Es slo una estructura de datos que Windows mantiene en su interior. Un contexto de dispositivo est asociado con un dispositivo en particular, tal como una impresora o un monitor de video. Para un monitor de video, un contexto de dispositivo est normalmente asociado con una ventana particular en el monitor. Algunos valores en el contexto de dispositivo son atributos grficos como colores, fuentes etc. Estos son valores por defecto que se pueden cambiar a voluntad. Existen para ayudar a reducir la carga de tener que especificar estos atributos en todas las llamadas a funciones GDI. Puedes pensar en un contexto de dispositivo como un ambiente por defecto preparado para t por Windows. Luego puedes anular algunos de los elementos establecidos por defecto si quieres. Cuando un programa necesita pintar, debe obtener un manejador al contexto de dispositivo. Normalmente, hay varias maneras de realizar esto. llamar a BeginPaint en respuesta al mensaje WM_PAINT. llamar a GetDC en respuesta a otros mensajes. llamar a CreateDC para crear tu propio contexto de dispositivo Hay algo que debes recordar para despus de que tengas el manejador [handle] del contexto de dispositivo, y que debes realizar para el procesamiento de cualquier mensaje: no obtener el manejador en respuesta a un mensaje y emplearlo como respuesta a otro. Windows enva mensajes WM_PAINT a la ventana para notificar que es ahora el momento de volver a pintar su rea cliente. Windows no salva el contenido del rea cliente de una

ventana. En vez de eso, cuando ocurre una situacin que garantiza que se va a volver a pintar el rea cliente (tal como cuando una ventana ha sido cubierta por otra y luego descubierta), Windows pone el mensaje WM_PAINT en la cola de mensajes de ese programa. Es responsabilidad de Windows volver a pintar su propia rea cliente. Debes renir toda la informacin sobre cmo volver a pintar el rea cliente en la seccin WM_PAINT de tu procedimiento de ventana, as que tu procedimiento de ventana puede volver a pintar tu area cliente cuando llega el mensaje WM_PAINT. Otro concepto que debes tener en consideracin es el de rectngulo invlido. Windows define un rectngulo invlido como el rea rectangular ms pequea que el rea cliente necesita para volver a ser pintada. Cuando Windows detecta un rectngulo invlido en el rea cliente de una ventana, enva un mensaje WM_PAINT a esa ventana. En respuesta al mensaje WM_PAINT, la ventana puede obtener una estructura paintstruct que contiene, entre otras cosas, la coordenada del rectngulo invlido. Puedes llamar a BeginPaint en respuesta al mensaje WM_PAINT para validar el rectngulo invlido. Si no procesas el mensaje WM_PAINT, al menos debes llamar a DefWindowProc o a ValidateRect para validar el rectngulo invlido, sino Windows te enviar repetidamente el mensaje WM_PAINT. Estos son los pasos que deberas realizar en respuesta a un mensaje WM_PAINT: Obtener un manejador al contexto de dispositivo con BeginPaint. Pintar el rea cliente. Liberar el manejador del contexto de dispositivo con EndPaint Nota que no tienes que validar explcitamente el rectngulo invlido. Esto es realizado automticamente por la llamada a BeginPaint. Entre las llamadas a BeginPaint y EndPaint, puedes llamar cualquiera de las funciones GDI para pintar tu rea. Casi todas ellas requieren el manejador del contexto de dispositivo como parmetro. Contenido: Escribiremos un programa que despliega una cadena con el texto "Win32 assembly is great and easy!" en el centro del rea cliente.

.386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .DATA ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 OurText db "Win32 assembly is great and easy!",0 .DATA? hInstance HINSTANCE ? CommandLine LPSTR ? .CODE start:

invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst 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 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,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL rect:RECT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke GetClientRect,hWnd, ADDR rect invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd, ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret

.ENDIF xor eax, eax ret WndProc endp end start

Anlisis:
La mayora del cdigo es el mismo que el del ejemplo del tutorial 3. Slo explicar los cambios importantes. LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL rect:RECT Estas variables locales son usadas por las funciones GDI en tu seccin WM_PAINT. hdc es usado para almacenar el manejador al contexto de dispositivo regresado por la llamada a BeginPaint. ps es una estructura PAINTSTRUCT. Normalmente no tienes que usar los valores en ps. Es pasado a la funcin BeginPaint y Windows la llena con valores apropiados. Luego pasa ps a la funcin EndPaint cuando terminas de pintar el rea cliente. rect es una estructura RECT definida as:

RECT Struct left LONG ? top LONG ? right LONG ? bottom LONG ? RECT ends left y top son las coordenadas de la esquina izquierda superior de un rectngulo. right y bottom son las coordenadas de la esquina derecha inferior. Debe recordarse que: El origen de los ejes x-y est en la esquina superior izquierda. Entonces el punto y=10 est DEBAJO del punto y=0. invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke GetClientRect,hWnd, ADDR rect invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd, ADDR ps En respuesta al mensaje WM_PAINT, llamas a BeginPaint pasando como parmetros al manejador de la ventana que quieres pintar y una estructura PAINTSTRUCT no inicializada. Despus de una llamada exitosa, eax contiene el manejador al contexto de dispositivo. Luego llamas a GetClientRect para recobrar la dimensin del rea cliente. La dimensin es regresada en la variable rect variable que t pasas a DrawText como uno de sus parmetros. La sintaxis de DrawText es: DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD DrawText es una funcin de la API de alto-nivel para salida de texto. Maneja algunos detalles tales como ajuste de lnea, centramiento, etc. as que puedes concentrarte slo en la cadena que quieres pintar. Su hermana de bajo nivel, TextOut, ser examinada en el prximo tutorial. DrawText formatea una cadena de texto para fijar dentro de los lmites de un rectngulo. Emplea la fuente seleccionada en el momento, color y fondo (en el contexto de dispositivo) para dibujar texto. Las lneas son ajustadas para fijarla dentro de los lmites del rectngulo.

Regresa la altura del texto de salida en unidades de dispositivo, en nuestro caso, pixeles. Veamos sus parmetros: hdc manejador al contexto de dispositivo lpString El puntero a la cadena que quieres dibujar en el rectngulo. La cadena debe estar terminada en NULL o sino tendrs que especificar su largo en el parmetro de texto, nCount. nCount El nmero de caracteres para salida. Si la cadena es terminada en cero, nCount debe ser -1. De otra manera nCount debe contener el nmero de caracteres en la cadena que quieres dibujar. lpRect El puntero al rectngulo (una estructura de tipo RECT) donde quieres dibujar la cadena. Nota que este rectngulo tambin es un rectngulo recortante [clipping rectangule], es decir, no podrs dibujar la cadena fuera del rectngulo. uFormat El valor que especifica como la cadena es desplegada en el rectngulo. Usamos tres valores combinados por el operador "or": o o o DT_SINGLELINE especifica una lnea de texto DT_CENTER centra el texto horizontalmante. DT_VCENTER centra el texto verticalmente. Debe ser usado con DT_SINGLELINE.

Despus de terminar de pintar el rea cliente, debes llamar a la funcin EndPaint para liberar el manejador del contexto de dispositivo. Eso es todo. Podemos hacer un sumario de los puntos relevantes: Llamas a BeginPaint-EndPaint en respuesta la mensaje WM_PAINT. Haces lo que gustes con el rea cliente de la ventana entre las llamadas a las funciones BeginPaint y EndPaint. Si quieres volver a pintar tu rea cliente en respuesta a otros mensajes, tienes dos posibilidades: o Usar el par GetDC-ReleaseDC y pintar entre estas dos llamadas o Llamar a InvalidateRect o a UpdateWindow para invalidar toda el rea cliente, forzando a Windows a que ponga un mensaje WM_PAINT en la cola de mensajes de tu ventana y pinte durante la seccin WM_PAINT

Tutorial 5: Ms sobre Textos


Experimentaremos ms sobre los atributos de los textos, es decir, sobre las fuentes y los colores. Baja el archivo ejemplo aqu.

Teora:
El sistema de colores de Windows est basado en valores RGB, R=red (rojo), G=Green (verde), B=Blue (azul). Si quieres especificar un color en Windows, debes establecer el color que desees en trminos de estos tres colores mayores. Cada valor de color tiene un rango desde 0 a 255 (un valor de un byte). Por ejemplo, si quieres un color rojo puro, deberas usar 255,0,0. O si quieres un color blanco puro, debes usar 255,255,255. Puedes ver en los ejemplos que obtener el color que necesitas es muy difcil con este sistema ya que tienes que tener una buena comprensin de como mezclar y hacer corresponder los colores.

Para el color del texto y del fondo, usas SetTextColor y SetBkColor, que requieren un manejador al contexto del dispositivo y un valor RGB de 32-bit. La estructura dvalor RGB de 32-bit est definida as: RGB_value struct unused db 0 blue db ? green db ? red db ? RGB_value ends Nota que no se emplea el primer byte y que debera ser cero. El orden de los restantes tres bytes es inverso, es decir. azul, verde y rojo. Sin embargo, no usaremos esta estructura ya que es embarazoso inicializarla y usarla. Ms bien crearemos una macro. La macro recibir tres parmetros: los valores rojo, verde y azul. Esto producir el valor RGB 32-bit deseado y lo almacenar en eax. La macro es como sigue a continuacin: RGB macro red,green,blue xor eax,eax mov ah,blue shl eax,8 mov ah,green mov al,red endm Puedes poner esta macro en el archivo include para usarla en el futuro. Puedes "crear" una fuente llamando a CreateFont o a CreateFontIndirect. La diferencia entre las dos funciones es que CreateFontIndirect recibe slo un parmetro: un puntero a la estructura lgica de la fuente, LOGFONT. CreateFontIndirect es la ms flexible de las dos, especialmente si tus programas necesitan cambiar de fuentes con frecuencia. Sin embargo, como en nuestro ejemplo "crearemos" slo una fuente para demostracin, podemos hacerlos con CreateFont. Despus de llamada a CreateFont, regresar un manejador a la fuente que debes seleccionar dentro del contexto de dispositivo. Despus de eso, toda funcin de texto de la API usar la fuente que hemos seleccionado dentro del contexto de dispositivo.

Contenido: .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib RGB macro red,green,blue xor eax,eax mov ah,blue shl eax,8 mov ah,green

mov al,red endm .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 TestString db "Win32 assembly is great and easy!",0 FontName db "script",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst 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 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,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL hfont:HFONT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\ OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\ DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\ ADDR FontName invoke SelectObject, hdc, eax mov hfont,eax RGB 200,200,50 invoke SetTextColor,hdc,eax RGB 0,0,255 invoke SetBkColor,hdc,eax invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString invoke SelectObject,hdc, hfont invoke EndPaint,hWnd, ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

Anlisis:
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\ OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\ DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\ ADDR FontName CreateFont crear la fuente lgica que ms coincida con los parmetros dados y con los datos de fuentes disponibles. Esta funcin tiene ms parmetros que cualquier otra funcin en Windows. Regresa un manejador a la fuente lgica a ser usada por la funcin SelectObject. Examinaremos sus parmetros en detalle. CreateFont proto nHeight:DWORD,\ nWidth:DWORD,\ nEscapement:DWORD,\ nOrientation:DWORD,\ nWeight:DWORD,\ cItalic:DWORD,\ cUnderline:DWORD,\ cStrikeOut:DWORD,\ cCharSet:DWORD,\ cOutputPrecision:DWORD,\ cClipPrecision:DWORD,\ cQuality:DWORD,\

cPitchAndFamily:DWORD,\ lpFacename:DWORD nHeight La altura deseada para los caracteres. 0 significa el tamao por defecto. nWidth La ancho deseado para los caracteres. Normalmente este valor debera ser 0 que permite a Windows coordinar el ancho y el alto. Sin embargo, en nuestro ejemplo, el ancho por defecto hace difcil la lectura del texto, as que usaremos mejor un ancho de 16. nEscapement Especifica la orientacin del prximo caracter de salida relativa al previo en dcimas de grados. Normalmente, se pone en 0. Si se pone en 900 tendremos que todos los caracteres irn por encima del primer caracter, 1800 los escribir hacia atrs, o 2700 para escribir cada caracter desde abajo. nOrientation Especifica cunto debera ser rotado el caracter cuando tiene una salida en dcimas de grados. Si se pone en 900 todos los caracteres reposaran sobre sus respaldos, 1800 se emplea para escribirlos upside-down, etc. nWeight Establece el grosor de las lneas de cada caracter. Windows define los siguientes tamaos: FW_DONTCARE FW_THIN FW_EXTRALIGHT FW_ULTRALIGHT FW_LIGHT FW_NORMAL FW_REGULAR FW_MEDIUM FW_SEMIBOLD FW_DEMIBOLD FW_BOLD FW_EXTRABOLD FW_ULTRABOLD FW_HEAVY FW_BLACK equ 0 equ 100 equ 200 equ 200 equ 300 equ 400 equ 400 equ 500 equ 600 equ 600 equ 700 equ 800 equ 800 equ 900 equ 900

cItalic 0 para normal, cualquier otro valor para caracteres en itlicas. cUnderline 0 para normal, cualquier otro valor para caracteres subrayados. cStrikeOut 0 para normal, cualquier otro valor para caracteres con una lnea a travs del centro. cCharSet Especifica la configuracin de la fuente [character set]. Normalmente debera ser OEM_CHARSET lo cual permite a Windows seleccionar la fuente dependiente del sistema operativo. cOutputPrecision Especifica cunto la fuente seleccionada debe coincidir con las caractersticas que queremos. Normalmente debera ser OUT_DEFAULT_PRECIS que define la conducta de proyeccin por defecto de la fuente. cClipPrecision Especifica la presisin del corte. La precisin del corte define cmo recortar los caracteres que son parcialmente fuera de la regin de recorte. Deberas poder obtenerlo con CLIP_DEFAULT_PRECIS que define la conducta por defecto del recorte. cQuality Especifiica la cualidad de la salida. La cualidad de salida define cun cuidadosamente la GDI debe intentar hacer coincidir los atributos de la fuente lgica con los de la fuente fsica actual. Hay tres posibilidades: DEFAULT_QUALITY, PROOF_QUALITY and DRAFT_QUALITY. cPitchAndFamily Especifiica la pitch y familia de la fuente. Debes combinar el valor pitch value y el valor de familia con el operador "or". lpFacename Un puntero a una cadena terminada en cero que especifica la tipografa de la fuente. La descripcin anterior no tiene nada de comprensible. Deberas revisar la referencia de la APi de Win32 API para ms detalles.

invoke SelectObject, hdc, eax mov hfont,eax Despus de obtener el manejador lgico de la fuente, debemos usarlo para seleccionar la fuente dentro del contexto de dispositivo llamando a SelectObject. SelectObject pone los nuevos objetos GDI tales como bolgrafos, brochas, y fuentes dentro del contexto de dispositivo a ser usado por las funciones GDI. Este regresa el manejador del objeto reemplazado, el cual deberamos salvar para su uso posterior a la llamada a SelectObject. Despus de llamar a SelectObject, cualquier funcin de salida de texto, usar la fuente que seleccionamos dentro del contexto de dispositivo. RGB 200,200,50 invoke SetTextColor,hdc,eax RGB 0,0,255 invoke SetBkColor,hdc,eax Usa la macro RGB para crear un valor de 32-bit RGB para ser usado por SetColorText y SetBkColor. invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString Llama a la funcin TextOut para dibujar el texto sobre el rea cliente. El texto estar en la fuente y el color que especificamos previamente. invoke SelectObject,hdc, hfont Cuando estemos trabajando con fuentes, deberamos almacenar la fuente original dentro del contexto de dispositivo. Deberas almacenar siempre el objeto que reemplazaste en el contexto de dispositivo.

Tutorial 6: Entrada del teclado


Aprenderemos como un programa de Windows recibe entrada de teclado. Baja el ejemplo aqu.

Teora:
Como normalmente slo hay un teclado para cada PC, todos los programas de Windows deben compartirlo entre s. Windows es responsable de enviar los golpes de tecla a la ventana que tiene el foco de entrada. Aunque pueden haber varias ventanas en el monitor, slo una de ellas tiene el foco de entrada. La ventana que tiene el foco de entrada es la nica que puede recibir los golpes de tecla. Puedes diferenciar la ventana que tiene el foco de entrada de las otras ventanas observando la barra de ttulo. La barra de ttulo del programa que tiene el foco est iluminada. Realmente, hay dos tipos principales de mensajes de teclado, dependiendo de tu punto de vista sobre el teclado. Puedes ver el teclado como una coleccin de teclas. En este caso, si presionas una tecla, Windows enva un mensaje WM_KEYDOWN a la ventana que tiene el foco de entrada, que notifica que una tecla ha sido presionada. Cuando sueltas la tecla, Windows enva un mensaje WM_KEYUP. T tratas a las teclas como si fueran botones. Otra manera de ver el teclado es como un dispositivo de entrada de caracteres. Cuando presionas "una" tecla, Windows enva un mensaje WM_CHAR a la ventana que tiene el foco de entrada, dicindole que el usuario enva "un" caracter a ella. En realidad, Windows enva

mensajes WM_KEYDOWN y WM_KEYUP a la ventana que tiene el foco de entrada y esos mensajes sern traducidos a mensajes WM_CHAR por una llamada a TranslateMessage. El procedimiento de ventana puede decidir si procesa los tres mensajes o slo los mensajes que interesan. Muchas veces, podrs ignorar WM_KEYDOWN y WM_KEYUP ya que la funcin TranslateMessage en el bucle de mensajes traduce los mensajes WM_KEYDOWN y WM_KEYUP a mensajes WM_CHAR. En este tutorial nos concentraremos en WM_CHAR.

Ejemplo:
.386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 char WPARAM 20h ; el caracter que el programa recibe del teclado .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst 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 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,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_CHAR push wParam pop char invoke InvalidateRect, hWnd,NULL,TRUE .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke TextOut,hdc,0,0,ADDR char,1 invoke EndPaint,hWnd, ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

Analysis:
char WPARAM 20h ; el caracter que el programa recibe del teclado

Esta es la variable que guardar el caracter recibido del teclado. Como el caracter es enviado en WPARAM del procedimiento de ventana, por simplicidad definimos los tipos de variables como WPARAM. El valor inicial es 20h o el espacio, ya que cuando nuestra ventana refresque su rea cliente por primera vez, ah no habr caracter de entrada. As que preferimos desplegar el espacio.

.ELSEIF uMsg==WM_CHAR push wParam pop char invoke InvalidateRect, hWnd,NULL,TRUE Esto es agregado al manejador del mensaje WM_CHAR en el procedimiento de ventana. Pone el caracter dentro de la variable llamada "char" y luego llama a InvalidateRect. InvalidateRect hace que le rectngulo especfico en el rea cliente quede invalidado para forzar a Windows para que enve el mensaje WM_PAINT al procedimiento de ventana. Su sintaxis es como sigue: InvalidateRect proto hWnd:HWND,\ lpRect:DWORD,\ bErase:DWORD lpRect es un opuntero al rectngulo en el rea clienete que queremos declarar invlida. Si este parmetro es nulo, toda el rea cliente ser marcada como invlida. bErase es una bandera que dice a Windows si necesita borrar el fondo. Su ventana es TRUE, luego Windows borrar el fondo del rectngulo invalidado cuando se llama a BeginPaint. As que la estrategia que usamos aqu es: almacenamos toda la informacin necesaria involucrada en la accin de pintar el rea cliente y generar el mensaje WM_PAINT para pintar el rea cliente. Por supuesto, el cdigo en la seccin WM_PAINT debe saber de antemano qu se espera de ella. Esto parece una manera indirecta de hacer las cosas, pero as es como lo hace Windows. Realmente podemos pintar el rea cliente durante el proceso del mensaje WM_CHAR llamando el par de funciones GetDC y ReleaseDC. No hay problema. Pero lo gracioso comienza cuando nuestra ventana necesita volver a pintar su rea cliente. Como el cdigo que pinta el caracter est en la seccin WM_CHAR, el procedimiento de ventana no ser capaz de pintar nuestro caracter en el area cliente. As que la linea de abajo es: poner todos el cdigo y los datos necesarios para que realicen la accin de pintar en WM_PAINT. Puedes enviar el mensaje WM_PAINT desde cualquier lugar de tu cdigo cada vez que quieras volver a pintar el rea cliente. invoke TextOut,hdc,0,0,ADDR char,1 Cuando se llama a InvalidateRect, enva un mensaje WM_PAINT de regreso al procedimiento de ventana. De esta manera es llamado el cdigo en la seccin WM_PAINT. Llama a BeginPaint como es usual para obtener el manejador al contexto del dispositivo y luego llama a TextOut que dibuja nuestro caracter en el area cliente en x=0, y=0. Cuando corres el programa y presionas cualquier tecla, vers un "eco" [echo] del caracter en la esquina izquierda superior del rea cliente de la ventana. Y cuando la ventana sea minimizada, al ser maximizada de nuevo tendr todava el caracter ah ya que todo el cdigo y los datos esenciales para volver a pintar son todos activados en la seccin WM_PAINT.

Tutorial 7: Entrada del Ratn


Aprenderemos a recibir y a responder a la entrada del ratn en nuestro procedimiento de ventana. El programa esperar por los clicks del botn izquierdo del ratn y desplegar una cadena de texto en exactamente el punto indicado por el ratn sobre el rea cliente. Bajar el ejemplo de aqu. Teora:

Como con la entrada del teclado, Windows detecta y enva notificaciones sobre las actividades del ratn que son relevantes para las ventanas. Esas actividades incluyen los clicks de los botones izquierdo y derecho del ratn, el movimiento del cursor del ratn sobre la ventana, doble clicks. A diferencia de la entrada del teclado, que es dirigida a la ventana que tiene el foco de entrada, los mensajes del ratn son enviados a cualquier ventana sobre la cual est el cursor del ratn, activo o no. Adems, tambin hay mensajes del ratn sobre el rea no cliente. Pero la mayora de las veces, afortunademente podemos ignorarlas. Podemos concentrarnos en los mensajes relacionados con el rea cliente. Hay dos mensajes para cada botn el ratn: los mensajes WM_LBUTTONDOWN, WM_RBUTTONDOWN y WM_LBUTTONUP, WM_RBUTTONUP. Para un ratn con tres botones, estn tambin WM_MBUTTONDOWN and WM_MBUTTONUP. Cuando el cursor del ratn se mueve sobre el rea cliente, Windows enva mensajes WM_MOUSEMOVE a la ventana debajo del cursor. Una ventana puede recibir mensajes de doble clicks, WM_LBUTTONDBCLK o WM_RBUTTONDBCLK, si y slo si la clase de su ventana tiene activada la bandera correspondiente al estilo CS_DBLCLKS, sino la ventana recibir slo una serie de mensajes del topo botn del ratn arriba o abajo. Para todos estos mensajes, el valor de lParam contiene la posicin del ratn. La palabra [word] baja es la coordenada 'x', y la palabra alta es la coordenada 'y' relativa a la esquina izquierda superior del rea cliente de la ventana. wParam indica el estado de los botones del ratn y de las teclas Shift y Ctrl.

Ejemplo:
.386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 MouseClick db 0 ; 0=no click yet .data? hInstance HINSTANCE ? CommandLine LPSTR ? hitpoint POINT <> .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst 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 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,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_LBUTTONDOWN mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax mov eax,lParam shr eax,16 mov hitpoint.y,eax mov MouseClick,TRUE invoke InvalidateRect,hWnd,NULL,TRUE .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax .IF MouseClick invoke lstrlen,ADDR AppName invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax .ENDIF invoke EndPaint,hWnd, ADDR ps

.ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

Analysis:
.ELSEIF uMsg==WM_LBUTTONDOWN mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax mov eax,lParam shr eax,16 mov hitpoint.y,eax mov MouseClick,TRUE invoke InvalidateRect,hWnd,NULL,TRUE El procedimiento de ventana espera a que el botn izquierdo del ratn haga un click. Cuando recibe el mensaje WM_LBUTTONDOWN, lParam contiene la coordenada del botn del ratn en el rea cliente. Salva la coordenada en la variable de tipo POINT definida as: POINT STRUCT x dd ? y dd ? POINT ENDS y establece la bandera, MouseClick, a TRUE, lo que siginifica que al menos hay un click del botn izquierdo del ratn sobre el rea cliente. mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax Como la coordenada 'x' es la palabra baja de lParam y los miembros de la estructura POINT tiene un tamao de 32-bits, debemos poner en cero la palabara alta de eax antes de almacenarla en hitpoint.x. shr eax,16 mov hitpoint.y,eax Como la coordenada 'y' es la palabra alta de lParam, debemos ponerla en la palabra baja de eax antes de almacenarla en hitpoint.y. Hacemos esto desplazando [shifting] el contenido de eax 16 bits a la derecha. Despus de almacenar la posicin del ratn, establecemos la bandera, MouseClick, a TRUE con el fin de dejar que el cdigo de pintura en la seccin WM_PAINT sepa que hay al menos un click en el rea cliente y puede dibujar la cadena en la posicin del ratn. Luego llamamos a la funcin InvalidateRect para que Windows vuelva a pintar toda el rea cliente. .IF MouseClick invoke lstrlen,ADDR AppName invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax .ENDIF

El cdigo de pintura en la seccin WM_PAINT debe chequear si MouseClick es uno ( TRUE ), ya que cuando la ventana fue creada, recibi un mensaje WM_PAINT en ese momento, ningn click del ratn haba ocurrido an, as que no dibujar la cadena en el rea cliente. Inicializamos MouseClick a FALSE y cambiamos su valor a TRUE cuando ocurre un click del ratn. Si ha ocurrido al menos un click de ratn, se dibuja la cadena en el rea cliente en la posicin del ratn. Nota que se llama a lstrlen para obtener el tamao de la cadena a desplegar y enva este tamao como ltimo parmetro de la funcin TextOut. En este tutorial aprenderemos como incorporar un men a nuestra ventana. Bajar el ejemplo 1 y el ejemplo 2.

Teora:
El men es uno de los componentes ms importantes en nuestra ventana. El men presenta una lista de los servicios que un programa ofrece a un usuario. El usuario ya no tiene que leer el manual incluido con el programa para utilizarlo, ya que puede leerse cuidadosamente el men para obtener una visin general de las capacidades de un programa particular y comenzar a trabajar con l inmediatamante. Como el men es una herramienta para obtener el acercamiento del usuario y correr el programa rpidamente, se debera seguir siempre el estandard. Puesto sucintamente, los primeros dos elementos [items] del men deberan ser Archivo [File] y Editar [Edit] y el ltimo debera ser Ayuda [Help]. Puedes insertar tus propios elementos de men entre Editar y Ayuda. Si un elemento de men invoca una caja de dilogo, deberas anexar una ellipsis (...) a la cadena del men. El men es un tipo de recurso. Hay varios tipos de recursos, tales como dialog box, string table, icon, bitmap, men etc. Los recursos son descritos en un archivo separado llamado archivo de recursos, el cual generalmente tiene extensin .rc. Luego combinas los recursos cion el archivo fuente durante el estado de enlace. El resultado final es un archivo ejecutable que contiene tanto instrucciones como recursos. Puedes escribir guiones [scripts] de recursos usando un editor de texto. Estos guiones estn compuestos por frases que describen la apariencia y otros atributos de los recursos usados en un programa particular. Aunque puedes escribir guiones de recursos con un editor de texto, esto resulta ms bien embarazoso. Una mejor altrenativa es usar un editor de recursos que te permita visualizar con facilidad el diseo de los recursos. Usualmente los paquetes de compiladores como Visual C++, Borland C++, etc, incluyen editores de recursos Describes los recursos ms o menos as:

Mymenu MENU { [menu list here] } Los programadores en lenguaje C pueden reconocer que es similar a la declaracin de una estructura. MyMenu sera un nombre de men seguido por la palabra clave MENU y una lista de men entre llaves. Alternativamente, si quieres puedes usar BEGIN y END en vez de las llaves. Esta sintaxis es mas portable para los programadores en Pascal. La lista de men puede ser un enunciado MENUITEM o POPUP. El enunciado MENUITEM define una barra de men que no invoca un men emetgente [popup] cuando es seleccionado. La sintaxis es como sigue:

MENUITEM "&text", ID [,options] Se comienza por la palabra clave MENUITEM seguida por el texto que quieres usar como cadena de texto de la barra de men. Nota el ampersand (&). Hace que el carcter que le sigue sea subrayado. Despus de la cadena de texto est el ID del elemento de men. El ID es un nmero que ser usado para identificar el elemento de men respectivo en el mensaje enviado al procedimiento de ventana cuando el elemento de men es selccionado. Como tal, cada ID de men debe ser nico entre ellos. Las opciones son 'opcionales'. Las disponibles son: GRAYED El elemento del men est inactivo, y no genera un mensaje WM_COMMAND. El texto est difuminada [grayed]. INACTIVE El elemento del men est inactivo, y no genera un mensaje WM_COMMAND. El texto es desplegado normalmente. MENUBREAK Este elemento y los siguientes aparecen en una lnea nueva del men. HELP Este elemento y los siguientes estn justificados a la derecha.

Puedes usar una de las opciones de arriba o combinarlas con el operador "or". Slo recuerda que INACTIVE y GRAYED no pueden ser combinados simultneamente. El enunciado POPUP tiene la siguiente sintaxis:

POPUP "&text" [,options] { [menu list] }

El enunciado POPUP define una barra de men que, cuando es seleccionada, despliega una lista de elementos de men en una ventana emergente. La lista de men puede ser una enunciado MENUTIEM o POPUP. Hay un tipo especial de enunciado MENUITEM, MENUITEM SEPARATOR, que dibuja una linea horizontal en la ventana emergente. El paso siguiente, despus de haber terminado con el guin de recursos, es hacer la referencia a l en el programa. Esto se puede hacer de dos maneras. En el miembro lpszMenuName de la estructura WNDCLASSEX. Es decir, si tienes un men llamado "FirstMenu", puedes asignar este men a tu ventana de la siguiente manera: .DATA MenuName db "FirstMenu",0 ........................... ........................... .CODE

........................... mov wc.lpszMenuName, OFFSET MenuName ........................... En el parmetro del manejador del men de CreateWindowEx ms o menos as: .DATA MenuName db "FirstMenu",0 hMenu HMENU ? ........................... ........................... .CODE ........................... invoke LoadMenu, hInst, OFFSET MenuName mov hMenu, eax invoke CreateWindowEx,NULL,OFFSET ClsName,\ OFFSET Caption, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,\ NULL,\ hMenu,\ hInst,\ NULL\ ........................... Entonces podras preguntarte, cul es la diferencia entre estos dos mtodos? Cuando haces referencia al men en la estructura WNDCLASSEX , el men llega a ser el men "por defecto" para la clase de ventana. Todas las ventanas de esa clase tendrn el mismo men. Si quieres que cada ventana creada a partir de la misma clase tenga diferente men, debes elegir la segunda manera. En este caso, cualquier ventana que se le pase un manejador de men en su funcin CreateWindowEx tendr un men que "reemplazar" el men por defecto definido en la estructura WNDCLASSEX. Ahora examinaremos como un men notifica al procedimiento de ventana cuando el usuario selecciona un elemento del men.

Cuando el usuario selecciona un elemento del men, el procedimiento de ventana recibir un


mensaje WM_COMMAND. La palabra baja de wParam contendr el ID del elemento del men. Ahora tienes suficiente informacin para usar el men. Vamos a hacerlo.

Ejemplo:
El primer ejemplo muestra cmo crear y usar un men especificando el nombre del men en la clase de ventana. .386 .model flat,stdcall option casemap:none

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 .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 MenuName db "FirstMenu",0 ; Nombre de nuestro men en el archivo .RC Test_string db "You selected Test menu item",0 Hello_string db "Hello, my friend",0 Goodbye_string db "See you again, bye",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .const IDM_TEST equ 1 IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4

; Menu IDs

.code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName ; Poner aqu el nombre del Men 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 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,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start ************************************************************************************************************* *************

Menu.rc ************************************************************************************************************* ************* #define IDM_TEST 1 #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4 FirstMenu MENU { POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT }

MENUITEM "&Test", IDM_TEST }

Anlisis:
vamos a analizar primero el gun de recursos.

#define IDM_TEST 1 #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4

/* equal to IDM_TEST equ 1*/

Las lneas de arriba definen los IDs de men usados por el guin de men. Puedes asignar cualquier valor al ID siempre que sea nico en el men. FirstMenu MENU Declarar tu men con la palabra clave MENU. POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT } Definir un men emergente con cuatro elementos, donde el tercer elemento es un separador. MENUITEM "&Test", IDM_TEST Definir una barra de men en el men principal. Ahora examinaremos el cdigo fuente.

MenuName db "FirstMenu",0 ; Nombre del men en el archivo de recursos. Test_string db "You selected Test menu item",0 Hello_string db "Hello, my friend",0 Goodbye_string db "See you again, bye",0

MenuName es el nombre del men en el archivo de recursos. Nota que puedes definir ms de un men en el archivo de recursos as que debes especificar cual usar. Las restantes tres lneas definen la cadena de texto a ser desplegada en las cajas de mensaje que son invocadas cuando el elemento de men apropiado es selecionado por el usuario.

IDM_TEST equ 1 IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4

; Menu IDs

Definir IDs de elementos de men para usar en el procedimiento de ventana. Estos valores DEBEN ser idnticos a los definidos en el archivo de recursos. .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF En el procedimiento de ventana, procesamos mensajes WM_COMMAND. Cuando el usuario seleccione un elemento de men, el ID de ese eemento de men es enviado al procedimiento de ventana en la palabra baja de wParam junto con el mensaje WM_COMMAND. As que cuando almacenamos el valor de wParam en eax, comparamos el valor en ax con los IDs de men que definimos previamente y actuamos de acuerdo con ello. En los primeros tres casos, cuando el usuario selecciona los elementos de men Test, Say Hello, y Say GoodBye, desplegamos una cadena de texto en la caja de mensaje. Si el usuario selecciona el elemento de men Exit, llamamos DestroyWindow con el manejador de nuestra ventana como su parmetro que cerrar nuestra ventana. Como puedes ver, especificar el nombre del men en una clase de ventana es muy fcil y directo. Sin embargo, tambin puedes usar un mtodo alternativo para cargar un men en tu ventana. No mostrar aqu todo el cdigo fuente. El archivo de recursos es el mismo en ambos mtodos. Hay algunos cambios menores en el archivo fuente que mostrar abajo.

.data? hInstance HINSTANCE ? CommandLine LPSTR ? hMenu HMENU ?

; handle of our menu

Definir un tipo de variable HMENU para almacenar nuestro manejador de men. invoke LoadMenu, hInst, OFFSET MenuName mov hMenu,eax INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\ hInst,NULL Antes de llamar a CreateWindowEx, llamamos a LoadMenu con el manejador de instancia del programa y un puntero al manejador de nuestro men. LoadMenu regresa el manejador de nuestro men en el archivo de recursos que se pas a CreateWindowEx.

Tutorial 9: Controles de Ventanas Hijas

En este tutorial exploraremos los controles de ventana hija que son unos dispositivos de entrada y salida muy importantes dentro de nuestros programas. Baja el ejemplo aqu.

Teora:
Windows provee algunas clases de ventana predefinidas que podemos usar satisfactoriamente dentro de nuestros programas. Muchas veces las usamos como componentes de una caja de dilogo por lo que ellas usualmente son llamadas controles de ventanas hijas. Los controles de ventanas hijas procesan sus propios mensajes de teclado y de ratn y notifican a las ventanas padres cuando sus estados han cambiado. Ellos liberan al programador de enormes cargas, as que deberas usarlas cada vez que sea posible. En este tutorial las pongo sobre una ventana normal para demostrar cmo puedes crearlas y usarlas, pero en realidad deberas ponerlas en una caja de dilogo. Ejemplos de clases de ventanas predefinidas son el botn, la caja de lista [listbox], la caja de chequeo [checkbox], botn de radio [radio button], edicin [edit] etc. Con el fin de usar el control de vemtana hija, debes crearla con CreateWindow o CreateWindowEx. Nota que no tienes que registrar la clase de ventana puesto que Windows lo hace por t. El parmetro nombre de la clase DEBE ser el nombre de la clase predefinida. Es decir, si quieres crear un botn, debes especificar "button" como nombre de la clase en CreateWindowEx. Los otros parmetros que debes llenar son la agarradera o manejador [handle] de la ventana padre y el ID del control. El ID del control debe ser nico entre los controles. El ID del control es el ID de ese control. Lo usas para diferenciar entre controles. Despus de que el control fue creado, enviar mensajes de notificacin a la ventana padre cuando su estado cambie. Normalmente, creas las ventanas hijas durante el mensaje WM_CREATE de la ventana padre. La ventana hija enva mensajes WM_COMMAND a la ventana padre con su ID de control en la palabra baja de wParam, el cdigo de notificacin en la palabra alta de wParam, y su manejador de ventana en lParam. Cada conrtol de ventana hija tiene su propio cdigo de notificacin, as que debes revisar la referencia de la API de Win32 para ms informacin. Tambin la ventana padre puede enviar rdenes o comandos a la ventana hija, llamando a la funcin SendMessage. Esta funcin enva el mensaje especificado acompaado de otrops valores en wParam y lParam a la ventana especificada por el manejador de ventana. Es una funcin extremadamente til, ya que puede enviar mensajes a cualquier ventana que conozcas su manejador. As que despus de crear ventanas hijas, la ventana padre debe procesar los mensajes WM_COMMAND para poder recibir cdigos de notificacin desde las ventanas hijas.

Ejemplo:
Crearemos una ventana que contenga un control de edicin y un "pushbutton". Cuando pulses el botn, un cuadro de mensaje aparecer mostrando un texto que hayas escrito en el cuadro de dilogo. Hay tambin un men con 4 elementos:

1. 2. 3. 4.

Say Hello -- Pone una cadena de texto en la caja de edicin Clear Edit Box -- Limpia el contenido de la caja de edicin Get Text -- despliega una caja de mensajes con el texto en la caja de edicin Exit -- Cierra el programa.

.386 .model flat,stdcall option casemap:none 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 .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 MenuName db "FirstMenu",0 ButtonClassName db "button",0 ButtonText db "My First Button",0 EditClassName db "edit",0 TestString db "Wow! I'm in an edit box now",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? hwndButton HWND ? hwndEdit HWND ? buffer db 512 dup(?) ; buffer para almacenar el texto recuperado desde la ventana de edicin .const ButtonID equ 1 EditID equ 2 IDM_HELLO equ 1 IDM_CLEAR equ 2 IDM_GETTEXT equ 3 IDM_EXIT equ 4

; El ID del control botn [button] ; El ID del control de edicin [edit]

.code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_BTNFACE+1 mov wc.lpszMenuName,OFFSET MenuName

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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \ ADDR AppName, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT, CW_USEDEFAULT,\ 300,200,NULL,NULL, hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\ WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\ ES_AUTOHSCROLL,\ 50,35,200,25,hWnd,8,hInstance,NULL mov hwndEdit,eax invoke SetFocus, hwndEdit invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\ WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\ 75,70,140,25,hWnd,ButtonID,hInstance,NULL mov hwndButton,eax .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 .IF ax==IDM_HELLO invoke SetWindowText,hwndEdit,ADDR TestString .ELSEIF ax==IDM_CLEAR invoke SetWindowText,hwndEdit,NULL .ELSEIF ax==IDM_GETTEXT invoke GetWindowText,hwndEdit,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE .IF ax==ButtonID shr eax,16 .IF ax==BN_CLICKED invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0 .ENDIF .ENDIF .ENDIF .ELSE

invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

Analysis:
Analicemos el programa . .ELSEIF uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE, \ ADDR EditClassName,NULL,\ WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\ or ES_AUTOHSCROLL,\ 50,35,200,25,hWnd,EditID,hInstance,NULL mov hwndEdit,eax invoke SetFocus, hwndEdit invoke CreateWindowEx,NULL, ADDR ButtonClassName,\ ADDR ButtonText,\ WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\ 75,70,140,25,hWnd,ButtonID,hInstance,NULL mov hwndButton,eax Creamos los controles cuando procesamos el mensaje WM_CREATE. Llamamos a CreateWindowEx con el estilo de ventana extra, WS_EX_CLIENTEDGE, el cual hace que la ventana cliente se vea con un borde sobreado. El nombre de cada control es un nombre predefinido, "edit" para el control de edicin, "button" para el control botn. Luego especificamos los estilos de ventanas hijas. Cada control tiene estilos extras adems de los estilos de ventana normal. Por ejemplo, los estilos de botn llevan el prefijo "BS_" para "button style", los estilos del control de edicin llevan "ES_" para "edit style". Tienes que revisar estos estilos en la referenicia de la API de Win32. Nota que pones un ID de control en lugar del manejador del men. Esto no causa problemas ya que el control de una ventana hija no puede tener men. Despus de crear cada control, guardamos su manejador en una variable para su futuro uso. Se llama a SetFocus para dar fooco de entrada a la caja de edicin de manera que el usuario pueda tipear texto dentro de ella immediatamente. Ahora la parte realmente exitante. Todo control de ventana hija enva una notificacin a su ventana padre con WM_COMMAND. .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 Recuerda que tambin un menu enva mensajes WM_COMMAND para notificar a su ventana sobre su estado. Cmo puedes diferenciar si los mensajes WM_COMMAND son originados desde un men o desde un control? He aqu la respuesta:

Palabra baja de wParam Menu Menu ID

Palabra alta de wParam 0

lParam 0

Control Control ID

Cdigo de notificacin

Manjeador de ventana hija

Como puedes ver, hay que chequear lParam. Si es cero, el mensaje WM_COMMAND actual es de un men.No puedes usar wParam para diferenciar entre un men y un control porque el ID del men y el ID del control pueden ser idnticos y el cdigo de notificacin puede ser cero. .IF ax==IDM_HELLO invoke SetWindowText,hwndEdit,ADDR TestString .ELSEIF ax==IDM_CLEAR invoke SetWindowText,hwndEdit,NULL .ELSEIF ax==IDM_GETTEXT invoke GetWindowText,hwndEdit,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK Puedes poner una cadena de texto dentro de una caja de edicin llamando a SetWindowText. Limpias el contenido de la caja de edicin llamando a SetWindowText con NULL. SetWindowText es una funcin de la API de propsito general. Puedes usar SetWindowText para cambiar el encabezamiento o ttulo [caption] de una ventana o el texto sobre un botn. Para obtener el texto en una caja de edicin, usas GetWindowText. .IF ax==ButtonID shr eax,16 .IF ax==BN_CLICKED invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0 .ENDIF .ENDIF El fragmento de cdigo de arriba tiene que ver con la condicin de si el usuario presiona el botn. Primero, chequea la palabra baja de wParam para ver si el ID del control coincide con el del botn. Si es as, chequea la palabra alta de wParam para ver si es el cdigo de notificacin BN_CLICKED que se enva cuando el botn es pulsado. La parte interesante es despus que el cdigo de notificacin es BN_CLICKED. Queremos obtener el texto de la caja de edicin y desplegarlo en la caja de edicin. Podemos duplicar el cdigo en la seccin IDM_GETTEXT de arriba pero no tiene sentido. Si de alguna manera podemos enviar un mensaje WM_COMMAND con el valor IDM_GETTEXT en la palabra baja de wParam a nuestro procedimiento de ventana, podemos evitar duplicacin de cdigo y simplificar nuestro programa. La funcin SendMessage es la respuesta. Esta funcin enva cualquier mensaje a cualquier ventana con cualquieras wParam y lParam que desiemos. As que e nvez de duplicar cdigo, llamamos a SendMessage con el manejador de ventana padre, WM_COMMAND, IDM_GETTEXT, y 0. Esto tiene un efecto idntico que seleccionar el elemento "Get Text" de nuestro men. El procedimiento de ventana no percibir ninguna diferencia entre los dos. Deberas usar estas tcnicas en la medida de lo posible para que tu cdigo sea ms organizado. Por ltimo, no olvides poner la funcin TranslateMessage en el bucle de mensajes, puesto que, como debes tipear algn texto en la caja de edicin, tu programa debe traducir la entrada cruda del teclado a texto que pueda ser ledo. Si omites esta funcin, no sers capz de editar nada en la caja de edicin.

Tutorial 10: Caja de Dilogo [Dialog Box] como Ventana Principal


Ahora viene la parte realmente interesante sobre GUI, la caja de dilogo. En este tutorial (y en el prximo), aprenderemos como podemos usar una caja de dilogo como programa principal. Bajar el primer ejemplo aqu, el segundo ejemplo aqu.

Teora:
Si juegas bastante con los ejemplos del tutorial anterior, encontrars que no puedes cambiar el foco de entrada de un control de ventana hija a otra con la tecla Tab. La nica manera de realizar eso es haciendo click sobre el control que deseas que gane el foco de entrada. Esta situacin es ms bien incmoda. Otra cosa que deberas notar es que cambi el color del fondo de la ventana padre a gris en vez de a blanco, como lo haba hecho en los ejemplos previos. Esto se hace as para que el color de la ventana hija pueda armonizar con el color del rea cliente del ventana padre. Hay otra manera de salvar este problema pero no es fcil. Tienes que subclasificar todos los controles de ventana hija en tu ventana padre. La razn de la existencia de tal inconveniente es que los controles de ventana hija estn originalmente diseados para trabajar dentro de cajas de dilogo, no en una ventana normal. Los colores por defecto de los controles de ventanas hijas, como los botones, es gris porque el rea cliente de la caja de dilogo normalmente es gris para que armonicen entre s sin ninguna intervensin por parte del programador. Antes de entrar en detalles, deberamos saber qu es una caja de dilogo. Una caja de dilogo no es ms que una ventana normal diseada para trabajar con controles de ventanas hijas. Windows tambin proporciona un administrador interno de cajas de dilogo ["dialog box manager"] responsable por gran parte de la lgica del teclado tal como desplazamiento del foco de entrada cuando el ususario presiona Tab, presionar el botn por defecto si la tecla Enter es presionada, etc; as los programadores pueden ocuparse de tareas de ms alto nivel. Las cajas de dilogo son usadas primero como dispositivos de entrada/salida. Como tal, una caja de dilogo puede ser considerada como una "caja negra" de entrada/salida lo que siginifica que no tienes que saber cmo funciona internamente una caja de dilogo para usarla, slo tienes que saber cmo interactuar con ella. Es un principio de la programacin orientada a objetos [object oriented programming (OOP)] llamado encapsulacin u ocultamiento de la informacin. Si la caja negra es *perfectamente* diseada , el usuario puede emplarla sin tener conocimiento de cmo funciona. Lo nico es que la caja negra debe ser perfecta, algo difcil de alcanzar en el mundo real. La API de Win32 API tambin ha sido diseada como una caja negra. Bien, parece que nos hemos alejado de nuestro camino. Regresemos a nuestro tema. Las cajas de dilogo han sido diseadas para reducir la carga de trabajo del programador. Normalmente si tienes que poner controles de ventanas hijas sobre una ventana normal, tienes que subclasificarlas y escribir t mismo la lgica del teclado. Pero si quieres ponerlas en una caja de dilogo, Windows manejar la lgica por t. Slo tienes que saber cmo obtener la entrada del usuario de la caja de dilogo o como enviar rdenes a ella. Como el men, una caja de dilogo se define como un recurso. Escribes un plantilla describiendo las caractersticas de la caja de dilogo y sus controles y luego compilas el guin de recursos con un compilador de recursos. Nota que todos los recursos se encuentran en el mismo archivo de guin de recursos. Puedes emplear cualquier editor de texto para escribir un guin de recursos, pero no lo recomiendo. Deberas usar un editor de recursos para hacer la tarea visualmente ya que arreglar la disposicin de los controles en la caja de dilgo es una tarea dura de hacer manualmente. Hay disponibles algunos excelentes editores de recursos. Muchos de las grandes suites de compiladores incluyen sus propios editores de recursos. Puedes usar cualquiera para crear un

guin de recursos para tu programa y luego cortar las lneas irrelevantes tales como las relacionadas con MFC. Hay dos tipos principales de cajas de dilogo: modal y no-modal. Una caja de dilogo no-modal te deja cambiar de foco hacia otra ventana. Un ejempo es el dilogo Find de MS Word. Hay dos subtipos de caja de dilogo modal: modal de aplicacin y modal de sistema. Una caja de dilogo modal de aplicacin no permite cambiar el foco a otra ventana en la misma aplicacin sino cambiar el foco de entrada a la ventana de OTRA aplicacin. Una caja de dilogo modal de sistema no te permite cambiar de foco hacia otra ventana hasta que respondas a la primera. Una caja de dilogo no-modal se crea llamando a la funcin de la API CreateDialogParam. Una caja de dilogo modal se crea llamando a DialogBoxParam. La nica diferencia entre una caja de dilogo de no-modal y una modal de sistema es el estilo DS_SYSMODAL. Si quieres incluir el estilo DS_SYSMODAL en una plantilla de caja de dilogo, esa caja de dilogo ser modal de sistema. Puedes comunicarte con cualquier control de ventana hija sobre una caja de dilogo usando la funcin SendDlgItemMessage. Su sintaxis es:

SendDlgItemMessage proto hwndDlg:DWORD,\ idControl:DWORD,\ uMsg:DWORD,\ wParam:DWORD,\ lParam:DWORD Esta llamada a la API es inmensamene til para interactuar con un control de ventana hija. Por ejemplo, si quieres obtener el texto de un control de edicin, puedes hacer esto: call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer Con el fin de saber qu mensaje enviar, deberas consultar la referencia de la API de Win32. Windows tambin provee algunas funciones especficas de la API para controles que permiten obtener y poner datos en los controles rpidamente, por ejemplo, GetDlgItemText, CheckDlgButton etc. Estas funciones especficas para controles son suministradas para conveniencia de los programadores de manera que l no tenga que revisar el significado de wParam y lParam para cada mensaje. Normalmente, deberas usar llamadas a las funciones especficas de la API para controles cada vez que sean disponibles ya que ellas facilitan el mantenimiento del cdigo fuente. Recurre a SendDlgItemMessage slo si no hay disponible llamadas a funciones especficas de la API. El manejador de Windows de cajas de dilogos enva varios mensajes a una funcin " callback" particular llamada procedimiento de caja de dilogo que tiene el siguiente formato: DlgProc proto hDlg:DWORD ,\ iMsg:DWORD ,\ wParam:DWORD ,\ lParam:DWORD El procedimiento de dilogo es similar al procedimiento de ventana excepto por el tipo de valor de retorno, que es TRUE/FALSE en vez de LRESULT. El administrador interno de la caja de dilogo dentro de Windows ES el verdadero procedimiento de ventana para la caja de dilogo. Llama a nuestra caja de dilogo con algunos mensajes que recibi. As que la regla general es que: si nuestro procedimiento de dilogo procesa un mensaje, DEBE regresar TRUE (1) en eax y si no procesa el mensaje, debe regresar FALSE (0) en eax. Nota que un procedimiento de caja de dilogo no pasa los mensajes no procesados a la llamada DefWindowProc ya que no es realmente un procedimiento de ventana.

Hay dos usos distintos de una caja de dilogo. Puedes usarlas como ventanas principal de tu aplicacin o usarla como un dispositivo de entrada. Examinaremos el primer acercamiento en este tutorial. "Usar una caja de dilogo como una ventana principal" puede ser interpretado en dos sentidos.

1. Puedes usar una plantilla de caja de dilogo como la plantilla de clase que registras al
llamar a RegisterClassEx. En este caso, la caja de dilogo se comporta como una ventana "normal": recibe mensajes del procedimiento de ventana referido por el miembro lpfnWndProc de la clase de ventana class, no a travs de un procedimiento de caja de dilogo. El beneficio de esto es que no tienes que crear por t mismo controles de ventana hija, Windows los crea por t cuando se crea la caja de dilogo. Tambin Windows maneja la lgica del teclado para t, por ejemplo se encarga de la orden Tab, etc. Adems puedes especificar el cursor y el icono de tu ventana en la estructura de la clase de ventana.

2.
Tu programa crea la caja de dilogo sin ninguna ventana padre. Esta aproximacin al problema hace inecesario el uso de un bucle de mensajes ya que los mensajes son enviados directamente al procedimiento de ventana de la caja de dilogo. Ya no tienes que registrar la clase de ventana! Este tutorial va a ser un poco largo. Presentar la primera aproximacin seguida por la segunda.

Ejemplos:
dialog.asm

.386 .model flat,stdcall option casemap:none 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 .data ClassName db "DLGCLASS",0 MenuName db "MyMenu",0 DlgName db "MyDialog",0 AppName db "Our First Dialog Box",0 TestString db "Wow! I'm in an edit box now",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? buffer db 512 dup(?) .const IDC_EDIT equ 3000 IDC_BUTTON equ 3001 IDC_EXIT equ 3002 IDM_GETTEXT equ 32000

IDM_CLEAR IDM_EXIT

equ 32001 equ 32002

.code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hDlg:HWND 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,DLGWINDOWEXTRA push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_BTNFACE+1 mov wc.lpszMenuName,OFFSET MenuName 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 invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL mov hDlg,eax invoke ShowWindow, hDlg,SW_SHOWNORMAL invoke UpdateWindow, hDlg invoke GetDlgItem,hDlg,IDC_EDIT invoke SetFocus,eax .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke IsDialogMessage, hDlg, ADDR msg .IF eax ==FALSE invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDIF .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 .IF ax==IDM_GETTEXT invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

.ELSEIF ax==IDM_CLEAR invoke SetDlgItemText,hWnd,IDC_EDIT,NULL .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE mov edx,wParam shr edx,16 .IF dx==BN_CLICKED .IF ax==IDC_BUTTON invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString .ELSEIF ax==IDC_EXIT invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .ENDIF .ENDIF .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

Dialog.rc

#include "resource.h" #define IDC_EDIT #define IDC_BUTTON #define IDC_EXIT #define IDM_GETTEXT #define IDM_CLEAR #define IDM_EXIT 3000 3001 3002 32000 32001 32003

MyDialog DIALOG 10, 10, 205, 60 STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK CAPTION "Our First Dialog Box" CLASS "DLGCLASS" BEGIN EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13 PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP END

MyMenu MENU BEGIN POPUP "Test Controls" BEGIN MENUITEM "Get Text", IDM_GETTEXT

MENUITEM "Clear Text", IDM_CLEAR MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/ MENUITEM "E&xit", IDM_EXIT END END

Anlisis:
Vamos a analizar el primer ejemplo. Este ejemplo muestra como registrar una plantilla de dilogo como una clase de ventana y crear una "ventana" a partir de esa clase. Simplifica tu programa ya que no tienes que crear t mismo los controles de ventana hija. Vamos a analizar primero la plantilla de dilogo. MyDialog DIALOG 10, 10, 205, 60 Declarar el nombre del dilogo, en este caso, "MyDialog" seguido por la palabra clave "DIALOG". Los siguientes cuatro nmeros son: x, y , ancho, y alto de la caja de dilogo en unidades de caja de dilogo no de pixeles [not the same as pixels]. STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK Declarar los estilos de la caja de dilogo. CAPTION "Our First Dialog Box" Este es el texto que aparecer en la barra de ttulo de la caja de dilogo. CLASS "DLGCLASS" Esta lnea es crucial. Es esta palabra clave, CLASS, lo que nos permite usar la caja de dilogo como una clase de ventana. Despus de la palabra clave est el "nombre de la clase" BEGIN EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13 PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13 END El bloque de arriba define los controles de ventana hija en la caja de dilogo. Estn definidos entre las palabras claves BEGIN y END. Generalmente la sintaxis es la siguiente: control-type "text" ,controlID, x, y, width, height [,styles] los tipos de controles son constantes del compilador de recursos as que tienes que consultar el manual. Ahora vamos a ensamblar el cdigo fuente. La parte interesante es la estructura de la clase de ventana: mov wc.cbWndExtra,DLGWINDOWEXTRA mov wc.lpszClassName,OFFSET ClassName Normalmente, este miembro se deja NULL, pero si queremos registrar una plantilla de caja de dilogo como una clase de ventana, debemos poner en este miembro el valor

DLGWINDOWEXTRA. Nota que el nombre de la clase debe ser idntico al que sigue a la palabra clave CLASS en la plantilla de la caja de dilogo. Los miembros restantes se inicializan como es usual. Despus de que llenas la estructura de la clase de ventana, la registras con RegisterClassEx. No te resulta esto familiar? Esta es la misma rutina que tienes que hacer con el fin de registrar una clase de ventana normal. invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL Despus de registrar la "clase de ventana", creamos nuestra caja de dilogo. En este ejemplo, lo creo como una caja de dilogo modal con la funcin CreateDialogParam. Esta funcin toma 5 parmetros, pero slo tienes que llenar los primeros dos: el manejador de instancia y el puntero al nombre de la plantila de la caja de dilogo. Nota que el segundo parmetro no es un puntero al nombre de la clase. En este punto, la caja de dilogo y sus controles de ventana hija son creados por Windows. Tu procedimiento de ventana recibir el mensaje WM_CREATE como es usual. invoke GetDlgItem,hDlg,IDC_EDIT invoke SetFocus,eax Despus de que es creada la caja de dilogo, quiero poner el foco de entrada a la caja de edicin. Si pongo estas instrucciones en la seccin WM_CREATE, la llamada a GetDlgItem fallar ya que en ese momento, ya que en ese instante todava no se han creado los controles de ventana hija. La nica manera de hacer esto es llamarlo despus de que la caja de dilogo y todos los controles de ventana hija son creados. As que pongo estas dos lineas despus de la llamada a UpdateWindow. La funcin GetDlgItem obtiene el ID del control y regresa el manejador del control de ventana asociado. As es como puedes obtener el manejador de un control de ventana hija si tienes su ID. invoke IsDialogMessage, hDlg, ADDR msg .IF eax ==FALSE invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDIF El programa introduce el bucle de mensajes y antes de que traduzcamos y despachemos mensajes, llamamos a la funcin IsDialogMessage para permitir que el administrador [manager] de la caja de dilogo maneje el teclado lgico de nuestra caja de dilogo. Si la funcin regresa TRUE, significa que el mensaje es enviado a la caja de dilogo y es procesado por el administrador de la caja de dilogo. Nota ahora la diferencia respecto al tutorial previo. Cuando el procedimiento de ventana quiere obtener el texto del control de edicin, llama a la fucin GetDlgItemText en vez de GetWindowText. GetDlgItemText acepta un ID de control en vez de un manejador de ventana. Eso facilita la llamada en el caso de que utilices una caja de dilogo.

Ahora vamos a la segunda aproximacin de cmo usar una caja de dilogo como ventana principal. En el siguiente ejemplo, crear una aplicacin de caja de dilogo modal. No encontrars un bucle de mensaje ni un procedimiento de ventana porque no son necesarios!

dialog.asm (part 2)

.386

.model flat,stdcall option casemap:none DlgProc 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 .data DlgName db "MyDialog",0 AppName db "Our Second Dialog Box",0 TestString db "Wow! I'm in an edit box now",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? buffer db 512 dup(?) .const IDC_EDIT IDC_BUTTON IDC_EXIT IDM_GETTEXT IDM_CLEAR IDM_EXIT

equ 3000 equ 3001 equ 3002 equ 32000 equ 32001 equ 32002

.code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL invoke ExitProcess,eax DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_INITDIALOG invoke GetDlgItem, hWnd,IDC_EDIT invoke SetFocus,eax .ELSEIF uMsg==WM_CLOSE invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 .IF ax==IDM_GETTEXT invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK .ELSEIF ax==IDM_CLEAR invoke SetDlgItemText,hWnd,IDC_EDIT,NULL .ELSEIF ax==IDM_EXIT invoke EndDialog, hWnd,NULL .ENDIF .ELSE mov edx,wParam shr edx,16 .if dx==BN_CLICKED .IF ax==IDC_BUTTON

invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString .ELSEIF ax==IDC_EXIT invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .ENDIF .ENDIF .ENDIF .ELSE mov eax,FALSE ret .ENDIF mov eax,TRUE ret DlgProc endp end start

dialog.rc (part 2)

#include "resource.h" #define IDC_EDIT #define IDC_BUTTON #define IDC_EXIT #define IDR_MENU1 #define IDM_GETTEXT #define IDM_CLEAR #define IDM_EXIT 3000 3001 3002 3003 32000 32001 32003

MyDialog DIALOG 10, 10, 205, 60 STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK CAPTION "Our Second Dialog Box" MENU IDR_MENU1 BEGIN EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13 PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13 END

IDR_MENU1 MENU BEGIN POPUP "Test Controls" BEGIN MENUITEM "Get Text", IDM_GETTEXT MENUITEM "Clear Text", IDM_CLEAR MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/ MENUITEM "E&xit", IDM_EXIT END END

A continuacin el anlisis: DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD Declaramos el prototipo de funcin para DlgProc de manera que podamos referirnos a l con el operador addr en la lnea de abajo: invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL la lnea de arriba llama a la funcin DialogBoxParam que toma 5 parmetros: el manejador de instancia, el nombre de la plantilla de la caja de dilogo, el manejador de la ventana padre, la direccin del procedimiento de de ventana, y los datos especficos del dilogo. DialogBoxParam crea una caja de dilogo modal. No regresar hasta que la caja de dilogo sea destruida. .IF uMsg==WM_INITDIALOG invoke GetDlgItem, hWnd,IDC_EDIT invoke SetFocus,eax .ELSEIF uMsg==WM_CLOSE invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 El procedimiento de la caja de dilogo se observa como un procedimiento de ventana excepto en que no recibe el mensaje WM_CREATE. El primer mensaje que recibe es WM_INITDIALOG. Normalmente puedes poner aqu el cdigo de inicializacin. Nota que debes regresar el valor TRUE en eax si procesas el mensaje. El administrador interno de la caja de dilogo no enva a nuestro procedimiento de dilogo el mensaje WM_DESTROY por defecto cuando WM_CLOSE es enviado a nuestra caja de dilogo. As que si queremos reaccionar cuando el usuario presiona el botn cerrar [close] en nuestra caja de dilogo, debemos procesar el mensaje WM_CLOSE. En nuestro ejemplo, enviamos el mensaje WM_COMMAND con el valor IDM_EXIT en wParam. Esto tiene el mismo efecto que cuando el usuario selecciona el elemento del men Exit. EndDialog es llamado en respuesta a IDM_EXIT. El procesamiento de WM_COMMAND se mantiene igual. Cuando quieres destruir la caja de dilogo, la nica manera es llamar a la funcin EndDialog. No emplees DestroyWindow! EndDialog no destruye la caja de dilogo de inmediato. Slo pone una bandera para ser revisada por el administrador interno de la caja de dilogo y contina para ejecutar la siguiente instruccin. Ahora vamos a revisar el archivo de recursos. El cambio notable es que en vez de usar una cadena de texto como nombre de men usamos un valor, IDR_MENU1. Esto es necesario si quieres agregar un men a la caja de dilog creada con DialogBoxParam. Nota que en la plantilla de caja de dilogo tienes que agregar la palabra clave MENU seguida por el ID del recurso. Una diferencia que puedes observar entre los dos ejemplos de este tutorial es la carencia de un icono en el ltimo ejemplo. Sin embargo, puedes poner el icono enviando el mensaje WM_SETICON a la caja de dilgo durante WM_INITDIALOG.

Tutorial 11: Mas sobre las Caja de Dilogo [Dialog Box]

En este tutorial vamos a aprender mas sobre las cajas de dilogo [dialog box]. Especificamente, vamos a explorar la manera de como usar cajas de dilogo como nuestra entrada-salida de datos. Si leste el tutorial anterior, este te va a resultar ligero. Encontrars algunos cambios menores, es todo lo que necesitamos para poder usar cajas de dilogo adjuntas a nuestra ventana principal. En este tutorial tambin aprenderemos como usar cajas de dilogo comunes. Bjate los ejemplos de cajas de dilogo aqu y aqu. Bajate el ejemplo de una cajas de dilogo comn aqu.

Teora:
Hay muy poco que decir sobre como usar las cajas de dilogo como entrada-salida de nuestro programa. Tu programa crea la pgina principal normalmente y cuando quieres mostrar la cajas de dilogo, llamas a CreateDialogParam o DialogBoxParam. Con la llamada a DialogBoxParam, no tendrs que hacer nada mas, slo procesar los mensajes en el procedimiento de la cajas de dilogo. Con CreateDialogParam, tendrs que insertar la llamada a IsDialogMessage en el bucle de mensajes para dejar a la cajas de dilogo el control sobre la navegacin del teclado en tu cajas de dilogo. Como los dos casos son diferentes, no pondr el codigo fuente aqu. Puedes bajarte los ejemplos y examinarlos tu mismo, aqu y aqu. Comencemos con las cajas de dilogo comunes. Windows tiene preperadas unas cajas de dilogo predefinidas que pueden ser usadas por tus aplicaciones. Estas cajas de dilogo existen para proveer un interfaz estandard de usuario. Consisten en cajas de dilogo de archivo, impresin, color, fuente, y busqueda. Deberas usarlas lo mximo posible. Las cajas de dilogo residen en comdlg32.dll. Para usarlas, tendrs que enlazar [link] el archivo comdlg32.lib. Creas estas cajas de dilogo llamando a la funcin apropiada en la librera de las cajas de dilogo. Para el archivo de dilogo "Abrir" [Open], se emplea la funcin GetOpenFileName, para la caja de dilgo "Guardar" [Save] GetSaveFileName, para dibujar un dilogo es PrintDlg y ya est. Cada una de estas funciones toma como parmetro un puntero a la estructura. Debers mirarlo en la referencia de la API de Win32. En este tutorial, demostrar como crear y usar un dilogo "Abrir archivo" [Open file]. Debajo est la el prototipo de la funcin GetOpenFileName:

GetOpenFileName proto lpofn:DWORD Puedes ver que slo recibe un parmetro, un puntero a la estructura OPENFILENAME. El valor devuelto es TRUE que significa que el usuario a seleccionado un archivo para abrir, de otra manera devolver FALSE. Lo siguiente que veremos ser la estructura OPENFILENAME.

OPENFILENAME STRUCT lStructSize DWORD ? hwndOwner HWND ? hInstance HINSTANCE ? lpstrFilter LPCSTR ? lpstrCustomFilter LPSTR ? nMaxCustFilter DWORD ? nFilterIndex DWORD ? lpstrFile LPSTR ? nMaxFile DWORD ? lpstrFileTitle LPSTR ?

nMaxFileTitle DWORD ? lpstrInitialDir LPCSTR ? lpstrTitle LPCSTR ? Flags DWORD ? nFileOffset WORD ? nFileExtension WORD ? lpstrDefExt LPCSTR ? lCustData LPARAM ? lpfnHook DWORD ? lpTemplateName LPCSTR ? OPENFILENAME ENDS Vamos a ver el significado de los miembros mas utilizados de la estructura.

lStructSize hwndOwner hInstance

El tamao de la estructura OPENFILENAME, en bytes El manejador (handle) de la caja de dilogo "open file". Manejador (handle) de la instancia de la aplicacin que crea la caja de dilogo "open file". Las cadenas de filtro en formato de pares de cadenas terninadas en null (0). La primera entre las dos es la de descripcin. La segunda cadena es el patrn de filtro. por ejemplo: FilterString db "All Files (*.*)",0, "*.*",0 db "Text Files (*.txt)",0,"*.txt",0,0 Date cuenta que slo el patrn en la segunda cadena de este par es usado actualmente por Windows para filtrar los archivos. Tambien date cuenta de que tienes que poner un 0 extra al final de la cadena de filtro para advertir el final de sta. Especifica que par de cadenas de filtro que sern usadas inicialmente cuando la caja de dilogo "open file" es mostrada por primera vez. El ndice es de base 1, que es el primer par 1, el segundo par es 2 y as el resto. En el ejemplo de arriba, si especificamos nFilterIndex como 2, ser usado el segundo patrn, "*.txt". Puntero al buffer que contiene el nombre del archivo usado para inicializar el control de edicin de nombre de archivo en la caja de dilogo. El buffer ser como mnimo de 260 bytes de largo. Despus de que el usuario seleccione el archivo a abrir, el nombre de archivo con toda la direccin (path) es almacenado en este buffer. Puedes extraer la informacin de aqu mas tarde. El tamao del buffer lpstrFile. Puntero al encabezamiento o ttulo de la caja de dilogo "open file" Determina el estilo y caractersticas de la caja de dilogo. despus de que el usuario haya seleccionado el archivo a abrir, este miembro contiene el ndice al primer caracter del nombre de archivo actual. Por ejemplo, si el nombre completo con el directorio (path) es "c:\windows\system\lz32.dll", este miembro contendr el valor 18. despus de que el usuario seleccione el archivo a abrir, este miembro contiene el ndice al primer caracter de la extensin del archivo

lpstrFilter

nFilterIndex

lpstrFile

nMaxFile lpstrTitle Flags nFileOffset

nFileExtension

Ejemplo:
El siguiente programa muestra una caja de dilogo de abrir archivo cuando el usuario seleccione File-> Open del menu. Cuando el usuario seleccione un archivo en la caja de

dilogo, el programa muestra una caja de mensaje mostrando el nombre completo, nombre de archivo, y extensin del archivo seleccionado. .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib .const IDM_OPEN equ 1 IDM_EXIT equ 2 MAXSIZE equ 260 OUTPUTSIZE equ 512 .data ClassName db "SimpleWinClass",0 AppName db "Our Main Window",0 MenuName db "FirstMenu",0 ofn OPENFILENAME <> FilterString db "All Files",0,"*.*",0 db "Text Files",0,"*.txt",0,0 buffer db MAXSIZE dup(0) OurTitle db "-=Our First Open File Dialog Box=-: Choose the file to open",0 FullPathName db "The Full Filename with Path is: ",0 FullName db "The Filename is: ",0 ExtensionName db "The Extension is: ",0 OutputString db OUTPUTSIZE dup(0) CrLf db 0Dh,0Ah,0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst

pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,300,200,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .if ax==IDM_OPEN mov ofn.lStructSize,SIZEOF ofn push hWnd pop ofn.hwndOwner push hInstance pop ofn.hInstance mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,MAXSIZE mov ofn.Flags, OFN_FILEMUSTEXIST or \ OFN_PATHMUSTEXIST or OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY mov ofn.lpstrTitle, OFFSET OurTitle invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke lstrcat,offset OutputString,OFFSET FullPathName invoke lstrcat,offset OutputString,ofn.lpstrFile invoke lstrcat,offset OutputString,offset CrLf invoke lstrcat,offset OutputString,offset FullName mov eax,ofn.lpstrFile push ebx xor ebx,ebx mov bx,ofn.nFileOffset add eax,ebx pop ebx invoke lstrcat,offset OutputString,eax invoke lstrcat,offset OutputString,offset CrLf invoke lstrcat,offset OutputString,offset ExtensionName

mov eax,ofn.lpstrFile push ebx xor ebx,ebx mov bx,ofn.nFileExtension add eax,ebx pop ebx invoke lstrcat,offset OutputString,eax invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE .endif .else invoke DestroyWindow, hWnd .endif .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

Anlisis: mov ofn.lStructSize,SIZEOF ofn push hWnd pop ofn.hwndOwner push hInstance pop ofn.hInstance Rellenamos en la rutina los miembros de la estructura ofn. mov ofn.lpstrFilter, OFFSET FilterString Este FilterString es el filtro para el nombre de archivo que especificamos como sigue: FilterString db "All Files",0,"*.*",0 db "Text Files",0,"*.txt",0,0 Date cuenta que las cuatro cadenas terminan en 0. La primera cadena es la descripcin de la siguiente cadena. El actual patrn es la cadena par, en este caso, "*.*" y "*.txt". Actualmente podemos especificar aqu cualquier patrn que queramos. DEBEMOS poner un cero extra despus de la ltima cadena de patrn para denotar el final de la cadena de filtro. No olvides esto sino tu caja de dilogo funcionar de forma extraa. mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,MAXSIZE Especificamos dnde la caja de dilogo pondr el nombre del archivo que ha seleccionado el usuario. Date cuenta que tendremos que especificar su tamao en el miembro nMaxFile. Podemos extraer ms tarde el nombre del archivo de este buffer. mov ofn.Flags, OFN_FILEMUSTEXIST or \ OFN_PATHMUSTEXIST or OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY Flags especifica las caractersticas de la caja de dilogo.

Las banderas [flags] OFN_FILEMUSTEXIST y OFN_PATHMUSTEXIST demandan que el nombre de archivo y direccin (path) que el usuario pone en el control de edicin (edit control) DEBEN existir. La bandera OFN_LONGNAMES dice a la caja de dilogo que muestre nombres largos de archivo. La bandera OFN_EXPLORER especifica que la apariencia de la caja de dilogo debe ser parecida a la del explorador. La bandera OFN_HIDEREADONLY oculta el cuadro de seleccin de solo-lectura en la caja de dilogo. Hay muchas banderas ms que puedes usar. Consulta tu referencia de la API de Win32. mov ofn.lpstrTitle, OFFSET OurTitle Especifica el titulo o encabezado [caption] de la caja de dilogo. invoke GetOpenFileName, ADDR ofn Llamada a la funcin GetOpenFileName. Pasando el puntero a la estructura ofn como parmetro. En este momento, la caja de dilogo de abrir archivo es mostrada en la pantalla. La funcin no volver hasta que el usuario seleccione un archivo para abrir o presione el boton de cancelar o cierre la caja de dilogo. Devolver el valor TRUE en eax si el usuario selecciona un archivo para abrir. Sino devolver FALSE en cualquier otro caso. .if eax==TRUE invoke lstrcat,offset OutputString,OFFSET FullPathName invoke lstrcat,offset OutputString,ofn.lpstrFile invoke lstrcat,offset OutputString,offset CrLf invoke lstrcat,offset OutputString,offset FullName En el caso que el usuario seleccione un archivo para abrir, preparamos la cadena de salida que se mostrar en la caja de mensajes. Ubicamos un bloque de memoria en la variable OutputString y entonces usamos la funcin de la API, lstrcat, para entrelazar las cadenas siempre. Para poner las cadenas en varias lneas, debemos separar cada lnea con el par de caracteres que alimentan el retorno de carro (13d o 0Dh) y el avance de lnea (10d o 0Ah). mov eax,ofn.lpstrFile push ebx xor ebx,ebx mov bx,ofn.nFileOffset add eax,ebx pop ebx invoke lstrcat,offset OutputString,eax Las lneas de arriba requieren una explicacin. nFileOffset contiene el ndice dentro de ofn.lpstrFile. Pero no puedes aadirlo directamente porque nFileOffset es una variable de tamao WORD y lpstrFile es de tamao DWORD. As que tendr que poner el valor de nFileOffset en la palabra baja [low word] de ebx y sumrselo al valor de lpstrFile. invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK Mostramos la cadena en la caja de mensajes. invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE Debemos *limpiar* el OutputString antes de poder meterle cualquier otra cadena. As que usamos la funcin RtlZeroMemory para hacer este trabajo.

Tutorial 12: Manejo de Memoria y E/S de Archivos


En este tutorial aprenderemos a manejar la memoria rudimentariamente y las operaciones de entrada/salida de archivos. Adicionalmente usaremos cajas de dilogo comunes como instrumento de entrada-salida. Bja el ejemplo aqu. Teoria: El manejo de la memoria bajo Win32 desde el punto de vista de las aplicaciones es un poco simple y directo. Cada proceso usa un espacio de 4 GB de dirrecciones de memoria. El modelo de memoria usado se llama modelo de memoria plana [flat]. En este modelo, todos los segmentos de registro (o selectores) direccionan a la misma localidad de memoria y el desplazamiento [offset] es de 32-bit. Tambien las aplicaciones pueden acceder a la memoria en cualquier punto en su espacio de direcciones sin necesidad de cambiar el valor de los selectores. Esto simplifica mucho el manejo de la memoria. No hay mas puntos "near" (cerca) o "far" (lejos). Bajo Win16, hay dos categoras principales de funciones de la API de memoria: Global y Local. Las de tipo Global tienen que ver con la memoria situada en diferentes segmentos, por eso hay funciones para memoria "far" (lejana). Lasfunciones de la API de tipo Local tienen que ver con un motculo [heap] de memoria local del proceso as que son las funciones de memoria "near" (cercana). Bajo Win32, estos dos tipos son uno y le mismo tipo. tendrs el mismo resultado si llamas a GlobalAlloc o LocalAlloc. Los pasos para ubicar y usar la memoria son los siguientes:

1. Ubicar el bloque de memoria llamando a GlobalAlloc. Esta funcin devuelve un 2. 3. 4. 5.


manejador (handle) al bloque de memoria pedido. "Bloquear" [lock] el bloque de memoria llamando a GlobalLock. Esta funcin acepta un manejador (handle) al bloque de memoria y devuelve un puntero al bloque de memoria. Puedes usar el puntero para leer o escribir en la memoria. Desbloquear [unlock] el bloque de memoria llamando a GlobalUnlock . Esta funcin invalida el puntero al bloque de memoria. Liberar el bloque de memoria llamando a GlobalFree. Esta funcin acepta un manejador (handle) al bloque de memoria.

Puedes sustituir "Global" por "Local" en LocalAlloc, LocalLock,etc. El mtodo de arriba puede simplificarse radicalmente usando el flag GMEM_FIXED en la llamada a GlobalAlloc. Si usas esta bandera [flag], El valor de retorno de Global/LocalAlloc ser el puntero al bloque de memoria reservado, no el manejador (handle). No tienes que llamar a Global/LocalLock y puedes pasar el puntero a Global/LocalFree sin llamar primero a Global/LocalUnlock. Pero en este tutorial, usar el modo "tradicional" ya que te lo puedes encontrar cuando leas el cdigo de otros programas. La E/S de archivos bajo Win32 tiene una semblanza ms notable que la de bajo DOS. Los pasos a seguir son los mismos. Slo tienes que cambiar las interrupciones por llamadas a la API y ya est. Los pasos requeridos son los siguientes:

1. Abrir o Crear el archivo llamando a la funcin CreateFile. Esta funcin es muy verstil:
aadiendo a los archivos, puede abrir puertos de comunicacin, tuberas [pipes], dipositivos de discos. Cuando es correcto, devuelve un manejador (handle) al archivo o

dispositivo. Entonces puedes usar este manejador (handle) para llevar a cabo operaciones en el archivo o dispositivo.

2.
Mueve el puntero a la posicin deseada llamando a SetFilePointer.

3. Realiza la operacin de lectura o escritura llamando a ReadFile o WriteFile. Estas


funciones transfieren datos desde un bloque de memoria hacia o desde un archivo. As que tendrs que reservar un bloque de memoria suficientemente grande para alojar los datos. Cierra el archivo llamando a CloseHandle. Esta funcin acepta el manejador (handle) de archivo.

4.

Contenido: El programa de abajo muestra una caja de dilogo de abrir archivo. Deja al usuario seleccionar un archivo de texto y muestra el contenido de este archivo en un control de edicin en su rea cliente. El usuario puede modificar el texto en el control de edicin como desee, y puede elegir guardar el contenido en un archivo. .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib .const IDM_OPEN equ 1 IDM_SAVE equ 2 IDM_EXIT equ 3 MAXSIZE equ 260 MEMSIZE equ 65535 EditID equ 1 ; ID del control de edicin

.data ClassName db "Win32ASMEditClass",0 AppName db "Win32 ASM Edit",0 EditClass db "edit",0 MenuName db "FirstMenu",0 ofn OPENFILENAME <> FilterString db "All Files",0,"*.*",0 db "Text Files",0,"*.txt",0,0 buffer db MAXSIZE dup(0) .data? hInstance HINSTANCE ? CommandLine LPSTR ? hwndEdit HWND ? hFile HANDLE ? hMemory HANDLE ? pMemory DWORD ? SizeReadWrite DWORD ?

; manejador (handle) del control de edicin ; manejador de archivo ; manejador del bloque de memoria reservada ; puntero al bloque de memoria reservada ; numero de bytes actualmente para leer o escribir

.code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,300,200,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_CREATE invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\ WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\ ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\ 0,0,0,hWnd,EditID,\ hInstance,NULL mov hwndEdit,eax invoke SetFocus,hwndEdit ;======================================================== ; Inicializacion de los miembros de la estructura OPENFILENAME ;======================================================== mov ofn.lStructSize,SIZEOF ofn push hWnd

pop ofn.hWndOwner push hInstance pop ofn.hInstance mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,MAXSIZE .ELSEIF uMsg==WM_SIZE mov eax,lParam mov edx,eax shr edx,16 and eax,0ffffh invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE .ELSEIF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .if lParam==0 .if ax==IDM_OPEN mov ofn.Flags, OFN_FILEMUSTEXIST or \ OFN_PATHMUSTEXIST or OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke CreateFile,ADDR buffer,\ GENERIC_READ or GENERIC_WRITE ,\ FILE_SHARE_READ or FILE_SHARE_WRITE,\ NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ NULL mov hFile,eax invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE mov hMemory,eax invoke GlobalLock,hMemory mov pMemory,eax invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory invoke CloseHandle,hFile invoke GlobalUnlock,pMemory invoke GlobalFree,hMemory .endif invoke SetFocus,hwndEdit .elseif ax==IDM_SAVE mov ofn.Flags,OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY invoke GetSaveFileName, ADDR ofn .if eax==TRUE invoke CreateFile,ADDR buffer,\ GENERIC_READ or GENERIC_WRITE ,\ FILE_SHARE_READ or FILE_SHARE_WRITE,\ NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\ NULL mov hFile,eax invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE mov hMemory,eax invoke GlobalLock,hMemory mov pMemory,eax invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL invoke CloseHandle,hFile invoke GlobalUnlock,pMemory invoke GlobalFree,hMemory

.endif invoke SetFocus,hwndEdit .else invoke DestroyWindow, hWnd .endif .endif .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

Anlisis: invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\ WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\ ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\ 0,0,0,hWnd,EditID,\ hInstance,NULL mov hwndEdit,eax En la seccin WM_CREATE, creamos el control de edicin. Date cuenta que los parmetros que especifican x, y, anchura, altura del control son todos ceros ya que reajustaremos el tamao del control despues para cubrir toda el area cliente de la ventana padre. En este caso, no tenemos que llamar a ShowWindow para hacer que el control de edicin aparezca en la pantalla porque hemos incluido el estilo WS_VISIBLE. Puedes usar este truco tambin en la ventana padre. ;================================================== ; Inicializa los miembros de la estructura OPENFILENAME ;================================================== mov ofn.lStructSize,SIZEOF ofn push hWnd pop ofn.hWndOwner push hInstance pop ofn.hInstance mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,MAXSIZE Despues de crear el control de edicin, tendremos que inicializar los miembros de ofn. Como queremos reciclar ofn en la caja de dilogo para guardar, pondremos solo los miembros *comunes* que son usados por ambos GetOpenFileName y GetSaveFileName. La seccin WM_CREATE es un lugar amplio para poner las incializaciones nicas (que se inicializan una sola vez). .ELSEIF uMsg==WM_SIZE mov eax,lParam mov edx,eax shr edx,16 and eax,0ffffh invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

Recibimos el mensaje WM_SIZE cuando el tamao de nuestro rea cliente en la ventana principal cambia. Tambien lo recibimos cuando la ventana es creada por primera vez. Para poder recibir el mensaje, el estilo de ventana debe incluir los estilos CS_VREDRAW y CS_HREDRAW. Usamos esta oportunidad para reajustar el tamao de nuestro control de edicin al mismo tamao de nuestro rea cliente de la ventana padre. Primero tenemos que saber la anchura y altura del rea cliente de la ventana padre. Obtenemos esta informacin de lParam. La palabra alta [high word] de lParam contiene la altura y la palabra baja [low word] de lParam la anchura del area cliente. Entonces usamos la informacin para reajustar el tamao del control de edicin llamando a la funcin MoveWindow, que adems de cambiar la posicin de la ventana, permite cambiar su tamao. .if ax==IDM_OPEN mov ofn.Flags, OFN_FILEMUSTEXIST or \ OFN_PATHMUSTEXIST or OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn Cuando el usuario selecciona el elemento de men File/Open, rellenamos el miembro Flags de la estructura ofn y llamamos a la funcin GetOpenFileName para mostrar la caja de dilogo de abrir archivo. .if eax==TRUE invoke CreateFile,ADDR buffer,\ GENERIC_READ or GENERIC_WRITE ,\ FILE_SHARE_READ or FILE_SHARE_WRITE,\ NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ NULL mov hFile,eax Despus que el usuario ha seleccionado el archivo que desea abrir, llamamos a CreateFile para abrir el archivo. Hemos especificado que la funcin intentar abrir el archivo para lectura y escritura. Despus de abrir el archivo, la funcin devuelve el manejador (handle) al archivo abierto que almacenamos en una variable global para futuros usos. Esta funcin es como sigue: CreateFile proto lpFileName:DWORD,\ dwDesiredAccess:DWORD,\ dwShareMode:DWORD,\ lpSecurityAttributes:DWORD,\ dwCreationDistribution:DWORD\, dwFlagsAndAttributes:DWORD\, hTemplateFile:DWORD dwDesiredAccess especifica qu operacin quieres que se haga en el archivo. 0 Abrir el archivo para pedir sus atributos. Tendrs derecho a escribir o leer los datos. GENERIC_READ Abrir el archivo para lectura. GENERIC_WRITE Abrir el archivo para escribir.

dwShareMode especifica qu operaciones quieres reservar para que otros procesos puedan llevarlas a cabo en el archivo que va a ser abierto. 0 No compartir el archivo con otros procesos. FILE_SHARE_READ permitir a otros procesos leer los datos del archivo abierto FILE_SHARE_WRITE permitir a otros procesos escribir datos en el archivo abierto.

lpSecurityAttributes no tiene significado bajo Windows 95. dwCreationDistribution especifica la accin a ejecutar por CeateFile cuando el archivo especificado en lpFileName existe o cuando no existe. CREATE_NEW Crear un nuevo archivo. La funcin falla si ya existe el archivo especificado. CREATE_ALWAYS Crea un nuevo archivo. La funcin sobreescribe el archivo si ste existe. OPEN_EXISTING Abre el archivo. La funcin falla si el archivo no existe. OPEN_ALWAYS Abre el archivo si existe. Si el archivo no existe, la funcin crea el archivo como si dwCreationDistribution fuera CREATE_NEW. TRUNCATE_EXISTING Abre el archivo. Una vez abierto, el archivo es truncado si su tamao es de cero bytes. El proceso llamado debe abrir el archivo como mnimo con el acceso GENERIC_WRITE. La funcin falla si el archivo no existe.

dwFlagsAndAttributes especifica los atributos de archivo FILE_ATTRIBUTE_ARCHIVE El archivo es del tipo archivo. Las aplicaciones usan este atributo para marcar las copias de seguridad [backup] o los removibles. FILE_ATTRIBUTE_COMPRESSED El archivo o directorio est coprimido. Para el archivo esto significa que todos los datos del archivo estn comprimidos. Para un directorio esto significa que la compresin es por defecto aplicada a los archivos y subdirectorios nuevos creados. FILE_ATTRIBUTE_NORMAL El archivo no tiene otros atributos activos. Este atributo es vlido slo si esta solo, no hay ningn otro atributo. FILE_ATTRIBUTE_HIDDEN El archivo es oculto. El archivo no es incluido en la lista ordinaria de directorios. FILE_ATTRIBUTE_READONLY El archivo es de solo lectura. Las aplicaciones pueden leer el archivo pero no pueden ni escribirlo ni borrarlo. FILE_ATTRIBUTE_SYSTEM El archivo es parte del sistema operativo o es usado por este exclusivamente. invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE mov hMemory,eax invoke GlobalLock,hMemory mov pMemory,eax Cuando se abre el archivo, reservamos un bloque de memoria para usar con las funciones ReadFile y WriteFile. Especificamos el flag GMEM_MOVEABLE para dejar a Windows mover el bloque de memoria para consolidar la memoria. La bandera [flag] GMEM_ZEROINIT le dice a GlobalAlloc que rellene el nuevo bloque de memoria reservado con ceros. Cuando GlobalAlloc vuelve satisfactoriamente, eax contiene el manejador (handle) al bloque de memoria reservado. Le pasamos este manejador (handle) a la funcin GlobalLock que nos devuelve un puntero al bloque de memoria. invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory Cuando el bloque de memoria esta listo para ser usado, llamamos a la funcin ReadFile para leer los datos del archivo. Cuando el archivo es abierto o creado por primera vez, el puntero del archivo esta en el deplazamiento [offset] 0. As que en este caso, empezamos a leer el primer byte del archivo. El primer parmetro de ReadFile es el manejador (handle) del archivo a leer, el segundo es el puntero al bloque de memoria para contener los datos, el siguiente es el numero de bytes a leer del archivo, el cuarto parmetro es la direccion de la variable de tamao DWORD que rellenaremos con el nmero de bytes realmente ledos del archivo.

Despues de rellenar el bloque de memoria con los datos, ponemos los datos en el control de edicin mandando el mensaje WM_SETTEXT al control de edicin con lParam conteniendo el puntero al bloque de memoria. Despues de esta llamada, el control de edicin muestra los datos en el rea cliente. invoke CloseHandle,hFile invoke GlobalUnlock,pMemory invoke GlobalFree,hMemory .endif En este punto, no necesitamos tener el archivo abierto por ms tiempo ya que nuestra intencin es grabar los datos modificados en el control de edicin en otro archivo, no el archivo original. Asi que cerramos el archivo llamando a CloseHandle con el manejador (handle) como su parmetro. Seguido desbloquearemos el bloque de memoria y lo liberamos. Actualmente no tienes que liberar la memoria en este punto, Puedes usar el bloque de memoria durante el proceso de grabacin despus. Pero como demostracin, yo he elegido liberarla aqu. invoke SetFocus,hwndEdit Cuando la caja de dilogo "abrir archivo" es mostrada en la pantalla, el foco de entrada se centra en ella. As que despus de cerrar el dilogo de "abrir archivo", tendremos que mover el foco de entrada otra vez al control de edicin. Esto termina la operacion de lectura de archivo. En este punto, el usuario puede editar el contenido del control de edicin. Y cuando quiera salvar los datos a otro archivo, deber seleccionar File/Save en el men que mostrar la caja de dilogo de salvar archivo. La creacin de la caja de dilogo de salvar archivo no es muy diferente de la de abrir archivo. Efectivamente, se diferencian slo en el nombre de la funcin, GetOpenFileName y GetSaveFileName. Puedes reciclar la mayora de los miembros de la estructura ofn excepto el miembro Flags. mov ofn.Flags,OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY En nuestro caso, queremos crear un nuevo archivo, as que OFN_FILEMUSTEXIST y OFN_PATHMUSTEXIST deben ser dejados fuera, sino la caja de dilogo no nos dejara crear un archivo que no exista ya. El parmetro dwCreationDistribution de la funcin CreateFile deber ser cambiada a CREATE_NEW ya que queremos crear un nuevo archivo. El resto del cdigo es idntico a todas las secciones de "abrir archivo" excepto las siguientes lneas: invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL Mandamos el mensaje WM_GETTEXT al control de edicin para copiar los datos del bloque de memoria, el valor devuelto en eax es la longitud de los datos dentro del buffer. Despus de que los datos estn en el bloque de memoria, los escribimos en un nuevo archivo.

Tutorial 13: Archivos Proyectados en Memoria

Te mostrar qu son los archivos proyectados en memoria y cmo usarlos para tu provecho. Usar un archivo proyectado en memoria es muy fcil, como vers en este tutorial. Baja el ejemplo aqu.

Teora:
Si examinas detenidamente el ejemplo del tutorial previo, encontrars que tiene un serio inconveniente: qu pasa si el archivo que quieres leer es ms grande que el bloque de memoria localizado? o qu si la cadena que quieres buscar es cortada por la mitad al final del bloque de memoria? La respuesta tradicional para la primera cuestin es que deberas leer repetidas veces en los datos desde el inicio del archivo hasta que encuentres el final del archivo. La respuesta para la segunda cuestin es que deberas prepararte para el caso especial al final del bloque de memoria. Esto es lo que se llama el problema del valor del lmite. Presenta terribles dolores de cabeza a los programadores y causa innumerables errores [bugs]. Sera agradable localizar un bloque de memoria, lo suficientemente grande para almacenar todo el archivo pero nuestro programa debera ser abundante en recursos. Proyeccin de archivo al ataque. Al usar proyeccin de archivo, puedes pensar en todo el archivo como si estuviera ya cargado en la memoria y puedes usar un puntero a la memoria para leer o escribir datos desde el archivo. Tan fcil como eso. No necesitas usar las funciones de memoria de la API y separar ms las funciones de la API para E/S de archivo, todas ellas son una y la misma bajo proyeccin de archivo. La proyeccin de archivos tambin es usada como un medio de compartir memoria entre los archivos. Al usar proyeccin de archivos de esta manera, no hay involucrados archivos reales. Es ms como un bloque de memoria reservado que todo proceso puede *ver*. Pero compartir datos entre procesos es un asunto delicado, no para ser tratado ligeramente. Tienes que implementar sincronizacin de procesos y de hilos, sino tus aplicaciones se quebrarn [crash] en un orden muy corto. No tocaremos el tema de los archivos proyectados como un medio de crear una memoria compartida en este tutorial. Nos concentraremos en cmo usar el archivo proyectado como medio para "proyectar" un archivo en memoria. En realidad, el cargador de archivos PE usa proyeccin de archivo para cargar archivos ejecutables en memoria. Es muy conveniente ya que slo las partes pueden ser ledas selectivamente desde el archivo en disco. Bajo Win32, deberas usar proyeccin de archivos cada vez que fuera posible. Sin embargo, hay algunas limitaciones al emplear archivos proyectados en memoria. Una vez que creas un archivo proyectado en memoria, su tamao no puede ser cambiado durante esa seccin. As que proyectar archivos es muy bueno para archivos de slo lectura u operaciones de archivos que no afecten el tamao del archivo. Eso no significa que no puedes usar proyeccin de archivo si quieres incrementar el tamao del archivo. Puedes estimar el nuevo tamao y crear archivos proyectados en memoria basados en el nuevo tamao y el archivo se incrementar a ese tamao. Esto es muy conveniente, eso es todo. Suficiente para la explicacin. Vamos a zambullirnos dentro de la implemantacin de la proyeccin de archivos. Con el fin de usar proyeccin de archivos, deben seguirse los siguientes pasos:

1. llamar CreateFile para abrir el archivo que quieres proyectar. 2. llamar CreateFileMapping con el manejador de archivo regresado por CreateFile como 3.
uno de sus parmetros. Esta funcin crea un objeto de archivo proyectado a partir del archivo abierto por CreateFile. llamar a MapViewOfFile para proyectar una regin del archivo seleccionado o el archivo completo a memoria. Esta funcin regresa un puntero al primer byte de la regin proyectada del archivo.

4. Usar el puntero para leer o escribir el archivo 5. llamar UnmapViewOfFile para des-proyectar el archivo. 6. llamar a CloseHandle con el manejador del archivo proyectado como parmetro para
cerrar el archivo proyectado.

7. llamar CloseHandle de nuevo, esta vez con el manejador regresado por CreateFile
para cerrar el archivo actual.

Ejemplo:
El programa que aparece abajo, te permite abrir un archivo a travs de una caja de dilogo "Open File". Abre el archivo utilizando proyeccin de archivo, si esto tiene xito, el encabezado de la ventana es cambiado al nombre del archivo abierto. Puedes salvar el archivo con otro nombre seleccionando File/Save como elemento de men. El programa copiar todo el contenido del archivo abierto al nuevo archivo. Nota que no tienes que llamar a GlobalAlloc para localizar un bloque de memoria en este programa. .386 .model flat,stdcall WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib .const IDM_OPEN equ 1 IDM_SAVE equ 2 IDM_EXIT equ 3 MAXSIZE equ 260 .data ClassName db "Win32ASMFileMappingClass",0 AppName db "Win32 ASM File Mapping Example",0 MenuName db "FirstMenu",0 ofn OPENFILENAME <> FilterString db "All Files",0,"*.*",0 db "Text Files",0,"*.txt",0,0 buffer db MAXSIZE dup(0) hMapFile HANDLE 0 ; Manejador al archivo proyectado en memoria, debe ser ;inicializado con 0 porque tambin lo usamos como ;una bandera en la seccin WM_DESTROY .data? hInstance HINSTANCE ? CommandLine LPSTR ? hFileRead HANDLE ? hFileWrite HANDLE ? hMenu HANDLE ? pMemory DWORD ? SizeWritten DWORD ? WriteFile

; Manejador al archivo fuente ; Manejador al archivo salida ; puntero a los datos en el archivo fuente ; nmero de bytes actualmente escritos por

.code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\ ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,300,200,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_CREATE invoke GetMenu,hWnd ;Obtener el manejador del men mov hMenu,eax mov ofn.lStructSize,SIZEOF ofn push hWnd pop ofn.hWndOwner push hInstance pop ofn.hInstance mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,MAXSIZE .ELSEIF uMsg==WM_DESTROY

.if hMapFile!=0 call CloseMapFile .endif invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .if lParam==0 .if ax==IDM_OPEN mov ofn.Flags, OFN_FILEMUSTEXIST or \ OFN_PATHMUSTEXIST or OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke CreateFile,ADDR buffer,\ GENERIC_READ ,\ 0,\ NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ NULL mov hFileRead,eax invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL mov hMapFile,eax mov eax,OFFSET buffer movzx edx,ofn.nFileOffset add eax,edx invoke SetWindowText,hWnd,eax invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED .endif .elseif ax==IDM_SAVE mov ofn.Flags,OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY invoke GetSaveFileName, ADDR ofn .if eax==TRUE invoke CreateFile,ADDR buffer,\ GENERIC_READ or GENERIC_WRITE ,\ FILE_SHARE_READ or FILE_SHARE_WRITE,\ NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\ NULL mov hFileWrite,eax invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0 mov pMemory,eax invoke GetFileSize,hFileRead,NULL invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL invoke UnmapViewOfFile,pMemory call CloseMapFile invoke CloseHandle,hFileWrite invoke SetWindowText,hWnd,ADDR AppName invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED .endif .else invoke DestroyWindow, hWnd .endif .endif .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax

ret WndProc endp CloseMapFile PROC invoke CloseHandle,hMapFile mov hMapFile,0 invoke CloseHandle,hFileRead ret CloseMapFile endp end start

Anlisis:
invoke CreateFile,ADDR buffer,\ GENERIC_READ ,\ 0,\ NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ NULL Cuando el usuario selecciona un archivo en el dilogo Open File, llamamos a CreateFile para abrirlo. Nota que especificamos GENERIC_READ para abrir este archivo con acceso de slo lectura y dwShareMode es cero porque no queremos ningn otro proceso para modificar el archivo durante nuestra operacin. invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL Luego llamamos a CreateFileMapping para crear un archivo proyectado en memoria a partir del archivo abierto. CreateFileMapping tiene la siguiente sintaxis: CreateFileMapping proto hFile:DWORD,\ lpFileMappingAttributes:DWORD,\ flProtect:DWORD,\ dwMaximumSizeHigh:DWORD,\ dwMaximumSizeLow:DWORD,\ lpName:DWORD Deberas saber primero que CreateFileMapping no tiene que proyectar todo el archivo a memoria. Puedes usar esta funcin para proyectar slo una parte del archivo actual a memoria. Especificas el tamao del archivo proyectado a memoria en los parmetros dwMaximumSizeHigh y dwMaximumSizeLow. Si especificas un tamao mayor al archivo actual, el tamao de este archivo ser expandido. Si quieres que el archivo proyectado sea del mismo tamao que el archivo actual, pon ceros en ambos parmetros. Puedes usar NULL en el parmetro lpFileMappingAttributes para que Windows cree un archivo proyectado en memoria con los atributos de seguridad por defecto. flProtect define la proteccin deseada para el archivo proyectado en memria. En nuestro ejemplo, usamos PAGE_READONLY para permitir slo operaciones de lectura sobre el archivo proyectado en memoria. Nota que este atributo no debe contradecir el atributo usado en CreateFile, sino CreateFileMapping fallar. lpName apunta al nombre del archivo proyectado en memoria. Si quieres compartir este archivo con otros procesos, debes suministrarle un nombre. Pero en nuestro ejemplo, nuestro proceso es el nico que usa este archivo, as que ignoramos este parmetro.

mov eax,OFFSET buffer movzx edx,ofn.nFileOffset add eax,edx invoke SetWindowText,hWnd,eax Si CreateFileMapping es satisfactorio, cambiamos el encabezado [caption] de la ventana al nombre del archivo abierto. El nombre del archivo con su ubicacin [path] completa es almacenado en un buffer, queremos desplegar slo el nombre del archivo en el encabezado, as que debemos agregar el valor del miembro nFileOffset de la estructura OPENFILENAME a la direccin del buffer. invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED Como precausin, no queremos que el usuario abra ms de un archivo a la vez, as que difuminar [gray out] el elemento Open del men y habilitamos el elemento Save. EnableMenuItem se emplea para cambiar el atributo de los elementos de men. Despus de esto, esperamos a que el usuario seleccione File/Save como elemento de men o cierre nuestro programa. S el usuario elige cerrar el programa, debemos cerrar el archivo proyectado en memoria y el archivo actual siguiendo un cdigo como el siguiente: .ELSEIF uMsg==WM_DESTROY .if hMapFile!=0 call CloseMapFile .endif invoke PostQuitMessage,NULL En el recorte de cdigo anterior, cuando el procedimiento de ventana recibe el mensaje WM_DESTROY, chequea primero el valor de hMapFile para comprobar si es cero o no. Si no es cero, llamam a la funcin CloseMapFile que contiene el siguiente cdigo: CloseMapFile PROC invoke CloseHandle,hMapFile mov hMapFile,0 invoke CloseHandle,hFileRead ret CloseMapFile endp CloseMapFile cierra el archivo proyectado en memoria y el archivo actual de manera que no haya prdida de recursos cuando nuestro programa salga a Windows. Si nuestro usuario elige guardar esos datos a otros archivos, el programa le presentar una caja de dilogo comn "save as". Despus de que el usuario escribe el nombre del nuevo archivo, el archivo es creado por la funcin CreateFile. invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0 mov pMemory,eax Inmediatamente despus de que el archivo de salida es creado, llamamos a MapViewOfFile para proyectar la parte deseada del archivo proyectado en memoria. Esta funcin tiene la siguiente sintaxis: MapViewOfFile proto hFileMappingObject:DWORD,\ dwDesiredAccess:DWORD,\ dwFileOffsetHigh:DWORD,\ dwFileOffsetLow:DWORD,\ dwNumberOfBytesToMap:DWORD

dwDesiredAccess especifica qu operaciones queremos hacer con el archivo. En nuestro ejemplo, slo queremos leer los datos de manera que usamos FILE_MAP_READ. dwFileOffsetHigh y dwFileOffsetLow especifican el desplazamiento inicial de la proyeccin del archivo que queremos proyectar en memoria. En nuestro caso, queremos leerlo todo, de manera que comenzamos desde el desplazamiento cero en adelante. dwNumberOfBytesToMap especifica el nmero de bytes a proyectar en memoria. Si quieres proyectar todo el archivo (especificado por CreateFileMapping), paamos 0 a MapViewOfFile. Despus de llamar a MapViewOfFile, la porcin deseada es cargada en memoria. Obtendrs el puntero al bloque de memoria que contiene los datos del archivo. invoke GetFileSize,hFileRead,NULL Conseguir el tamao del archivo. El tamao del archivo es regresado en eax. Si el archivo es mayor a 4 GB, la palabra alta DWORD del tamao del archivo es almacenada en FileSizeHighWord. Ya que no esperamos manejar un archivo de tal tamao, podemos ignorarlo. invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL Escribir los datos del archvo proyectado en memoria en el archivo de salida. invoke UnmapViewOfFile,pMemory Cuando hayamos terminado de realizar las operaciones que desebamos con el archivo de entrada, des-proyectarlo (unmapping) de la memoria.. call CloseMapFile invoke CloseHandle,hFileWrite Y cerrar todos los archivos. invoke SetWindowText,hWnd,ADDR AppName Restablecer el texto original del encabezado. invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED Habilitar el elemento del Open de men y eliminar la difuminacin del elemento Save As del men Aprenderemos qu es un proceso, cmo crearlo y cmo terminarlo. Bajar el ejemplo aqu. Preliminares: Qu es un proceso? He extraido esta definicin del referencia de la API de Win32: "Un proceso es una aplicacin en ejecucin que consiste en un espacio de direcciones privado, cdigo, datos, y otros recursos del sistema operativo, tales como archivos, tuberas, y objetos de sincronizacin que son visibles al proceso." Cmo puedes ver, un proceso "se apropia" de algunos objetos: el espacio de direcciones, el mdulo ejecutante o los mdulos, y cualquier cosa que el mdulo ejecutante pueda crear o

abrir. Al menos, un proceso debe consistir en un mdulo ejecutable, un espacio de direcciones privado y un hilo. Todo proceso debe tener por lo menos un hilo. Qu es un hilo? Un hilo es realmente una cola o flujo de ejecucin. Cuando Windows crea un proceso, crea slo un hilo para el proceso. Este hilo generalmente inicia la ejecucin desde la primera instruccin en el mdulo. Si el proceso luego necesita ms hilos, puede crearlos explcitamente. Cuando Windows recibe la orden de crear un proceso, crea un espacio de direcciones privado para el proceso y luego proyecta el archivo ejecutable en la memoria de ese proceso. Despus de eso crea el hilo primario del proceso. Bajo Win32, puedes crear procesos desde tus programas llamando a la funcin CreateProcess. CreateProcess tiene la siguiente sintaxis: CreateProcess proto lpApplicationName:DWORD,\ lpCommandLine:DWORD,\ lpProcessAttributes:DWORD,\ lpThreadAttributes:DWORD,\ bInheritHandles:DWORD,\ dwCreationFlags:DWORD,\ lpEnvironment:DWORD,\ lpCurrentDirectory:DWORD,\ lpStartupInfo:DWORD,\ lpProcessInformation:DWORD No te alarmes por el nmero de parmetros. Podemos ignorar muchos de ellos. lpApplicationName --> El nombre del archivo ejecutable, con o sin ubicacin, que quieres ejecutar. Si este parmetro es nulo, debes proveer el nombre del archivo ejecutable en el parmetro lpCommandLine lpCommandLine --> Los argumentos en la lnea de rdenes del programa que quieres ejecutar. Nota que si lpApplicationName es NULL, este parmetro debe contener tambin el nombre del archivo ejecutable. Como este: "notepad.exe readme.txt" lpProcessAttributes ylpthreadAttributes --> Especifican los atributos de seguridad para el proceso y el hilo primario. Si son NULLs, son usados los atributos de seguridad por defecto. bInheritHandles --> Una bandera que especifica si quieres que el nuevo proceso herede todos los manejadores abiertos de tu proceso. dwCreationFlags --> Algunas banderas que determinan la conducta del proceso que quieres crear, tales como, quieres que el proceso sea cerrado pero inmediatamente suspendido para que puedas examinarlo o modificarlo antes de que corra? Tambin puedes indicar la clase de prioridad del(os ) hilo(s) en el nuevo proceso. Esta clase de prioridad es usada para determinar el plan de prioridades de los hilos dentro del proceso. Normalmente usamos la bandera NORMAL_PRIORITY_CLASS. lpEnvironment --> Puntero a un bloque del entorno que contiene algunas cadenas del entorno para el nuevo proceso. Si este parmetro es NULL, el nuevo proceso hereda el bloque de entorno del proceso padre. lpCurrentDirectory --> Puntero que especifica el volumen o disco duro y el directorio para el proceso hijo. NULL si quieres que el proceso hijo herede el del padre. lpStartupInfo --> Apunta a una estructura STARTUPINFO que especifica como la ventana principal del nuevo proceso debera aparecer. la estructura STARTUPINFO contienen muchos

miembros que especifican la apariencia de la ventana principal del proceso hijo. Si no quieres nada especial, puedes llenar la estructura STARTUPINFO con los valores del proceso padre llamando a la funcin GetStartupInfo. lpProcessInformation --> Apunta a la estructura PROCESS_INFORMATION que recibe informacin sobre la identificacin del nuevo proceso. La estructura PROCESS_INFORMATION tiene los siguientes miembros: PROCESS_INFORMATION STRUCT hProcess HANDLE ? ; handle to the child process hThread HANDLE ? ; handle to the primary thread of the child process dwProcessId DWORD ? ; ID of the child process dwThreadId DWORD ? ; ID of the primary thread of the child process PROCESS_INFORMATION ENDS El manejador del proceso y su ID son dos cosas diferentes. Un ID de proceso es un identificador nico para el proceso en el sistema. Un manejador de proceso es un valor que regresa Windows para usar en otras funciones API relacionadas con el proceso. Un manejador de proceso no puede ser usado para identificar un proceso, ya que no es nico. Despus de llamar a CreateProcess, se crea un nuevo proceso y la llamada a CreateProcess regresa de inmediato. Puedes chequear si el nuevo proceso todava est activo llamando a la funcin GetExitCodeProcess que tiene la siguiente sintaxis: GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD Si esta llamada tiene xito, lpExitCode contiene el status de terminacin del proceso en cuestin. Si el valor en lpExitCode es igual a STILL_ACTIVE, entonces ese proceso todava est corriendo. Puedes forzar la terminacin de un proceso llamando a la funcin TerminateProcess. Tiene la siguiente sintaxis: TerminateProcess proto hProcess:DWORD, uExitCode:DWORD Puedes especificar el cdigo de salida que desees para el proceso, cualquier valor que te guste. TerminateProcess no es una manera limpia de terminar un proceso ya que ninguna dll enganchada al proceso ser notificada que el proceso ha terminado.

Ejemplo:
El siguiente ejemplo crear un nuevo proceso cuando el usuario seleccione el elemento de men "create process". Intentar ejecutar "msgbox.exe". Si el usuario quiere terminar el nuevo proceso, puede seleccionar el elemento de men "terminate process". El programa chequear primero si el nuevo proceso ya est destruido, si este no es el caso, el programa llamar a la funcin TerminateProcess para destruir el nuevo proceso. .386 .model flat,stdcall option casemap:none 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

.const IDM_CREATE_PROCESS equ 1 IDM_TERMINATE equ 2 IDM_EXIT equ 3 .data ClassName db "Win32ASMProcessClass",0 AppName db "Win32 ASM Process Example",0 MenuName db "FirstMenu",0 processInfo PROCESS_INFORMATION <> programname db "msgbox.exe",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? hMenu HANDLE ? ExitCode DWORD ? ; contiene el estatus del cdigo de salida de la llamada a ; GetExitCodeProcessl. .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,300,200,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd invoke GetMenu,hwnd mov hMenu,eax .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0

.BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL startInfo:STARTUPINFO .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_INITMENUPOPUP invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode .if eax==TRUE .if ExitCode==STILL_ACTIVE invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED .else invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED .endif .else invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED .endif .ELSEIF uMsg==WM_COMMAND mov eax,wParam .if lParam==0 .if ax==IDM_CREATE_PROCESS .if processInfo.hProcess!=0 invoke CloseHandle,processInfo.hProcess mov processInfo.hProcess,0 .endif invoke GetStartupInfo,ADDR startInfo invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\ NORMAL_PRIORITY_CLASS,\ NULL,NULL,ADDR startInfo,ADDR processInfo invoke CloseHandle,processInfo.hThread .elseif ax==IDM_TERMINATE invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode .if ExitCode==STILL_ACTIVE invoke TerminateProcess,processInfo.hProcess,0 .endif invoke CloseHandle,processInfo.hProcess mov processInfo.hProcess,0 .else invoke DestroyWindow,hWnd .endif .endif .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start Anlisis:

El programa crea la ventana principal y regresa el manejador del men para usarlo en el futuro. Luego espera a que el usuario seleccione una orden o comando en el men. Cuando el usuario selecciona el elemento de men "Process" en el men principal, procesamos el mensaje WM_INITMENUPOPUP para modificar los elementos de men dentro de el men emergente antes de que sea desplegado. .ELSEIF uMsg==WM_INITMENUPOPUP invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode .if eax==TRUE .if ExitCode==STILL_ACTIVE invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED .else invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED .endif .else invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED .endif Por qu queremos procesar este mensaje? porque queremos preparar los elementos en el men emergente antes de que el usuario pueda verlos. En nuestro ejemplo, si el nuevo proceso an no ha comenzado, queremos habilitar el elemento "start process" y difuminar [gray out] el elemento "terminate process". Hacemos la inversin si el nuevo proceso ya est activo. Primero chequeamos si el nuevo proceso todava est activo llamando a la funcin GetExitCodeProcess con el manejador de proceso llenado por la funcin CreateProcess. Si GetExitCodeProcess regresa FALSE, significa que el proceso no ha comenzado todava as que difuminamos el elemento de men "terminate process". Si GetExitCodeProcess regresa TRUE, sabemos que ha sido iniciado un nuevo proceso, pero tenemos que chequear luego si todava est corriendo. As que comparamos el valor en ExitCode al valor STILL_ACTIVE, si son iguales, el proceso todava est corriendo: debemos difuminar el elemento de men "start process" ya que no queremos iniciar varios procesos concurrentes. .if ax==IDM_CREATE_PROCESS .if processInfo.hProcess!=0 invoke CloseHandle,processInfo.hProcess mov processInfo.hProcess,0 .endif invoke GetStartupInfo,ADDR startInfo invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\ NORMAL_PRIORITY_CLASS,\ NULL,NULL,ADDR startInfo,ADDR processInfo invoke CloseHandle,processInfo.hThread Cuando el usuario selecciona el elemento de men "start process", primero chequeamos si el miembro hProcess de la estrcutura PROCESS_INFORMATION ya est cerrado. Si es la primera vez, el valor de hProcess siempre ser cero ya que definimos la estructura PROCESS_INFORMATION en la seccin .data. Si el valor del miembro hProcess no es 0, significa que el proceso hijo ha terminado pero no hemos cerrado su manejador de proceso todava. As que este es el momento de hacerlo. Si llamamos a la funcin GetStartupInfo llenaremos la estructura startupinfo que pasaremos a la funcin CreateProcess. Despus de que llamamos a la funcin CreateProcess para comenzar el nuevo proceso. Nota que no hemos chequeado el valor regersado por CreateProcess ya que hara ms complejo el ejemplo. En la vida real, deberas chequear el valor regresado por CreateProcess. Inmediatamente despus de CreateProcess, cerramos el manejador de hilo primario regresado en la estructura processInfo. Cerrar el manejador no

significa que hemos terminado el hilo, slo siginifica que no queremos usar el manejador para referirse al hilo de nuestro programa. Si no lo cerramos, causar carencia de recursos. .elseif ax==IDM_TERMINATE invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode .if ExitCode==STILL_ACTIVE invoke TerminateProcess,processInfo.hProcess,0 .endif invoke CloseHandle,processInfo.hProcess mov processInfo.hProcess,0 Cuando el usuario selecciona el elemento de men "terminate process", chequeamos si el nuevo proceso ya est activo llamando a la funcin GetExitCodeProcess. Si todava est activo, llamamos la funcin TerminateProcess para matar el proceso. Tambin cerramos el manejador del proceso hijo ya que no lo necesitamos ms.

Tutorial 15: Programacin Multihilo


En este tutorial aprenderemos como crear un programa multihilos [multithreading program]. tambin estudiaremos los mtodos de comunicacin entre los hilos. Bajar el ejemplo aqu.

Teora:
En el tutorial previo, aprendiste que un proceso consta de al menos un hilo [thread]: el hilo primario. Un hilo es una cadena de ejecucin. tambin puedes crear hilos adicionales en tu programa. Puedes concebir la programacin multihilos [multithreading programming] como una programacin multitareas [multitasking programming] dentro de un mismo programa. En trminos de implementacin, un hilo es una funcin que corre concurrentemente con el hilo principal. Puedes correr varias instancias de la misma funcin o puedes correr varias funciones simultneamente dependiendo de tus requerimientos. La programacin multihilos es especfica de Win32, no existe una contraparte en Win16. Los hilos corren en el mismo proceso, as que ellos pueden acceder a cualquiera de sus recursos tal como variables globales, manejadores etc. Sin embargo, cada hilo tiene su pila [stack] propia, as que las variables locales en cada hilo son privadas. Cada hilo tambin es propietario de su grupo de registros privados, as que cuando Windows conmuta a otros hilos, el hilo puede "recordar" su ltimo estado y puede "resumir" la tarea cuando gana el control de nuevo. Esto es manejado internamente por Windows. Podemos dividir los hilos en dos categoras:

1. Hilo de interface de usuario: Este tipo de hilo crea su propia ventana, y as recibe
mensajes de ventana. Puede responder al usuario a travs de su propia ventana. Este tipo de hilo est sujeto a la regla del Mutex de Win16 que permite slo un hilo de interface de usuario en el ncleo de usuario y gdi de 16-bit. Mientras el hilo de interface de usuario est ejecutando cdigo de ncleo de usuario y gdi de 16-bit, otros hilos UI no podrn usar los servicios del ncleo de usuario y gdi. Nota que este Mutex de Win16 es especfico a Windows 95 desde su interior, pues las funciones de la API de Windows 95 se remontan [thunk down] hasta el cdigo de 16-bit. Windows NT no tiene Mutex de Win16 as que los hilos de interface de usuario bajo NT trabajan con ms fluidez que bajo Windows 95. Hilo obrero [Worker thread]: Este tipo de hilo no crea ninguna ventana as que no puede recibir ningn mensaje de ventana. Existe bsicamente para hacer la tarea asignada en el trasfondo hence el nombre del hilo obrero.

2.

Recomiendo la siguiente estrategia cuando se use la capacidad multihilo de Win32: Dejar que el hilo primario haga de interface de usuario y los otros hilos hagan el trabajo duro en el trasfondo. De esta manera, el hilo primario es como un Gobernador, los otros hilos son como el equipo del gobernador [Governor's staff]. El Gobernador delega tareas a su equipo mientras mantiene contacto con el pblico. El equipo del Gobernador ejecuta con obediencia el trabajo y lo reporta al Gobernador. Si el Gobernador fuera a realizar todas las tareas por s mismo, el no podra atender bien al pblico ni a la prensa. Esto sera parecido a una ventana que est realizando una tarea extensa en su hilo primario: no responde al usuario hasta que la tarea ha sido completada. Este programa podra beneficiarse con la creacin de un hilo adicional que sera el respondable de la extensa tarea, permitiendo al hilo primario responder a las rdenes del usuario. Podemos crear un hilo llamando a la funcin CreateThread que tiene la siguiente sintaxis: CreateThread proto lpThreadAttributes:DWORD,\ dwStackSize:DWORD,\ lpStartAddress:DWORD,\ lpParameter:DWORD,\ dwCreationFlags:DWORD,\ lpThreadId:DWORD La funcin CreateThread se parece un poco a CreateProcess. lpThreadAttributes --> Puedes usar NULL si quieres que el hilo tenga el manejador de seguridad por defecto. dwStackSize --> especifica el tamao de la pila del hilo. Si quieres que la pila del nuevo hilo tenga el mismo tamao que la pila del hilo primario, usa NULL en este parmetro. lpStartAddress--> Direccin de la funcin del hilo. Es la funcin que har el trabajo del hilo. Esta funcin DEBE recibir uno y slo un parmetro de 32-bits y regresar un valor de 32-bits. lpParameter --> El parmetro que quieres pasar a la funcin del hilo. dwCreationFlags --> 0 significa que el hilo corre inmediatamante despus de que es creado. Lo opuesto es la bandera CREATE_SUSPENDED. lpThreadId --> La funcin CreateThread llenar el ID del hilo del nuevo hilo creado en esta direccin. Si la llamada a CreateThread tiene xito, regresa el manejador del hilo creado. Sino, regresa NULL. La funcin del hilo corre tan pronto se realiza la llamada a CreateThread, a menos que especifiques la bandera CREATE_SUSPENDED en dwCreationFlags. En ese caso, el hilo es suspendido hasta que se llama a la funcin ResumeThread. Cuando la funcin del hilo regresa con la instruccin ret, Windows llama a la funcin ExitThread para la funcin de hilo implcitamente. T mismo puedes llamar a la funcin ExitThread con tu funcin de hilo pero hay un pequeo punto qu considerar al hacer esto. Puedes regresar el cdigo de salida del hilo llamando a la funcin GetExitCodeThread. Si quieres terminar un hilo desde otro, puedes llamar a la funcin TerminateThread. Pero slo deberas usar esta funcin bajo circunstancias extremas ya que la funcin termina el hilo de inmediato sin darle chance al hilo de limpiarse despus. Ahora vamos a ver los mtodos de comunicacin entre los hilos. Hay tres de ellos: Usar variable globales Mensajes de Windows Eventos

Los hilos comparten los recursos del proceso, incluyendo variables globales, as que los hilos pueden usar variables globales para comunicarse entre s. Sin embargo este mtodo debe ser usado con cuidado. La sincronizacin de hilos debe tenerse en cuenta. Por ejemplo, si dos hilos usan la misma estructura de 10 miembros, qu ocurre cuando Windows de repente jala hacia s el control de un hilo mientras ste estaba en medio de la actualizacin de la estructura? El otro hilo quedar con datos inconsistentes en la estructura! No cometas ningn error, los programas multihilos son difciles de depurar y de mantener. Este tipo de errores parecen ocurrir al azar lo cual es muy difcil de rastrear. Tambin puedes usar mensajes Windows para comunicar los hilos entre s. Si todos los hilos son interface de usuario, no hay problema: este mtodo puede ser usado como una comunicacin en dos sentidos. Todo lo que tienes que hacer es definir uno o ms mensajes de ventana hechos a la medida que sean significativos slo para tus hilos. Defines un mensaje hecho a la medida usando el mensaje WM_USER como el valor base: WM_MYCUSTOMMSG equ WM_USER+100h Windows no usar ningn valor desde WM_USER en adelante para sus propios mensajes, as que puedes usar el valor WM_USER y superiores como tus valores para los mensajes hechos a la medida. Sin uno de los hilos es una interface de ususario y el otro es un obrero, no puedes usar este mtodo como dos vas de comunicacin ya que el hilo obrero no tiene su propia ventana, as que no posee una cola de mensajes. Puedes usar el siguiente esquema: Hilo de interface de usuario ------> variable(s) global(es)----> Hilo obrero Hilo obrero ------> mensaje(s) de ventana hecho(s) a la medida----> Hilo de interface de usuario En realidad, usaremos este mtodo en nuestro ejemplo. El ltimo mtodo de comunicacin es un objeto de evento. Puedes concebir un objeto de evento como un tipo de bandera. Si el objeto evento es un estado "no sealado" [unsignalled], el hilo est durmiendo o es un durmiente, en este estado, el hilo no recibe una porcin de tiempo del CPU. Cuando el objeto de evento est en estado "sealado" [signalled], Windows "despierta" el hilo e inicia la ejecucin de la tarea asignada.

Ejemplo:
Deberas bajar el archivo zip y correr thread1.exe. Haz click sobre el elemento de men "Savage Calculation". Esto le ordenar al programa que ejecute "add eax,eax " por 600,000,000 veces. Nota que durante ese tiempo, no puedes hacer nada con la ventana principal: no puedes moverla, no puedes activar su men, etc. Cuando el clculo se ha completado, aparece una caja de mensaje. Despus de eso, la ventana acepta tus rdenes normalmente. Para evitar este tipo de inconvenientes al usuario, podemos mover la rutoina de "clculo" a un hilo obrero separado y dejar que el hilo primario contine con su tarea de interface de usuario. Incluso puedes ver que aunque la ventana principal responde ms lento que de costumbre, todava responde .386 .model flat,stdcall option casemap:none 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 .const IDM_CREATE_THREAD equ 1 IDM_EXIT equ 2 WM_FINISH equ WM_USER+100h .data ClassName db "Win32ASMThreadClass",0 AppName db "Win32 ASM MultiThreading Example",0 MenuName db "FirstMenu",0 SuccessString db "The calculation is completed!",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? hwnd HANDLE ? ThreadID DWORD ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,300,200,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0

.BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .if lParam==0 .if ax==IDM_CREATE_THREAD mov eax,OFFSET ThreadProc invoke CreateThread,NULL,NULL,eax,\ 0,\ ADDR ThreadID invoke CloseHandle,eax .else invoke DestroyWindow,hWnd .endif .endif .ELSEIF uMsg==WM_FINISH invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp ThreadProc PROC USES ecx Param:DWORD mov ecx,600000000 Loop1: add eax,eax dec ecx jz Get_out jmp Loop1 Get_out: invoke PostMessage,hwnd,WM_FINISH,NULL,NULL ret ThreadProc ENDP end start

Anlisis:
El programa principal presenta al usuario una ventana normal con un men. Si el usuario selecciona el elemento de men "Create Thread", el programa crea un hilo a travs del siguiente cdigo: .if ax==IDM_CREATE_THREAD mov eax,OFFSET ThreadProc invoke CreateThread,NULL,NULL,eax,\ NULL,0,\

ADDR ThreadID invoke CloseHandle,eax La funcin de arriba crea un hilo que crear un procedimiento llamado ThreadProc que correr concurrentemente con el hilo primario. Despus de una llamada satisfactoria, CreateThread regresa de inmediato y ThreadProc comienza a correr. Puesto que no usamos manejadores de hilos, deberamos cerrarlo, sino habr cierta carencia de memoria. Nota que al cerrar el manejador del hilo ste no termina. El nico efecto es que ya no se puede usar ms el manejador del hilo. ThreadProc PROC USES ecx Param:DWORD mov ecx,600000000 Loop1: add eax,eax dec ecx jz Get_out jmp Loop1 Get_out: invoke PostMessage,hwnd,WM_FINISH,NULL,NULL ret ThreadProc ENDP Como puedes ver, ThreadProc ejecuta un cculo salvaje que tarda un poco para terminar y cuando finaliza enva un mensaje WM_FINISH a la ventana principal. WM_FINISH es nuestro mensaje hecho a la medida definido como: WM_FINISH equ WM_USER+100h no tienes que agregar WM_USER con 100h pero es ms seguro hacerlo as. El mensaje WM_FINISH es significativo slo dentro del programa. Cuando la ventana principal recibe el mensaje WM_FINISH, responde desplegando una caja de mensaje que dice que el clculo ha terminado. Puedes crear varios hilos en sucesin enviando varias veces el mensaje "Create Thread". En este ejemplo, la comuicacin se realiza en un solo sentido ya que slo un hilo puede notificar la ventana principal. Si quieres que el hilo principal enve rdenes [commands] al hilo obrero, lo puedes hacer as: agregar un elemento de men que diga algo como "Kill Thread" en el men una variable global que ser usada como bandera de mando [command flag] TRUE=Detener el hilo, FALSE=continuar el hilo modificar ThreadProc para chequear el valor de la bandera de mando en el bucle.

Cuando el usuario selecciona el elemento "Kill Thread" del men, el programa principal pondr el valor TRUE en la bandera de mando. Cuando ThreadProc observa que el valor en la bandera de mando es TRUE, sale del bucle y regresa terminando entonces el hilo. Aprenderemos qu es un objeto evento y como usarlo en un programa multithilo. Bajar el ejemplo aqu.

Teora:
En el tutorial anterior, demostr como se comunican los hilos usando un mensaje de ventana custom. I left out otros dos mtodos: variable global y objeto evento. Usaremos los dos en este tutorial.

Un objeto evento es como un conmutador [switch]: tiene dos estados: activado [on] o inactivado [off]. Cuando un objeto es activado [turned on], est en estado "sealado". Cuando es desactivado [turned off], est en estado "no-sealado". Creas un evento y pones en un recorte de cdigo en los hilos releventes para ver el estado del objeto evento. Si el objeto evento est en el estado no sealado, los hilos que esperan sern puestos a dormir [asleep]. Cuando los hilos estn en estado de espera, consumen algo de tiempo del CPU. Creas un objeto evento llamando a la funcin CreateEvent que tiene la siguiente sintaxis: CreateEvent proto lpEventAttributes:DWORD,\ bManualReset:DWORD,\ bInitialState:DWORD,\ lpName:DWORD lpEventAttribute--> Si especificas un valor NULL, el objeto evento es creado con el descriptor de seguridad por defecto. bManualReset--> Si quieres que Windows automticamente restablezca el objeto evento a estado no sealado despus de llamara WaitForSingleObject, debes especificar FALSE en este parmetro. Sino debes restablecer manualmente el objeto evento con la llamada a ResetEvent. bInitialState--> Si quieres que el objeto evento sea creado en el estado sealado, especifica TRUE como este parmetro sino el objeto evento ser creado en estado no sealado. lpName --> Puntero a una cadena ASCIIZ que es el nombre de un objeto evento. Este nombre es usado cuando quieres llamar a OpenEvent. Si la llamada tiene xito, regresa el manejador al objeto evento creado sino regresa NULL. Puedes modificar el estado de un objeto evento con dos llamadas a la API: SetEvent y ResetEvent. La funcin SetEvent pone el objeto evento en estado sealado. ResetEvent lo pone en el estado inverso. Cuando se crea un objeto, debes poner la llamada a WaitForSingleObject en el hilo que espera por el estado de un objeto evento. WaitForSingleObject tiene la siguiente sintaxis: WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD hObject --> Un manejador a uno de los objetos de sincronizacin. El objeto evento es uno de los objetos de sincronizacin. dwTimeout --> especificar el tiempo en milisegundos que esta funcin esperar para ser el objeto sealado. Si el tiempo especificado ha pasado y el evento objeto todava no est en estado sealado, WaitForSingleObject regresa a la instruccin que le llam. Si quieres esperar por el objeto indefinidamente, debes especificar el valor INFINITE como valor de este parmetro.

Ejemplo:
El ejemplo de abajo despliega una ventana que espera a que el usuario seleccione una orden [command] del men. Si el usuario selecciona "run thread", el hilo comienza el clculo salvaje. Cuando finaliza, aparece una caja de mensaje informando al usuario que la tarea est hecha. Durante el tiempo que el hilo est corriendo, el usuario puede seleccionar "stop thread" para detener el hilo. .386 .model flat,stdcall option casemap:none 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 .const IDM_START_THREAD equ 1 IDM_STOP_THREAD equ 2 IDM_EXIT equ 3 WM_FINISH equ WM_USER+100h .data ClassName db "Win32ASMEventClass",0 AppName db "Win32 ASM Event Example",0 MenuName db "FirstMenu",0 SuccessString db "The calculation is completed!",0 StopString db "The thread is stopped",0 EventStop BOOL FALSE .data? hInstance HINSTANCE ? CommandLine LPSTR ? hwnd HANDLE ? hMenu HANDLE ? ThreadID DWORD ? ExitCode DWORD ? hEventStart HANDLE ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,300,200,NULL,NULL,\

hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd invoke GetMenu,hwnd mov hMenu,eax .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_CREATE invoke CreateEvent,NULL,FALSE,FALSE,NULL mov hEventStart,eax mov eax,OFFSET ThreadProc invoke CreateThread,NULL,NULL,eax,\ NULL,0,\ ADDR ThreadID invoke CloseHandle,eax .ELSEIF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .if lParam==0 .if ax==IDM_START_THREAD invoke SetEvent,hEventStart invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED .elseif ax==IDM_STOP_THREAD mov EventStop,TRUE invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED .else invoke DestroyWindow,hWnd .endif .endif .ELSEIF uMsg==WM_FINISH invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp ThreadProc PROC USES ecx Param:DWORD invoke WaitForSingleObject,hEventStart,INFINITE mov ecx,600000000 .WHILE ecx!=0 .if EventStop!=TRUE add eax,eax dec ecx .else

invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK mov EventStop,FALSE jmp ThreadProc .endif .ENDW invoke PostMessage,hwnd,WM_FINISH,NULL,NULL invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED jmp ThreadProc ret ThreadProc ENDP end start

Anlisis:
En este ejemplo, demuestro otra tcnica para implementar hilos. .IF uMsg==WM_CREATE invoke CreateEvent,NULL,FALSE,FALSE,NULL mov hEventStart,eax mov eax,OFFSET ThreadProc invoke CreateThread,NULL,NULL,eax,\ NULL,0,\ ADDR ThreadID invoke CloseHandle,eax Puedes ver que creo un objeto evento durante el proceso del mensaje WM_CREATE. Creo el objeto evento en estado no sealado con restableci iento automtico. Despus de que es creado el objeto evento, creo el hilo. Sin embargo, el hilo no corre de inmediato, porque espera que el objeto evento est en el estado sealado segn el cdigo siguiente: ThreadProc PROC USES ecx Param:DWORD invoke WaitForSingleObject,hEventStart,INFINITE mov ecx,600000000 La primera linea del procedimiento de hilo es la llamada a WaitForSingleObject. Espera infinitamente por el estado sealado del objeto evento antes de que retorne. Esto significa que incluso cuando el hilo es creado, lo ponemos en estado durmiente. Cuando el usuario selecciona la orden "run thread" del men, ponemos el evento en estado sealado siguiendo este cdigo: .if ax==IDM_START_THREAD invoke SetEvent,hEventStart La llamada a SetEvent pone el evento en estado sealado lo cual hace que la llamada a WaitForSingleObject en el procedimiento de hilo regrese y el hilo comience a correr. Cuando el usuario selecciona la orden [command] "stop thread", ponemos el valor de la variable global "EventStop" en TRUE. .if EventStop==FALSE add eax,eax dec ecx .else invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK mov EventStop,FALSE jmp ThreadProc .endif

Esto detiene el hilo y salta de nuevo a la llamada a WaitForSingleObject. Nota que no tienes que restablecer manualmente el objeto evento en estado no sealado porque especificamos el parmetro bManualReset de la llamada a CreateEvent como FALSE.

Tutorial 17: Librerias De Enlace Dinamico (DLL)


En este tutorial aprenderemos algo sobre DLLs, qu son y cmo crearlas. Te puedes bajar el ejemplo aqu.

Teora:
Si tu programa se agranda demasiado, encontrars que los programas que escribes usualmente tienen algunas rutinas en comn. Es una prdida de tiempo reescribirlas cada vez que empiezas un nuevo programa. Volviendo a los viejos tiempos del DOS, los programadores almacenaban estas rutinas que usaban comnmente en una o varias libreras. Cuando queran usar las funciones, enlazaban la librera al archivo objeto y el enlazador extraa las funciones de la librera y las insertaba en el ejecutable final. Este proceso se llama enlace esttico. Las libreras de rutinas del lenguaje C son un buen ejemplo. La parte negativa de este mtodo est en que tienes funciones idnticas en muchos programas que las usan. Tu espacio en disco se llena almacenando copias idnticas de las funciones. Pero para programas de DOS este mtodo es bastante aceptable ya que suele haber un nico programa activo en memoria. As que no hay un desperdicio notable de memoria. Bajo Windows, la situacin es mas crtica porque puedes tener varios programas funcionando al mismo tiempo. La memoria es consumida rpidamente si tu programa es bastante grande. Windows tiene la solucin a este tipo de problemas: dynamic link libraries [librerias de enlace dinmico]. Las libreras de enlace dinamico son una especie de recopilacin de funciones comunes. Windows no cargar varias copias de la DLL en la memoria de manera que si hay muchos programas que la usen solo corriendo al mismo tiempo, habr una copia de la DLL en la memoria para todos estos programas. Voy a aclarar este punto un poco. En realidad, los programas que usan la misma DLL tendrn su propia copia de esta DLL. Esto har parecer que hay varias copias de la DLL en memoria. Pero en realidad, Windows hace que esto sea mgico a travs de la paginacin de manera que todos los procesos compartan el mismo cdigo de la DLL. As que en la memoria fisica slo hay una copia del cdigo de la DLL. Como siempre, cada proceso tendr su seccin nica de datos de la DLL. El programa enlaza la DLL en tiempo de ejecucin [at run time], no como en las viejas libreras estticas. Por qu se la llama librera de enlace dinmico?. Slo puedes descargar la DLL en el proceso cuando ya no la necesitas. Si el programa es el nico que usa la DLL, ser descargada de la memoria inmediatamente. Pero si la DLL todava es usada por algn otro programa, la DLL contina en memoria hasta que el ltimo programa que la use la descargue. Como siempre, el enlazador tiene el trabajo ms difcil cuando fija las direcciones del archivo ejecutable final. Como no puede "extraer" las funciones e insertarlas en el ejecutable final, de alguna manera tendr que almacenar suficiente informacin sobre la DLL y las funciones en el ejecutable final para poder localizar y cargar la DLL correcta en tiempo de ejecucin [at run time]. Ah es donde interviene la librera de importacin [import library]. Una librera de importacin contiene la informacin sobre la DLL que representa. El enlazador puede extraer la informacin que necesita de la librera de importacin y mete esos datos en el ejecutable. Cuando el cargador de Windows carga el programa en memoria, ve que el programa enlace a una DLL,

as que busca esta DLL, la proyecta en el espacio de direcciones del proceso y fija las direcciones para las llamadas a las funciones en la DLL. Puedes elegir t mismo cargar la librera sin dejrselo al cargador de Windows. Este mtodo tiene sus pro y sus contras: No se necesita una librera de importacin, as que puedes cargar y usar cualquier librera siempre aunque no venga con librera de importacin. Pero todava tienes que saber algo sobre las funciones de su interior, cuantos parmetros cogen y sus contenidos. Cuando dejas al cargador que carge la DLL para tu programa si el cargador no puede encontrar la DLL desplegar el mensaje "A required .DLL file, xxxxx.dll is missing" (El archivo DLL requerido, xxxxx.dll no ha sido encontrado)" y poof! tu programa no tiene la oportunidad de correr aunque esta DLL no sea esencial para esa operacin. Si cargas la DLL t mismo, cuando la DLL no ha sido encontrada y no es esencial para la operacin tu programa podr advertir al usuario sobre el suceso y seguir. Puedes llamar a funciones *no documentadas* que no estn incluidas en la librera de importacin. Suponiendo que conozcas suficiente informacin acerca de la funcin . Si usas LoadLibrary tienes que llamar a GetProcAddress para todas las funciones que quieres llamar. GetProcAddress devuelve la direccin del punto de entrada de la funcin de una DLL en particular. As que tu codigo ser un poco mas largo o mas corto, pero nada mas.

Viendo las ventajas/desventajas de la llamada a LoadLibrary, ahora iremos detallando como crear una DLL. El siguiente codigo es el esqueleto de una DLL. ;-------------------------------------------------------------------------------------; DLLSkeleton.asm ;-------------------------------------------------------------------------------------.386 .model flat,stdcall option casemap:none 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 .data .code DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD mov eax,TRUE ret DllEntry Endp ;------------------------------------------------------------------------------------------------------------------------------------------------; Esta es una funcin ficticia. ; No hace nada. La he puesto esto aqu para mostrar donde puedes insertar las funciones ; dentro de una DLL. ;-------------------------------------------------------------------------------------------------------------------------------------------------TestFunction proc ret TestFunction endp

End DllEntry ;------------------------------------------------------------------------------------; DLLSkeleton.def ;------------------------------------------------------------------------------------LIBRARY DLLSkeleton EXPORTS TestFunction

El programa anterior es el esqueleto de una DLL. Todas las DLL deben tener una funcin de punto de entrada. Windows llamar a la funcin del punto de entrada en caso que: La DLL es cargada por primera vez La DLL es descargada Un hilo es creado en el mismo proceso El hilo es destruido en el mismo proceso

DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD mov eax,TRUE ret DllEntry Endp Puedes nombrar la funcin del punto de entrada como quieras pero tendrs que terminarla END <Nombre de la funcin de entrada>. Esta funcin tiene tres parametros, slo los dos primeros de estos son importantes. hInstDLL es el manejador del mdulo (module handle) de la DLL. Este no es el mismo que el manejador de la instancia (instance handle) del proceso. Tendrs que guardar este valor si necesitas usarlo mas tarde. No podrs obtenerlo otra vez fcilmente. reason puede ser uno de los cuatro valores: DLL_PROCESS_ATTACH La DLL recibe este valor cuando es injertada por primera vez dentro del espacio de direcciones del proceso. Puedes usar esta oportunidad para hacer la inicializacin . DLL_PROCESS_DETACH La DLL recibe este valor cuando va a ser descargada del espacio de direcciones del proceso. Puedes aprovechar esta oportunidad para hacer algo de limpieza y liberar memoria. DLL_THREAD_ATTACH La DLL recibe este valor cuando el proceso crea un nuevo hilo . DLL_THREAD_DETACH La DLL recibe este valor cuando un hilo en el proceso es destruido.

Devuelves TRUE en eax si quieres que la DLL siga funcionando. Si devuelves FALSE, la DLL no ser cargada. Por ejemplo, si tu codigo de inicializacin debe reservar algo de memoria y no puede hacerlo satisfactoriamente, la funcin del punto de entrada devolver FALSE para indicar que la DLL no puede funcionar. Puedes poner tus funciones en la DLL detrs o delante de la funcin de punto de entrada. Pero si quieres que puedan ser llamadas por otros programas debes poner sus nombres en la lista de exportaciones en el archivo de mdulo de definicin (.def). LA DLL necesita un archivo de mdulo de definicin en su entorno de desarrollo. Vamos a echarle un vistazo a esto. LIBRARY DLLSkeleton EXPORTS TestFunction

Normalmente debers tener la primera linea. La declaracin LIBRARY define el nombre interno del mdulo de la DLL. Tendrs que proporcionarlo con el nombre de archivo de la DLL. La definicin EXPORTS le dice al enlazador que funciones de la DLL son exportadas, es decir, pueden ser llamadas desde otros programas. En el ejemplo, queremos que otros mdulos sean capaces de llamar a TestFunction, as que ponemos el nombre en la definicin EXPORTS. Cualquier otro cambio es en las opciones del enlazador. Debers poner /DLL opciones y /DEF:<el nombre de archivo de tu def> en las opciones de tu enlazador algo as : link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj Las opciones del ensamblador son las mismas, esto es /c /coff /Cp. As que despus de enlazar el archivo objeto, obtendrs un .dll y un .lib. El .lib es la librera importada que puedes usar para enlazar a otros programas que usen las funciones de la DLL. Seguido mostrar como usar LoadLibrary para cargar la DLL. ;--------------------------------------------------------------------------------------------; UseDLL.asm ;---------------------------------------------------------------------------------------------.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib .data LibName db "DLLSkeleton.dll",0 FunctionName db "TestHello",0 DllNotFound db "Cannot load library",0 AppName db "Load Library",0 FunctionNotFound db "TestHello function not found",0 .data? hLib dd ? TestHelloAddr dd ?

; el manejador (handle) de la librera (DLL) ; la direccin de la funcin TestHello

.code start: invoke LoadLibrary,addr LibName ;--------------------------------------------------------------------------------------------------------------------------------------; Llama a LoadLibrary con el nombre de la DLL deseada. Si la llamada es correcta ; devolver el manejador (handle) de la librera (DLL). Si no devolver NULL ; Puedes pasar el manejador (handle) a GetProcAddress u otra funcin que requiera ; el manejador (handle) de la librera como parametro. ;---------------------------------------------------------------------------------------------------------------------------------------.if eax==NULL invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK .else mov hLib,eax invoke GetProcAddress,hLib,addr FunctionName ;-------------------------------------------------------------------------------------------------------------------------------------------------

; Cuando obtienes el manejador (handle) de la librera, lo pasas a GetProcAddress con la ; direccin del nombre de la funcin en la DLL que quieres llamar. Esto devuelve la direccin ; de la funcin si es correcto. De otra manera devuelve NULL ; Las direcciones de las funciones no cambian a menos que descarges y recarges la librera . ; As que puedes ponerlas en variables globales para usos futuros. ;-------------------------------------------------------------------------------------------------------------------------------------------------.if eax==NULL invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK .else mov TestHelloAddr,eax call [TestHelloAddr] ;----------------------------------------------------------------------------------------------------------------------------------------------------; Lo siguiente, puedes llamar la funcin con un simple call con la variable conteniendo ; la direccin de la funcin como operando. ;----------------------------------------------------------------------------------------------------------------------------------------------------.endif invoke FreeLibrary,hLib ;------------------------------------------------------------------------------------------------------------; Cuando no necesitas mas la librera descargarla con FreeLibrary. ;------------------------------------------------------------------------------------------------------------.endif invoke ExitProcess,NULL end start Puedes ver que usando LoadLibrary es un poco mas problemtico pero mas flexible

Tutorial 18: Controles Comunes


Aprenderemos qu son los contrioles comunes y cmo usarlos. Este tutorial slo ser una introduccin rpida a ellos. Bajar el ejemplo de cdigo fuente aqu.

Teora:
Windows 95 viene con varias ampliaciones de la interface de usuario sobre Windows 3.1x. Esas ampliaciones enriquecen la GUI. Algunas de ellas eran apliamente usadas antes de que Windows 95 llegara a los almacenes, tales como la barra de estado [status bar], barras de herramientas etc. Los programadores tenan que escribir el cdigo para estas ampliaciones. Ahora Microsoft las ha incluido con Windows 9x y NT. Aprenderemos sobre ellas aqu. Estos son los nuevos controles: Toolbar Tooltip Status bar Property sheet Property page Tree view

List view Animacin Drag list Header Hot-key Image list Progress bar Right edit Tab Trackbar Up-down

Ya que hay muchos, cargarlos todos en memoria y registrarlos sera un despilfarro de recursos. Todos ellos, con excepcin del control "rich edit", estn almacenados en comctl32.dll, desde donde las aplicaciones pueden cargarlas cuando se quiera usarlos. El control "rich edit" reside en su propia dll, richedXX.dll, porque es muy complicado y debido a que es ms grande que su brethren. Puedes cargar comctl32.dll incluyendo una llamada a InitCommonControls en tu programa. InitCommonControls es una funcin en comctl32.dll, as que refirindola en cualquier parte del cdigo de tu programa har que el cargador de archivos PE cargue comctl32.dll cuando corra tu programa.No tienes que ejecutarla , slo inclyela en algn lugar de tu cdigo. Esta funcin no hace NADA! Su unica instruccin es "ret". Su nico propsito es incluir la referencia a comctl32.dll en la seccin de importacin de manera que el caragador de archivos PE lla cargue cada vez que el programa sea caragado. El verdaero canalllo de batalla es el punto de entrada de la fucnin en la DLL que registra todas las clases de controles comunes cuando es cargada la dll. Los controles comunes son creados sobre la base de esas clases as como los controles de ventana hija tales como "edit", "listbox", etc. El control "rich edit" es otra cosa. Si quieres usarlo, tienes que llamar a a LoadLibrary para caragarlo explcitamente y luego llamar a FreeLibrary para descargarlo. Ahora aprenderemos cmo crearlos. Puedes usar un editor de recursos para incorporarlos en las cajas de dilogo o puedes crearlos tmismo. Casi todos los controles comunes se crean llamando a CreateWindowEx o a CreateWindow, pasando le el nombre de la clse del control. Algunos controles comunes tienen funciones de creacin especficas, sin embargo, they are just wrappers around CreateWindowEx para facilitar la creacin de esos controles. Las funciones de creacin especficas existenetes son: CreateToolbarEx CreateStatusWindow CreatePropertySheetPage PropertySheet ImageList_Create

Con el fin de crear controles comunes, tienes que saber sus nombres de clase. Aparecen en la siguiente lista:

Nombre de la Clase ToolbarWindow32 tooltips_class32 msctls_statusbar32

Control Comn Toolbar Tooltip Status bar

SysTreeView32 SysListView32 SysAnimate32 SysHeader32 msctls_hotkey32 msctls_progress32 RICHEDIT msctls_updown32 SysTabControl32

Tree view List view Animacin Header Hot-key Progress bar Rich edit Up-down Tab

Los controles property sheets, property pages y image list tienen sus propias funciones de creacin especficas. Los controles drag list son cajas de listas [listbox] potenciadas as que no tienen su propia clase. Los nombres de las clases de arriba pueden ser verificados chequeando el guin de recursos generado por el editor de recursos de Visual C++. Difieren de los nombres de clase que aparecen en la lista de la referencia de la api de Windows de Borland y de la lista del libro Programacin en Windows 95 de Charles Petzold. La lista de arriba es la precisa. Esos controles comunes pueden usar estilos de ventana generales tales como WS_CHILD, etc. Tambin tienen sus estilos especficos tales como TVS_XXXXX para el control tree view, LVS_xxxx para el control list view, etc. La referencia de la api de Win32 es tu mejor amigo en este punto. Ahora que sabemos cmo crear controles comunes, podemos ver el mtodo de comunicacin entre los controles comunes y sus padres. A diferencia de los controles de ventanas hija, los controles comunes no se comunican con el padre a travs de WM_COMMAND. En vez de eso, ellos envan mensajes WM_NOTIFY a la ventana padre cuando algunos eventos interesantes ocurren con ellos. El padre puede controlar al hijo envindoles mensajes. Tambin hay muchos mensajes para los nuevos controles. Deberas consultar tu referencia de la api de win32 para ms detalles. Vamos ver los controles de barra de progreso [progress bar] y barra de estado [status bar] en el siguiente ejemplo. Cdigo muestra : .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD .const IDC_PROGRESS equ 1 IDC_STATUS equ 2 IDC_TIMER equ 3

; IDs de los controles

.data ClassName db "CommonControlWinClass",0

AppName db "Common Control Demo",0 ProgressClass db "msctls_progress32",0 progreso Message db "Finished!",0 TimerID dd 0

; el nombre de la clase de la barra de

.data? hInstance HINSTANCE ? hwndProgress dd ? hwndStatus dd ? CurrentStep dd ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEB OX+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg==WM_CREATE invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\

WS_CHILD+WS_VISIBLE,100,\ 200,300,20,hWnd,IDC_PROGRESS,\ hInstance,NULL mov hwndProgress,eax mov eax,1000 ; the lParam of PBM_SETRANGE message contains the range mov CurrentStep,eax shl eax,16 ; the high range is in the high word invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0 invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS mov hwndStatus,eax invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; crear un temporizador mov TimerID,eax .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .if TimerID!=0 invoke KillTimer,hWnd,TimerID .endif .elseif uMsg==WM_TIMER ; cuando ocurre un evento de temporizador invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; incrementar el progreso en la barra de progreso sub CurrentStep,10 .if CurrentStep==0 invoke KillTimer,hWnd,TimerID mov TimerID,0 invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SendMessage,hwndStatus,SB_SETTEXT,0,0 invoke SendMessage,hwndProgress,PBM_SETPOS,0,0 .endif .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start

Anlisis:
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls Deliberadamente pongo InitCommonControls despus de ExitProcess para demostrar que InitCommonControls est justo ah para poner una referencia a comctl32.dll en la seccin de importacin. Como puedes ver, los controles comunes trabajan incluso si InitCommonControls no se ejecuta. .if uMsg==WM_CREATE invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\ WS_CHILD+WS_VISIBLE,100,\ 200,300,20,hWnd,IDC_PROGRESS,\ hInstance,NULL mov hwndProgress,eax

Aqu es donde creamos el control comn. Nota que esta llamada a CreateWindowEx contiene hWnd como manejador de la ventana padre. Tambin especifica un ID de control para identificar este control. Sin embargo, como tenemos un manejador del control de ventana, este ID no es usado. Todos los controles de ventana hija debe tener el estilo WS_CHILD. mov eax,1000 mov CurrentStep,eax shl eax,16 invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0 Despus de que es creada la barra de progreso, podemos establecer su rango. El rango por defecto es desde 0 a 100. Si no ests satisfecho con esto, puedes especificar tu propio rango con el mensaje PBM_SETRANGE. lParam de este parmetro contiene el rango, el mximo rango est en la palabra alta y el mnimo est en la palabra baja. Puedes especificar cunto toma un paso [how much a step takes] usando el mensaje PBM_SETSTEP. El ejemplo lo establece a 10 lo cual significa que cuando t envas un mensaje PBM_STEPIT a la barra de progreso, el indicador de progreso se incrementar por 10. Tambin puedes poner tu indicador de posicin enviando mensajes PBM_SETPOS. Este mensaje te da un control ms estricto sobre la barra de progreso. invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS mov hwndStatus,eax invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer mov TimerID,eax Luego, creamos la barra de estado llamando a CreateStatusWindow. Esta llamada es fcil de entender, as que no la comentar. Despus de que es creada la ventana de estado, creamos un temporizador. En este ejemplo, actualizaremos la barra de progreso en un intervalo regular de 100 ms as que debemos crear un control de teporizacin. Abajo est el prototipo de la funcin SetTimer. SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD hWnd : Manejador de la ventana padre TimerID : un identificador del temporizador, nunca igual a cero. Puedes crear tu propio identificador. TimerInterval : el intervalo del temporizador en milisegundos que deben pasar antes de que el temporizador llame al proceso del temporizador o enva un mensaje WM_TIMER lpTimerProc : la direccin de la funcin del temporizador que ser llamada cuando el intervalo de tiempo expire. Si este parmetro es NULL, el temporizador enviar ms bien el mensaje WM_TIMER a la ventana padre. Si esta llamada tiene xito, regresar el TimerID. Si falla, regresa 0. Esto se debe a que el valor del ID del temporizador debe ser diferente a cero. .elseif uMsg==WM_TIMER invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 sub CurrentStep,10 .if CurrentStep==0 invoke KillTimer,hWnd,TimerID mov TimerID,0 invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SendMessage,hwndStatus,SB_SETTEXT,0,0

invoke SendMessage,hwndProgress,PBM_SETPOS,0,0 .endif Cuando expira el intervalo de tiempo especificado, el temporizador enva un mensaje WM_TIMER. Aqu pondrs el cdigo que ser ejecutado. En este ejemplo, actualizamos la barra de progreso y luego chequeamos si ha sido alcanzado el lmite mximo. Si ha sido alcanzado, mataremos el temporizador y luego pondremos el texto en la ventana de estado con el mensaje SB_SETTEXT. Se depliega una caja de mensaje y cuando el usuario hace click sobre OK, limpiamos el texto en la barra de estado y en la barra de progreso. En este tutorial aprenderemos a usar el control Tree View (vista de rbol). Es ms, tambin aprenderemos cmo arrastrar objetos y ponerlos en el control Tree View; adems veremos cmo usar una lista de imgenes en este control. Puedes bajar el ejemplo aqu.

Tutorial 19: Control Tree View Teora:


Un control Tree View es un tipo especial de ventana que representa objetos en orden jerrquico. Un ejemplo es el panel izquierdo del Explorador de Windows. Se puede personalizar este control para mostrar relaciones entre los objetos. Puedes crear un control Tree View llamando CreateWindowEx y pasando la cadena "SysTreeView32" como el nombre de la clase o puedes incorporarlo en una caja de dilogo. No hay que olvidar poner una llamada a InitCommonControls en el cdigo fuente. Hay varios estilos especficos al control Tree View. Estos tres son los ms usados. TVS_HASBUTTONS == Despliega botones ms (+) y menos (-) al lado de los elementos [items] padres. El usuario hace click con los botones del ratn sobre los elementos para expandir o contraer una lista de elementos hijos subordinada a un elemento padre. Para incluir botones con elementos en la raiz del tree view, debe especificarse el estilo TVS_LINESATROOT. TVS_HASLINES == Usa lneas para mostrar la jerarqua de elementos. TVS_LINESATROOT == Usa lneas para enlazar elementos en la raiz del control tree-view. Este valor es ignorado ignored si TVS_HASLINES tampoco es especificado. El control Tree View, como otros controles comunes, se comunica con su ventana padre a travs de mensajes. La ventana padre le puede enviar varios mensajes y el control Tree View puede enviar mensajes de "notificacin" a su ventana padre. En este proceso, el control Tree View no difiere de las otras ventanas: cuando algo interesante ocurre en l, enva un mensaje WM_NOTIFY con informacin a la ventana padre. WM_NOTIFY wParam == ID del control, nada garantiza que este valor sea el nico, as que nosotros no lo usamos. Es mejor usar hwndFrom o el miembro IDFrom de la estructura NMHDR a la que apunta lParam lParam == Puntero a la estructura NMHDR. Algunos controles pueden pasar un puntero ms largo pero debe tener una estructura NMHDR como primer miembro. Es decir, cuando tenemos lParam, podemos estar seguros que apunta a una estructura NMHDR. Ahora examinaremos la estructura NMHDR.

NMHDR struct DWORD hwndFrom DWORD ? idFrom DWORD ? code DWORD ? NMHDR ends hwndFrom es el manejador de la ventana que enva este mensaje WM_NOTIFY. idFrom es el ID del control que enva este mensaje. code es el mensaje actual que el control quiere enviar a su ventana padre. Las notificaciones del control Tree view tienen TVN_ al comienzo, como TVM_CREATEDRAGIMAGE. El control tree view enva TVN_xxxx en el miembro cdigo de NMHDR. La ventana padre puede enviar TVM_xxxx para controlarlo.

Agregando elementos a un control list view


Despus de crear un control Tree View, podemos agregarle elementos. Puedes hacer esto envindole TVM_INSERTITEM. TVM_INSERTITEM wParam = 0; lParam = pointer to a TV_INSERTSTRUCT; A estas alturas, bebes conocer cierta terminologa sobre la relacin entre los elementos en el control Tree View. Un elemento puede ser al mismo tiempo padre, hijo, o ambos. Un elemento padre es el que tiene algn(os) otro(s) subelemento(s) asociado(s) con l. Al mismo tiempo, el el elemento padre puede ser un hijo de algn otro elemento. Un elemento sin un padre se llama elemento raz. Puede haber muchos elementos raz en un control tree view. Ahora examinemos la estructura TV_INSERTSTRUCT TV_INSERTSTRUCT STRUCT DWORD hParent DWORD ? hInsertAfter DWORD ? ITEMTYPE < > TV_INSERTSTRUCT ENDS hParent = Manejador del elemento padre. Si este miembro es el valor TVI_ROOT o NULL, el elemento es insertado en la raiz del control tree-view. hInsertAfter = Manejador del elemento despus del cual el nuevo elemento va a ser insertado o uno de los siguientes valores: TVI_FIRST == Inserta el elemento al comienzo de la lista. TVI_LAST == Inserta el elemento al final de la lista. TVI_SORT == Inserta el elemento dentro de la lista en orden alfabtico. ITEMTYPE UNION itemex TVITEMEX < >

item TVITEM < > ITEMTYPE ENDS Aqu usaremos solamente TVITEM. TV_ITEM STRUCT DWORD imask DWORD ? hItem DWORD ? state DWORD ? stateMask DWORD ? pszText DWORD ? cchTextMax DWORD ? iImage DWORD ? iSelectedImage DWORD ? cChildren DWORD ? lParam DWORD ? TV_ITEM ENDS Esta estructura es empleada para enviar y recibir informacin sobre un elemento del tree view, dependiendo de los mensajes. Por ejemplo, con TVM_INSERTITEM, es usada para especificar el atributo del elemento a ser insertado dentro del control tree view. Con TVM_GETITEM, ser llenado con informacin sobre el elemento seleccionado. imask es usado para especificar cual(es) miembro(s) de la estructura TV_ITEM es (son) valido(s). Por ejemplo, si el valor en imask es TVIF_TEXT, significa slo que el miembro pszText es vlido. Puede cpmbinarse con algunas banderas. hItem es el manejador del elemento en el control tree view. Cada elemento tiene su propio manejador, lo mismo que una ventana tiene su manejador. Si se quiere hacer algo con un elemento, debe seleccionarse ese elemento por su manejador. pszText es el puntero a una cadena terminada en cero que indica el nivel de un elemento dentro de un control tree view. cchTextMax es usado slo cuando se quiere regresar el nivel de un elemento dentro de un control tree view. Como el prgramador debe suministrar el puntero al bufer en pszText, Windows debe saber el tamao del buffer provedo. Hay que suministrar el tamao del buffer en este miembro. iImage y iSelectedImage refieren al ndice dentro de una lista de imgenes que contienen las imgenes a ser mostradas cuando el elemento no es seleccionado (iImage) y cuando es seleccionado (iSelectedImage). Si corres el Explorador de Windows, vers que en el panel izquierdo unas imgenes que representan las carpetas; esas imgenes estn especificadas por estos dos miembros. Con el fin de insertar un elemento dentro del control tree view, al menos deben llenarse los miembros hParent, hInsertAfter, as como imask and pszText.

Agregando imgenes a un control tree view


Si quieres poner una imagen a la izquierda de la etiqueta de un elemento del control tree view, se debe crear una lista de imgenes y asociarla con el control tree view. Se puede crear una lista de imgenes llamando a ImageList_Create. ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \ cInitial:DWORD, cGrow:DWORD Si esta funcin tiene xito, regresa el manejador a una lista de imgenes vaca.

cx == ancho de cada imagen en esta lista, en pixeles. cy == altura de cada imagen en esta lista, en pixeles. Todas las imgenes en una lista deben ser iguales en tamao. Si se especifica un gran bitmap, Windows usar cx y cy para *cortarla* en varios pedazos. As que las imgenes propias deben prepararse como una secuencia de "pinturas" del mismo tamao. flags == especifica el tipo de imgnes en esta lista: si son de color o moncromos y su profundidad de color. Consulta la refencia de la API de Win32 para mayor informacin. cInitial == El nmero de imgenes que esta lista contendr inicialmente. Windows usar esta informacin para localizar memoria para las imgenes. cGrow == Cantidad de imgenes a las que la lista de imgenes puede expandirse cuando el sistema necesita cambiar el tamao de la lista para hacer disponibles ms imgnes. Este parmetro representa el nmero de imgenes nuevas que puede tener una lista de imgenes que ha cambiado de tamao. Una lista de imgenes no es una ventana! Es slo un depsito de imgenes que ha de ser usado por otras ventanas. Despus de crear una lista de imgens, se deben agregar imgenes llamando a ImageList_Add ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD Esta funcin regresa -1 si no tiene xito. himl == manejador de la lista de imgenes a la cual quieres agregar imgenes. Es el valor regresado por una llamada satisfactoria a ImageList_Create hbmImage == manejador del bitmap a ser agregado a la lista de imgenes. Usualmente almacenas el bitmap en el recurso y lo cargas en la llamada a LoadBitmap. Nota que no tienes que especificar los parmetros pasados con el nmero de imgenes contenidas en este bitmap porque esta informacin es inferida a partir de los parmetros cx y cy pasados con la llamada a ImageList_Create. hbmMask == Manejador [handle] al bitmap que contiene la mscara. Si no se usa ninguna mscara con la lista de imgenes, se ignora este parmetro. Normalmente, agregaremos slo dos imgenes a la lista de imgenes para usar con el control treeview: una usada cuando el elemento del control tree view no est seleccionado, y otra para cuando el elemento es seleccionado. Cuando la lista de imgenes ya est lista, la asociamos con el control treeview enviando TVM_SETIMAGELIST al control. TVM_SETIMAGELIST wParam = tipo de lista de imagen a establecer. Hay dos posibilidades: o o TVSIL_NORMAL Establece la lista de igenes normal, que contiene los elementos seleccionados y no seleccionados para el elemento del control tree-view. TVSIL_STATE Establece el estado de la lista de imgenes, que contiene las imgens para los elementos del control tree-view que estn en un estado definido por el usuario. lParam = Manejador de la lista de imgenes

Recuperar informacin sobre el elemento del treeview


Puedes recuperar informacin sobre un elemento de un control tree view enviando el mensaje TVM_GETITEM.

TVM_GETITEM wParam = 0 lParam = puntero a la estructura TV_ITEM structure a ser llenada con la informacin antes de enviar este mensaje, debe llenarse el miembro imask con la(s) bandera(s) que especifica(n) cual(es) miembro(s) de TV_ITEM queires que llene Windows. Y lo ms importante, debes llenar hItem con el manejador al elemento del cual deseas obtener informacin. Pero esto tiene un problema: Cmo puedes conocer el manejador del elemento del cual deseas recuperar informacin? Habr que almacenar todos los manejadores del control Tree View? La respuesta es simple: no tienes necesidad de hacerlo. Puedes enviar el mensaje TVM_GETNEXTITEM al control tree view para recuperar el manejador al elemento del tree view elemento que tiene el (o los) que t especificaste. Por ejemplo, puedes solicitar el manejador del primer elemento hijo, del elemento raiz, del elemento seleccionado, etc. TVM_GETNEXTITEM wParam = bandera lParam = manejador a un elemento tree view (slo necesario para algunos valores de la bandera) El valor en wParam es tan importante que prefiero presentar abajo todas las banderas: o o o o o o TVGN_CARET Regresa el elemento seleccionado. TVGN_CHILD Regresa el primer elemento hijo del elemento especificado por el parmetro hitem TVGN_DROPHILITE Regresa el elemento que es el target de una operacin arrastrar-y-soltar [drag-and-drop]. TVGN_FIRSTVISIBLE Regresa el primer elemento visible. TVGN_NEXT Regresa el siguiente elemento hermano [sibling]. TVGN_NEXTVISIBLE Regresa el siguiente elemento visible que sigue al elemento especificado. El elemento especificado debe ser visible. Usa el mensaje TVM_GETITEMRECT para determinar si un elemento es visible. TVGN_PARENT Regresa el padre del elemento especificado. TVGN_PREVIOUS Regresa el elemento hermano anterior. TVGN_PREVIOUSVISIBLE Regresa el primer elemento visible que precede al elemento especificado. El elemento especificado debe ser visible. Usa el mensaje TVM_GETITEMRECT para determinar si un elemento es visible. TVGN_ROOT Regresa el primer elemento o el que est en el tope del control tree-view.

o o o o

Como puede verse, si se quiere recuperar el manejador a un elemento del control tree view este mensaje resulta de gran inters. SendMessage regresa el manejador al elemento del tree view si tiene xito. Se puede llenar el valor regresado dentro del miembro hItem de TV_ITEM a ser usado con el mensaje TVM_GETITEM.

Operacin Drag and Drop [arrastrar y soltar] en un control tree view


Esta parte es la razn por la cual decid escribir este tutorial. Cuando intent seguir el ejemplo en la referencia de la api win32 (el win32.hlp desde InPrise), qued muy frustrado porque careca de la informacin vital sobre este punto. Por error y prueba, finalmente esboz como implementar drag & drop [arrastrar y soltar] en un control tree view y no quiero que nadie pase por lo mismo que yo.

Abajo estn los pasos para implementar operaciones drag & drop en un control tree view.

1. Cuando el usuario trata de arrastrar [drag] un elemento, el control tree view control
enva la notificacin TVN_BEGINDRAG a la ventana padre. Puedes aprovechar esta oportunidad para crear una imagen de arrastre [drag image] que sea la imagen a ser usada para representar el elemento mientras que est siendo arrastrado. Puedes enviar TVM_CREATEDRAGIMAGE al control tree view para decirle que cree una imagen de arrastre por defecto a partir de la imagen que est siendo usada por el elemento que ser arrastrado. El control tree view crear una lista de imgenes con slo una imagen de arratre y te regresar el manejador de la lista de imgenes. Despus de que la imagen de arratre es creada, especificas el "punto caliente" [hotspot] de la imagen de arratre llamando a ImageList_BeginDrag. ImageList_BeginDrag PROTO himlTrack:DWORD, \ iTrack:DWORD , \ dxHotspot:DWORD, \ dyHotspot:DWORD himlTrack es el manejador a la lista de imgenes que contiene la imagen de arratre. iTrack es el ndice a la lista de imgenes que especifica la imagen de arrastre dxHotspot especifica la distancia relativa al "punto caliente" [hotspot] en el plano horizontal en la imagen de arrastre ya que esta imagen ser usada en el lugar del cursor del ratn, as que especificamos usar cual parte de la imagen es el "punto caliente" . dyHotspot especifica la distancia relativa del "punto caliente" en el plano vertical. Normalmente, iTrack debera ser 0 si le dices al control tree view que cree la imagen de arrastre para t, y dxHotspot y dyHotspot pueden ser 0 si quieres que la esquina izquierda superior de la imagen de arrastre sea el hotspot.

2.

3. Cuando la imagen de arrastre est lista para ser desplazada, llamamos a


ImageList_DragEnter para desplegar la imagen de arrastre en la ventana. ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD hwndLock es el manejador [handle] de la imagen propietaria de la imagen de arrastre. La imagen de arrastre no ser capaz de moverse fuera de esa ventana. x e y son las coordenadas x- e y- del lugar donde la imagen de arrastre debera estar desplegada inicialmente. Nota que estos valores son relativos a la esquina izquierda superior de la ventana, no del rea cliente.

4. Ahora que la imagen de arrastre est desplegada en la ventana, tendrs que soportar
la operacin de arrastre en el control tree view. Sin embargo, no hay mucho problema con esto. Tenemos que monitorear el paso del arrastre con WM_MOUSEMOVE y la localizacin para la operacin de soltar [drop] con mensajes WM_LBUTTONUP. Sin embargo, si la imagen de arrastre est sobre una de las ventanas hijas, la ventana padre recibir cualquier mensaje del ratn. La solucin es capturar la entrada del ratn con SetCapture. Usando esta llamada, los mensajes del ratn sern enviados a la ventana especificada independientemente de donde est el cursor del ratn. Dentro del manejador de WM_MOUSEMOVE, actualizars el paso del arrastre con una llamada a ImageList_DragMove. Esta funcin mueve la imagen que est siendo arrastrada durante una operacin de arrastar-y-soltar [drag-and-drop]. Adems, si lo deseas, puedes "iluminar" [hilite] el elemento sobre el cual est la imagen de arrastre enviando TVM_HITTEST para chequear si la imagen de arrastre est sobre algn elemento. Si lo est, puedes enviar TVM_SELECTITEM con la bandera TVGN_DROPHILITE para "iluminar" ese elemento. Nota que antes de enviar el mensaje TVM_SELECTITEM, debes esconder primero la imagen de arrastre sino tu imagen de arrastre dejar trazas horribles. Puedes esconder la imagen de arrastre enviando ImageList_DragShowNolock y, despus que la operacin de iluminacin [hilite] haya finalizado, llamar de nuevo a ImageList_DragShowNolock para mostrar la imagen de arrastre.

5.

6. Cuando el ususario suelta el botn izquierdo del ratn, debes hacer varias cosas. Si t
iluminas [hilite] un elemento, debes quitarle la iluminacin [un-hilite it] enviando TVM_SELECTITEM con la bandera TVGN_DROPHILITE de nuevo, pero esta vez, lParam DEBE ser cero. Si t no le quitas la iluiminacin el elemento, obtendrs un efecto extrao: cuando selecciones algn otro elemento, ese elemento ser encerrado por un rectngulo pero la iluminacin estar sobre el ltimo elemento iluminado. Luego, debes llamar a ImageList_DragLeave seguido por ImageList_EndDrag. Debes liberar al ratn llamando a ReleaseCapture. Si creas una lista de imgenes, debes destruirla llamando a ImageList_Destroy. Despus de eso, puedes ir a lo que que quieres que haga tu programa cuando la operacin arrastrar y soltar [drag & drop] se haya completado.

Cdigo de demostracin:
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\gdi32.lib includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD .const IDB_TREE equ 4006 ; ID del recurso bitmap .data ClassName db "TreeViewWinClass",0 AppName db "Tree View Demo",0 TreeViewClass db "SysTreeView32",0 Parent db "Parent Item",0 Child1 db "child1",0 Child2 db "child2",0 DragMode dd FALSE ; una bandera para determinar si estamos en modo de arrastre .data? hInstance HINSTANCE ? hwndTreeView dd ? ; manjeador del control tree view hParent dd ? ; manejador del elemento raz del control tree view hImageList dd ? ; manejador de la lista de imgenes usadas en el control tree view hDragImageList dd ? ; manejador de la lista de imgenes usada para almacenar la imagen de arrastre .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX

LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEB OX+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,200,400,NULL,NULL,\ hInst,NULL mov hwnd,eax .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL tvinsert:TV_INSERTSTRUCT LOCAL hBitmap:DWORD LOCAL tvhit:TV_HITTESTINFO .if uMsg==WM_CREATE invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\ WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\ 0,200,400,hWnd,NULL,\ hInstance,NULL ; Create the tree view control mov hwndTreeView,eax invoke ImageList_Create,16,16,ILC_COLOR16,2,10 ; crear la lista de imgenes asociada mov hImageList,eax invoke LoadBitmap,hInstance,IDB_TREE ; cargar el bitmap desde el recurso mov hBitmap,eax invoke ImageList_Add,hImageList,hBitmap,NULL; Agregar el bitmap a la lista de imgenes invoke DeleteObject,hBitmap ; borrar siempre el recurso bitmap invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList mov tvinsert.hParent,NULL mov tvinsert.hInsertAfter,TVI_ROOT mov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE mov tvinsert.item.pszText,offset Parent mov tvinsert.item.iImage,0 mov tvinsert.item.iSelectedImage,1

invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert mov hParent,eax mov tvinsert.hParent,eax mov tvinsert.hInsertAfter,TVI_LAST mov tvinsert.item.pszText,offset Child1 invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert mov tvinsert.item.pszText,offset Child2 invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert .elseif uMsg==WM_MOUSEMOVE .if DragMode==TRUE mov eax,lParam and eax,0ffffh mov ecx,lParam shr ecx,16 mov tvhit.pt.x,eax mov tvhit.pt.y,ecx invoke ImageList_DragMove,eax,ecx invoke ImageList_DragShowNolock,FALSE invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit .if eax!=NULL invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax .endif invoke ImageList_DragShowNolock,TRUE .endif .elseif uMsg==WM_LBUTTONUP .if DragMode==TRUE invoke ImageList_DragLeave,hwndTreeView invoke ImageList_EndDrag invoke ImageList_Destroy,hDragImageList invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0 invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0 invoke ReleaseCapture mov DragMode,FALSE .endif .elseif uMsg==WM_NOTIFY mov edi,lParam assume edi:ptr NM_TREEVIEW .if [edi].hdr.code==TVN_BEGINDRAG invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0, [edi].itemNew.hItem mov hDragImageList,eax invoke ImageList_BeginDrag,hDragImageList,0,0,0 invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y invoke SetCapture,hWnd mov DragMode,TRUE .endif assume edi:nothing .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start

Anlisis:
Dentro del manejador WM_CREATE, se crea el control tree view invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\ WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATRO OT,0,\ 0,200,400,hWnd,NULL,\ hInstance,NULL Notar los estilos. TVS_xxxx son los estilos especficos del control tree view. invoke ImageList_Create,16,16,ILC_COLOR16,2,10 mov hImageList,eax invoke LoadBitmap,hInstance,IDB_TREE mov hBitmap,eax invoke ImageList_Add,hImageList,hBitmap,NULL invoke DeleteObject,hBitmap invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList Despus, se crea una lista de imgenes vaca para que acepte imgenes de 16x16 pixeles en tamao, 16-bit de color e inicialmente, contendr 2 imgenes pero puede ser expandido hasta 10 si se necesita. Luego cargamos el bitmap desde el recurso y lo agregamos a la lista de imgenes. Despus de eso, borramos el manejador del bitmap ya que no ser usado ms. Cuando la lista de imgenes est toda establecida, la asociamos con el control tree view control enviando TVM_SETIMAGELIST al control tree view. mov tvinsert.hParent,NULL mov tvinsert.hInsertAfter,TVI_ROOT mov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE mov tvinsert.u.item.pszText,offset Parent mov tvinsert.u.item.iImage,0 mov tvinsert.u.item.iSelectedImage,1 invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert Insertamos elementos en el control tree view y empezamos del elemento de la raz. Puesto que ser el elemento raz, el miembro del hParent es NULL y hInsertAfter es TVI_ROOT. El miembro imask especifica a ese pszText, iImage y miembros del iSelectedImage de la estructura de TV_ITEM es vlido. Llenamos a esos tres miembros de valores apropiados. pszText contiene la etiqueta del elemento raz, iImage es el ndice a la imagen en la lista de la imagen que se desplegar a la izquierda del elemento del no seleccionado, y iSelectedImage es el ndice a la imagen en la lista de la imagen que se desplegar cuando el elemento se selecciona. Cuando todos los miembros apropiados estn llenos, enviamos el mensaje TVM_INSERTITEM al control tree view para agregar el elemento raz a l. mov hParent,eax mov tvinsert.hParent,eax mov tvinsert.hInsertAfter,TVI_LAST mov tvinsert.u.item.pszText,offset Child1 invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert mov tvinsert.u.item.pszText,offset Child2 invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert Despus de agregar el elemento [item] raiz, podemos enganchar los elementos a la ventana hija. El miembro hParent es llenado ahora con el manejador del elemento padre. Y usaremos imgenes idnticas en la lista de imgenes, as que no cambiemos los miembros iImage e iSelectedImage.

.elseif uMsg==WM_NOTIFY mov edi,lParam assume edi:ptr NM_TREEVIEW .if [edi].hdr.code==TVN_BEGINDRAG invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0, [edi].itemNew.hItem mov hDragImageList,eax invoke ImageList_BeginDrag,hDragImageList,0,0,0 invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y invoke SetCapture,hWnd mov DragMode,TRUE .endif assume edi:nothing Ahora cuando el usuario trata de arrastrar [drag] un elemento, el control tree view control enva un mensaje WM_NOTIFY con el cdigo TVN_BEGINDRAG. lParam es el puntero a una estructura NM_TREEVIEW que contiene algunas piezas de informacin que necesitamos para poner su valor dentro de edi y usar edi como el puntero a la estructura NM_TREEVIEW. assume edi:ptr NM_TREEVIEW es una manera de decirle a MASM que trate a edi como el puntero a la estructura NM_TREEVIEW. Luego creamos una imagen de arrastre enviando TVM_CREATEDRAGIMAGE al control tree view. Regresa el manejador de la lista de imgenes nuevamente creada con una imagen drag image dentro. Llamamos a ImageList_BeginDrag para establecer el "punto caliente" en la imagen de arrastre. Luego introducimos la operacin de arrastre llamando a ImageList_DragEnter. Esta funcin emplea la imagen de arrastre en el lugar especificado de la ventana. Usamos la estructura ptDrag que es un miembro de la estructura NM_TREEVIEW como el punto donde la imagen de arratre debera ser inicializada. Despus de eso capturamos la entrada del ratn y ponemos la bandera para indicar que ahora introducimos el modo de arrastre. .elseif uMsg==WM_MOUSEMOVE .if DragMode==TRUE mov eax,lParam and eax,0ffffh mov ecx,lParam shr ecx,16 mov tvhit.pt.x,eax mov tvhit.pt.y,ecx invoke ImageList_DragMove,eax,ecx invoke ImageList_DragShowNolock,FALSE invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit .if eax!=NULL invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax .endif invoke ImageList_DragShowNolock,TRUE .endif Ahora nos concentramos en WM_MOUSEMOVE. Cuando el usuario arrastra [drags] la imagen de arrastre [the drag image along], nuestra ventana padre recibe el mesaje WM_MOUSEMOVE. En respuesta a estos mensajes, actualizamos la posicin de la imagen de arrastre con ImageList_DragMove. Despus de eso, chequeamos si la drag image est sobre algn elemento. Hacemos eso enviando el mensaje TVM_HITTEST al control tree view con un punto para que l lo chequee. Si la imagen de arrastre est sobre el elemento, iluminamos [hilite] ese elemento enviando el mensaje TVM_SELECTITEM con la bandera TVGN_DROPHILITE al control tree view. Durante la operacin de iluminacin, escondemos la imagen de arrastre de manera que no deje desagradables manchas sobre el control tree view. .elseif uMsg==WM_LBUTTONUP .if DragMode==TRUE invoke ImageList_DragLeave,hwndTreeView

invoke ImageList_EndDrag invoke ImageList_Destroy,hDragImageList invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0 invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0 invoke ReleaseCapture mov DragMode,FALSE .endif Cuando el usuario suelta el botn izquierdo del ratn, llega a su final la operacin de arrastre. Abandonamos el modo de arrastre llamando a ImageList_DragLeave, seguido por ImageList_EndDrag y ImageList_Destroy. Para hacer que los elementos del cotrol tree view se vean bien, tambin chequeamos el ltimo elemento iluminado [hilited], y lo seleccionamos. Tambin debemos quitar la iluminacin [un-hilite], sino los otros elementos no sern iluminaods cuando ellos sean seleccionados. Y finalmente, liberamos la captura del ratn.

Tutorial 20: Subclasificacin de Ventanas


En este tutorial, aprenderemos acerca de la subclasificacin de ventanas, qu es y como aprovecharla. Bajar el ejemplo aqu.

Teora:
Si programas por un tiempo en Windows encontrars casos donde tu ventana tiene CASI todos los atributos que deseas pero no todos. Haz encontrado una situacin donde deseas una clase especial de control de edicin que filtre algunos caracteres indeseables? Lo ms directo que se puede hacer es codificar tu propia ventana. Pero relamente es un trabajo duro y consume tiempo. La subclasificacin de Windows al rescate. En pocas palabras, la subclasificacin de ventanas te permite hacerte cargo de la ventana subclasificada. Tienes el control absoluto sobre ella. Tomemos un ejemplo para clarificar ms. Supongamos que necesitas una caja de texto que acepte slo cadenas en hexadecimal. Si usas un control de edicin simple, tienes que decir algo cuando el usuario transcribe algo diferente a nmeros hexadecimales dentro de la caja de texto, es decir. si el usuario escribe "zb+q" dentro de la caja de texto, no puedes hacer nada con ello, excepto rechazar toda la cadena de texto. Esto al menos carece de profesionalidad. En esencia, necesitas la habilidad de examinar cada caracter que el usuario escribi dentro de la caja de texto en el momento que l lo transcribi. Ahora examinaremos cmo hacerlo. Cuando el usuario tipea algo dentro de la caja de texto, Windows enva el mensaje WM_CHAR al procedimiento de ventana del control de edicin. Este procedimiento de ventana reside dentro de Windows as que no podemos modificarlo. Pero podemos redirigir el flujo de mensajes a nuestro propio procedimiento de ventana. As que nuestro procedimiento de ventana obtendr primero un impacto [shot] de cualquier mensaje de Windows antes de que sea enviado al control de edicin. Si nuestro procedimeinto de ventana resuelve actuar sobre el mensaje, puede aprovechar ahora para hacerlo. Pero si no

desea manejar el mensaje, lo pasa al procedimiento de ventana principal. De esta manera, nuestro procedimiento de ventana se inserta dentro de Windows y del control de edicin. Veamos el siguiente flujo: Antes de la Subclasificacin Windows ==> procedimiento de ventana del control de edicin Despus de la Subclasificacin Windows ==> nuestro procedimiento de ventana ----> procedimiento de ventana del control de edicin Ahora ponemos nuestra atencin en cmo subclasificar una ventana. Nota que la subclasificacin no est limitada a los controles, puede ser usada con cualquier ventana. Pensemos cmo Windows llega a tener conocimiento sobre dnde residen los procedimientos de ventana de los controles de edicin. Una pista?......el miembro lpfnWndProc de la estructura WNDCLASSEX. Si podemos reemplazar este miembro con la direccin de nuestro procedimiento de ventana, Windows enviar ms bien mensajes a nuestro procedimiento de ventana. Podemos hacer esto llamando a SetWindowLong. SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD hWnd = manejador de la ventana cuya estructura WNDCLASSEX ser cambiada nIndex == valor a cambiar. GWL_EXSTYLE Establece un nuevo valor de estilo extendido de ventana. GWL_STYLE Establece un nuevo valor de estilo de ventana. GWL_WNDPROC Establece una nueva direccin del procedimiento de ventana. GWL_HINSTANCE Establece un nuevo manejador de instacia de la aplicacin. GWL_ID Establece un nuevo ID para la ventana. GWL_USERDATA Establece un valor de 32-bit asociado con la ventana. Cada ventana tiene un valor correspondiente de 32-bit para usar por la aplicacin que cre la ventana. dwNewLong = el valor de reemplazo. As que la tarea es fcil. Programamos un procedimiento de ventana que maneje los mensajes para el control de edicin y luego llamamos a SetWindowLong con la bandera GWL_WNDPROC, pasando junto a ella la direccin de nuestro procedimento de ventana como tercer parmetro. Si la funcin tiene xito, el valor de regreso es el valor previo del nmero entero especificado de 32-bit, en nuestro caso, la direccin del procedimiento de ventana original. Necesitamos almacenar este valor para usarlo dentro de nuestro procedimiento de ventana. Recuerda que habr algunos mensajes que no querrs manejar, los pasaremos al procedimiento de ventana original. Podemos hacerlo llamando a la funcin CallWindowProc. CallWindowProc PROTO lpPrevWndFunc:DWORD, \ hWnd:DWORD,\ Msg:DWORD,\ wParam:DWORD,\ lParam:DWORD lpPrevWndFunc = la direccin del procedimiento de ventana original.

Los restantes cuatro parmetros son pasados a nuestro procedimiento de ventana. Los pasamos justo con CallWindowProc. Cdigo Muestra: .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD .data ClassName db "SubclassWinClass",0 AppName db "Subclassing Demo",0 EditClass db "EDIT",0 Message db "You pressed Enter in the text box!",0 .data? hInstance HINSTANCE ? hwndEdit dd ? OldWndProc dd ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEB OX+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,350,200,NULL,NULL,\ hInst,NULL mov hwnd,eax .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ 20,300,25,hWnd,NULL,\ hInstance,NULL mov hwndEdit,eax invoke SetFocus,eax ;----------------------------------------; Subclass it! ;----------------------------------------invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc mov OldWndProc,eax .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_CHAR mov eax,wParam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK .if al>="a" && al<="f" sub al,20h .endif invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam ret .endif .elseif uMsg==WM_KEYDOWN mov eax,wParam .if al==VK_RETURN invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif .else

invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif xor eax,eax ret EditWndProc endp end start

Anlisis:
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc mov OldWndProc,eax Despus de que el control de ventana es creado, lo subclasificamos llamando a SetWindowLong, reemplazando la direccin principal del procedimiento de ventana con la direccin del nuestro. Observa que almacenamos la direccin del procedimiento principal para usarlo luego con CallWindowProc. Nota tambin que EditWndProc es un procedimiento de ventana comn y corriente. .if uMsg==WM_CHAR mov eax,wParam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK .if al>="a" && al<="f" sub al,20h .endif invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam ret .endif Dentro de EditWndProc, filtramos los mensajes WM_CHAR. Si el caracter est entre 0-9 o a-f, lo aceptamos pasndolo con el mensaje al procedimiento de ventana original. Si el carcter est en minscula, lo convertimos en mayscula agregndole 20h. Nota que si el caracter no es el nico que esperamos, lo descartamos. No lo pasamos al procedimiento de ventana original. As que cuando el usuario escribe algo distinto a 0-9 o a a-f, el caracter no aparecer en la ventana de edicin. .elseif uMsg==WM_KEYDOWN mov eax,wParam .if al==VK_RETURN invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .end Ahora quiero demostrar el poder de la subclasificacin atrapando la tecla ENTER. EditWndProc chequea el mensaje WM_KEYDOWN para verificar si es VK_RETURN (la tecla Enter). Si lo es, despliega una caja de mensaje que dice "You pressed the Enter key in the text box!". Si no es una tecla Enter, pasa el mensaje al procedimiento de ventana original. Puedes usar la subclasificacin de ventanas para tomar el control sobre otras ventanas. Es una de las tcnicas ms poderosas que deberas tener en tu arsenal.

Tutorial 21: Tubera

En este tutorial, exploraremos la tubera [pipe], qu es y qu podemos hacer por l. Para hacerlo ms interesante, me meto en la tcnica de cmo cambiar el color del fondo y del texto de una ventana de edicin. Bajar el ejempo aqu.

Teora:
Una tubera [pipe] es un conducto o va de comunicacin entre dos terminales. Puedes usar una tubera para intercambiar datos entre dos procesos diferentes, o dentro del mismo proceso. Es como un walkie-talkie. Das a la otra parte una configuracin y esta parte puede usarla para comunicarse contigo. Hay dos tipos de tuberas: annima y con nombre. Una tubera annima es, como lo dice el nombre, annima: es decir, puedes usarla sin saber su nombre. Una tubera nombrada es lo opuesto: tienes que conocer su nombre antes de usarla. Tambin puedes clasificar las tuberas de acuerdo a su propiedad: un-sentido (one-way) o dossentidos (two way). En una tubera de un sentido, los datos pueden fluir slo en una direccin: de un terminal a otro. Mientras que en una tubera de dos sentidos, los datos pueden ser intercambiados entre dos terminales. Una tubera annima siempre es de un sentido mientras que una tubera nombrada puede ser de un sentido o de dos sentidos. Usualmente se usa una tubera nombrada en un entorno de red donde un servidor puede conectarse a varios clientes. En este tutorial, examinaremos con cierto detalle las tuberas annimas. El propsito principal de una tubera annima es ser usada como un canal de comunicacin entre un proceso padre y un proceso hijo o entre procesos hijos. La tubera annima es realmente til cuando tratamos con una aplicacin de cnsola. Una aplicacin de cnsola es un tipo de programa win32 que usa una cnsola para su entrada y su salida. Una consola es como una caja DOS. Sin embargo, una aplicacin de cnsola es un programa totalmente en 32-bit. Puede usar cualquier funcin GUI, como otros porgramas GUI. Lo que ocurre es que tiene una cnsola para su uso. Una aplicacin de cnsola tiene tres manejadores [handles] que pueden usarse para entrada y salida. Los llamamos manejadores estndard. Hay tres de ellos: entrada estndard, salida estndar y error estndard. El manejador de entrada estndard es usado para leer/recobrar la informacin de la cnsola y el manejador de salida eestndar es usado para informacin salida/impresin para la cnsola. El manejador de error estndar es usado para reportar condiciones de error ya que su salida no puede ser redireccionada. Una aplicacin de cnsola puede recuperar estos tres manejadores llamando a la funcin GetStdHandle, especificando el manejador que quiere obtener. Una aplicacin GUI no tiene una cnsola. Si llamas a GetStdHandle, regresar un error. Si en realidad quieres usar una consola, puedes llamar a AllocConsole para localizar una nueva cnsola. Sin embargo, no olvides llamar a FreeConsole cuando hayas hecho lo que tienes que hacer con la cnsola. Con ms frecuencia se emplea una tubera annima para redireccionar entrada y/o salida de una aplicacin de cnsola hija. Para que esto trabaje el proceso padre puede ser una aplicacin de cnsola o una GUI, pero el proceso hijo debe ser una aplicacin de cnsola. Como debes saber, una aplicacin de cnsola usa manejadores estndard para su entrada y salida. Si queremos redireccionar entrada y/o salida de una aplicacin de cnsola, podemos reemplazar su manejador con el manejador de un terminal de una tubera. Una aplicacin de cnsola no sabe que est usando el manejador de un terminal de una tubera. Lo usar como un manejador estndar. Esto es un tipo de polimorfismo, como se dira en la jerga POO [OOP:

Object Oriented Programming = Programacin Orientada a Objetos]. Esta aproximacin al problema es poderosa ya que no necesitamos modificarde ninguna manera el proceso hijo . Otra cosa que deberas saber sobre las aplicaciones de cnsola es de donde obtiene los manejadores estndar. Cuando se crea una aplicacin de cnsola, el proceso padre tiene dos posiblidades: puede crear una nueva cnsola para la hija o puede dejar que la hija herede su propia cnsola. Para que trabaje la segunda opcin, el proceso padre debe ser una aplicacin de cnsola, pero si es una aplicacin GUI, debe llamar primero a AllocConsole para localizar una cnsola. Comencemos a trabajar. Con el fin de crear una tubera annima necesitas llamar a CreatePipe. CreatePipe tiene el siguiente prototipo: CreatePipe proto pReadHandle:DWORD, \ pWriteHandle:DWORD,\ pPipeAttributes:DWORD,\ nBufferSize:DWORD pReadHandle es un puntero a una variable dword que recibir el manejador al terminal de lectura de la tubera pWriteHandle es un puntero a una variable dword que recibir el manejador al terminal de escritura de la tubera. pPipeAttributes apunta a una estructura SECURITY_ATTRIBUTES que determina si los manejadores lectura y escritura regresados son heredables por el proceso hijo nBufferSize es el tamao sugerido del buffer que la pipa reservar para su uso. Es slo un tamao sugerido. Puedes usar NULL para decirle a la funcin que use el tamao por defecto.

Si la llamada tiene xito, el valor regresado es distinto de cero. Si falla, el valor regresado es cero. Despus de que la llamada tiene xito, obtendrs dos manejadores, uno para el terminal de lectura de la tubera y otro para el terminal de escritura. Ahora resumiremos los pasos para redireccionar la salida estndard de un programa de cnsola hijo hacia nuestro propio proceso. Nota que mi mtodo difiere del de la referencia de la api de win32 suministrada por Borland. El mtodo en la referencia de la api win32 asume que el proceso padre es una aplicacin de cnsola y por eso el proceso hijo puede heredar los manejadores estndar de l. Pero en muchas ocasiones, necesitaremos redireccionar la salida desde una aplicacin de cnsola a una GUI.

1. Crear una tubera annima con CreatePipe. No olvides establecer el miembro 2.


bInheritable de SECURITY_ATTRIBUTES a TRUE para que el manejador sea heredable. Ahora debemos preparar los parmetros que pasaremos a CreateProcess ya que los usaremos para cargar la aplicacin de cnsola hija. Otra estructura importante es STARTUPINFO. Esta estructura determina la apariencia de la ventana principal del proceso hijo cuando aparece por vez primera. Esta estructura es vital para nuestro propsito. Puedes esconder la ventana principal y pasar el manejador de la tubera al proceso de cnsola hijo con ella. Abajo estn los miembros que debes llenar: o cb : tamao de la estructura STARTUPINFO o dwFlags : las banderas binarias que determinan cuales de los miembros de la estructura son validos tambin gobiernan el estado mostrado/oculto de la ventana principal. Para nuestro propsito, debera usar una combinacin de STARTF_USESHOWWINDOW y STARTF_USESTDHANDLES o hStdOutput and hStdError : los manejadores que quieres que el proceso hijo use como manejadores salida/error estndar. Para nuestro propsito, pasaremos el manejador de escritura de la tubera como manejador de salida y

3.

4.

5.

error estndar del proceso hijo. De manera que cuando las salidas hijas algo a la salida/error estndar, realmente pase la informacin a travs de la tubera al proceso padre. o wShowWindow gobierna el estado mostrado/oculto de la ventana principal. Para nuestro propsito, no queremos que la ventana de la cnsola del proceso hijo se muestre, as que ponemos SW_HIDE en este miembro. Llamar a CreateProcess para cargar la aplicacin hija. Despus de que CreateProcess ha tenido xito, la hija todava est durmiendo. Es cargada en la memoria pero no corre de inmediato. Cerrar el manejador de escritura de la tubera. Esto es necesario. Porque el proceso padre no tiene uso para el manejador de escritura de la tubera, y la pipa no trabajar si hay ms de un terminal de escritura, DEBEMOS cerrarlos antes de leer los datos de la tubera. Sin embargo, no cierres el manejador de escritura antes de llamar a CreateProcess, porque sino tu pipa se romper. Deberas cerrarla justo despus del regreso de CreateProcess y antes de leer los datos del terminal de lectura de la tubera. Ahora puedes leer los datos del terminal de lectura de la piap con ReadFile. Con ReadFile, you kick dentro del proceso hijo en modo de ejecucin [running mode]. Iniciar la ejecucin y cuando escriba algo al manejador de salida estndar (el cual es realmente el manejador al termianl de escritura de la tubera), los datos sern enviados a travs de la tubera al terminal de lectura. Puedes pensar en ReadFile como una extraccin de datos del terminal de lectura de la tubera. Debes llamar a ReadFile repetidas veces hasta que retorne 0 lo cual significa que ya no hay ms datos que leer. Puedes hacer algo con los datos que lees de la tubera. En nuestro ejemplo, los pongo dentro del control de edicin. Cerrar el manejador de lectura de la tuberia.

Ejemplos:
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\gdi32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD .const IDR_MAINMENU equ 101 IDM_ASSEMBLE equ 40001

; el ID del men principal

.data ClassName db "PipeWinClass",0 AppName db "One-way Pipe Example",0 EditClass db "EDIT",0 CreatePipeError db "Error during pipe creation",0 CreateProcessError db "Error during process creation",0 CommandLine db "ml /c /coff /Cp test.asm",0 .data? hInstance HINSTANCE ? hwndEdit dd ? .code start:

invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE mov wc.lpszMenuName,IDR_MAINMENU 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 Reg es terClassEx, addr wc invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL mov hwnd,eax .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke D es patchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL rect:RECT LOCAL hRead:DWORD LOCAL hWrite:DWORD LOCAL startupinfo:STARTUPINFO LOCAL pinfo:PROCESS_INFORMATION LOCAL buffer[1024]:byte LOCAL bytesRead:DWORD LOCAL hdc:DWORD LOCAL sat:SECURITY_ATTRIBUTES .if uMsg==WM_CREATE invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL mov hwndEdit,eax .elseif uMsg==WM_CTLCOLOREDIT invoke SetTextColor,wParam,Yellow invoke SetBkColor,wParam,Black invoke GetStockObject,BLACK_BRUSH ret .elseif uMsg==WM_SIZE

mov edx,lParam mov ecx,edx shr ecx,16 and edx,0ffffh invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE .elseif uMsg==WM_COMMAND .if lParam==0 mov eax,wParam .if ax==IDM_ASSEMBLE mov sat.niLength,sizeof SECURITY_ATTRIBUTES mov sat.lpSecurityDescriptor,NULL mov sat.bInheritHandle,TRUE invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL .if eax==NULL invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK .else mov startupinfo.cb,sizeof STARTUPINFO invoke GetStartupInfo,addr startupinfo mov eax, hWrite mov startupinfo.hStdOutput,eax mov startupinfo.hStdError,eax or startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES mov startupinfo.wShowWindow,SW_HIDE invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo .if eax==NULL invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK .else invoke CloseHandle,hWrite .while TRUE invoke RtlZeroMemory,addr buffer,1024 invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL .if eax==NULL .break .endif invoke SendMessage,hwndEdit,EM_SETSEL,-1,0 invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer .endw .endif invoke CloseHandle,hRead .endif .endif .endif .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start

Anlisis:
El ejemplo llamar a ml.exe para ensamblar un archivo llamado test.asm y redireccionar la salida de ml.exe al control de edicin en su rea cliente.

Cuando el programa es cargado, registra la clase de ventana y crea la ventana principal como es usual. Lo primero que hace durante la creacin de la ventana principal es crear un control de edicin que ser usado paera desplegar la salida de ml.exe. Ahora la parte interesante, cambiaremos el color del texto y del fondo del control de edicin. Cuando un control de edicin va a pintar su rea cliente, enva el mensaje WM_CTLCOLOREDIT a su padre. wParam contiene el manejador del dispositivo de contexto que usar el control de edicin para escribir su propia rea cliente. Podemos aprovechar esto para cambiar las caractersticas de HDC. .elseif uMsg==WM_CTLCOLOREDIT invoke SetTextColor,wParam,Yellow invoke SetTextColor,wParam,Black invoke GetStockObject,BLACK_BRUSH ret SetTextColor cambia el color del texto a amarillo. SetTextColor cambia el color de fondo del texto a negro.Y finalmente, obtenemos el manejador a la brocha negra que queremos regresar a Windows. Con el mensaje WM_CTLCOLOREDIT, debes regresar un manejador a la brocha que Windows usar para pintar el fondo del control de edicin. En nuestro ejemplo, quiero un fondo negro as que regreso a Windows un manejador a la brocha negra. Ahora el usuario selecciona el elemento de men Assemble, crea una tubera annima. .if ax==IDM_ASSEMBLE mov sat.niLength,sizeof SECURITY_ATTRIBUTES mov sat.lpSecurityDescriptor,NULL mov sat.bInheritHandle,TRUE Antes de llamar a CreatePipe, primero debemos llenar la estrcutura SECURITY_ATTRIBUTES. Nota que podemos usar NULL en el miembro lpSecurityDescriptor si nos tiene sin cuidado la seguridad. Y el miembro bInheritHandle debe ser TRUE para que los manejadores de la tubera sean heredados al proceso hijo. invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL Despus de eso llamamos a CreatePipe que, si tiene xito, llenar las variables hRead y hWrite con los manejadores a los terminales de lectura y escritura respectivamente. mov startupinfo.cb,sizeof STARTUPINFO invoke GetStartupInfo,addr startupinfo mov eax, hWrite mov startupinfo.hStdOutput,eax mov startupinfo.hStdError,eax mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES mov startupinfo.wShowWindow,SW_HIDE Ahora vamos a llenar la estructura STARTUPINFO. Llamamos a GetStartupInfo para llenar la estructura STARTUPINFO con los valores por defecto del proceso padre. Debes llenar la estructura STARTUPINFO con esta llamada si quieres que tu cdigo trabaja con win9x y NT. Despus que regrese la llamada a GetStartupInfo, puedes modificar los miembros que son importantes. Copiamos el manejador del terminal de escritura de la tubera dentro de hStdOutput y hStdError ya que queremos que el proceso hijo lo use en vez de los manejadores estndar de salida/error. Tambin queremos esconder la ventana de la cnsola del proceso hijo, as que ponemos el valor SW_HIDE dentro del miembro wShowWidow. Por ltimo,

debemos indicar que los miembros hStdOutput, hStdError y wShowWindow son vlidos y deben ser usados especificando las banderas STARTF_USESHOWWINDOW y STARTF_USESTDHANDLES en el miembro dwFlags. invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo Ahora creamos el proceso hijo con la llamada a CreateProcess. Nota que el parmetro bInheritHandles debe ser establecido a TRUE para que trabaje el manejador de la tubera. invoke CloseHandle,hWrite Despus de que creamos satisfactoriamente el proceso hijo, debemos cerrar el terminal de escritura de la tubera. Recuerda que pasamos el manejador de escritura al proceso hijo a travs de la estructura STARTUPINFO. Si no cerramos el manejador de escritura de nuestro terminal, habrn dos terminales de escritura. Y la tubera no trabajar. Debemos cerrar el manejador de escritura despus de CreateProcess pero antes de leer los datos del terminal de lectura. .while TRUE invoke RtlZeroMemory,addr buffer,1024 invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL .if eax==NULL .break .endif invoke SendMessage,hwndEdit,EM_SETSEL,-1,0 invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer .endw Ahora estamos listos para leer los datos de la salida estndar del proceso hijo. Nos mantenemos en un bucle infinito hasta que no hayan ms datos que leer desde el terminal de lectura de la tubera. Llamamos a RtlZeroMemory para llenar el buffer con ceros y luego llmamos a ReadFile, pasando el manejador de lectura de la tubera en lugar de un manejador de archivo. Nota que slo leemos un mximo de 1023 bytes ya que necesitamos que los datos sean una cadena ASCIIZ que podemos pasar al control de edicin Cuando regresa ReadFile con los datos en el buffer, llenamos los datos dentro del control de edicin. Sin embargo, aqu hay un pequeo problema. Si usamos SetWindowText para poner los datos dentro del control de edicin, los nuevos datos sobreescribirn los datos existentes! Queremos que los datos se anexen al final de los datos existentes. Para alcanzar esa meta, primero movemos la careta alfinal del texto en el control de edicin enviando el mensaje EM_SETSEL con wParam==-1. Luego, anexamos los datos en ese punto enviando el mensaje EM_REPLACESEL. invoke CloseHandle,hRead Cuando ReadFile retgresa NULL, rompemos el bucle y cerramos el manejador de escritura.

Tutorial 22: Superclasificacin


En este tutorial aprenderemos sobre la superclasificacin, qu es y para qu sirve. Tambin aprenders cmo suministrar navegacin con la tecla Tab a los controloes de tu propia ventana. Bajar el ejemplo aqu

Teora:
En tu carrera de programacin seguramente encontrars alguna situacin donde necesitars controles con un comportamiento *levemente* distinto. Por ejemplo, puedes necesitar diez controles de edicin que acepten slo nmeros hexadecimales. Hay varias maneras de alcanzar este objetivo: Crear tu propia clase e instanciar los cotroles Crear esos controles de edicin y luego subclasificarlos Superclasificar el control de edicin

El primer mtodo es demasiado tedioso. Tienes que implementar todas las funcionalidades del control t mismo. Es una tarea difcil de hacer con agilidad. El segundo mtodo es mejor que el primero, pero todava requiere mucho trabajo. Es bueno slo si subclasificas unos cuantos controles pero ya casi es una pesadilla subclasificar ms de doce ventanas. La superclasificacin es la tcnica que deberas usar para esta ocasin. La subclasificacin es la tcnica que empleaas para *tomar el control* de una clase de ventana particular. Por *tomar el control*, quiero decir que puedes modificar la propiedad de la clase de la ventana para adaptarla a tus propsitos y luego crear un montn de controles. Aqu estn esbozados los pasos de la subclasificacin: llamar a GetClassInfoEx para obtener la informacin de la ventana que quieres subclasificar. GetClassInfoEx requiere un puntero a la estructura WNDCLASSEX que ser llenada con la informacin si la llamada resulta satisfactoria. Modificar los miembros de WNDCLASSEX que quieres. Sin embargo, hay dos miembros que DEBES modificar: o hInstance Debes poner el manejador de instancia de tu programa dentro de este miembro. o lpszClassName Debes suministrarlo con un puntero al nuevo nombre de la clase. No necesitas modificar el miembro lpfnWndProc pero muchas veces, necesitas hacerlo. Slo recuerda salvar el miembro original lpfnWndProc si quieres llamarla con CallWindowProc. Registtrar la estructura WNDCLASSEX modificada. Tendrs una nueva clase de ventana que tendr algunas caractersticas de la antigua clase de ventana. Crear la nueva ventana a partir de la nueva clase.

La superclasificacin es mejor que subclasificar s quieres crear mechos controles con las mismas caractersticas. Ejemplo: .386 .model flat,stdcall option casemap:none 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 WM_SUPERCLASS equ WM_USER+5 WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data ClassName db "SuperclassWinClass",0 AppName db "Superclassing Demo",0 EditClass db "EDIT",0 OurClass db "SUPEREDITCLASS",0 Message db "You pressed the Enter key in the text box!",0 .data? hInstance dd ? hwndEdit dd 6 dup(?) OldWndProc dd ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 hInst pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEB OX+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,350,220,NULL,NULL,\ hInst,NULL mov hwnd,eax .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp

WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL wc:WNDCLASSEX .if uMsg==WM_CREATE mov wc.cbSize,sizeof WNDCLASSEX invoke GetClassInfoEx,NULL,addr EditClass,addr wc push wc.lpfnWndProc pop OldWndProc mov wc.lpfnWndProc, OFFSET EditWndProc push hInstance pop wc.hInstance mov wc.lpszClassName,OFFSET OurClass invoke RegisterClassEx, addr wc xor ebx,ebx mov edi,20 .while ebx<6 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ edi,300,25,hWnd,ebx,\ hInstance,NULL mov dword ptr [hwndEdit+4*ebx],eax add edi,25 inc ebx .endw invoke SetFocus,hwndEdit .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_CHAR mov eax,wParam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK .if al>="a" && al<="f" sub al,20h .endif invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam ret .endif .elseif uMsg==WM_KEYDOWN mov eax,wParam .if al==VK_RETURN invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .elseif al==VK_TAB invoke GetKeyState,VK_SHIFT test eax,80000000 .if ZERO? invoke GetWindow,hEdit,GW_HWNDNEXT .if eax==NULL invoke GetWindow,hEdit,GW_HWNDFIRST .endif

.else invoke GetWindow,hEdit,GW_HWNDPREV .if eax==NULL invoke GetWindow,hEdit,GW_HWNDLAST .endif .endif invoke SetFocus,eax xor eax,eax ret .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif xor eax,eax ret EditWndProc endp end start

Anlisis:
El programa crear una ventana con seis controles de edicin "modificados" en su rea cliente. Los controles de edicin aceptarn slo dgitos hexadecimales. Realmente, el ejemplo de subclasificacin para hacer supreclasificacin. El programa comienza normalmente hasta llegar a la parte interesante, donde se crea la ventana: .if uMsg==WM_CREATE mov wc.cbSize,sizeof WNDCLASSEX invoke GetClassInfoEx,NULL,addr EditClass,addr wc Primero debemos llenar la estructura WNDCLASSEX con los datos de la clase que queremos subclasificar, en este caso, la clase EDIT. Recuerda que debes establecer el miembro cbSize de la estructurta WNDCLASSEX antes de llamar a GetClassInfoEx sino la estrcutura WNDCLASSEX no ser llenada debidamente. Despus de que regresa GetClassInfoEx, wc es llenada con toda la informacin que necesitamos para crear la nueva clase de ventana. push wc.lpfnWndProc pop OldWndProc mov wc.lpfnWndProc, OFFSET EditWndProc push hInstance pop wc.hInstance mov wc.lpszClassName,OFFSET OurClass Ahora debemos modificar algunos miembros de wc. El primero es el puntero al procedimiento de ventana. Como necesitamos encadenar nuestro procedimiento de ventana con el original, debemos salvarlo con una variable para poderlo llamar con CallWindowProc. Esta tcnica es idntica a la de subclasificacin, excepto porque modificas la estructura WNDCLASSEX directamente sin tener que llamar a SetWindowLong. Hay dos miembros que debern ser cambiados, sino no padrs registrar la nueva clase de ventana:, hInstance and lpsClassName. Debes reemplazar el valor original de hInstance con hInstance del propio programa. Y debes elegir un nuevo nombre para la nueva clase. invoke RegisterClassEx, addr wc

Cuando todo est listo, registras la nueva clase de ventana. Obtendrs una nueva clase de ventana con algunas caractersticas de la antigua clase. xor ebx,ebx mov edi,20 .while ebx<6 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ edi,300,25,hWnd,ebx,\ hInstance,NULL mov dword ptr [hwndEdit+4*ebx],eax add edi,25 inc ebx .endw invoke SetFocus,hwndEdit Ahora que hemos registrado la clase, podemos crear ventanas basadas en ella. En el recorte anterior, uso ebx como contador del nmero de ventanas creadas. edi es usado como la coordenada y correspondiente a la esquina izquierda del programa. Cuando se crea una ventana, su manejador es almacenado en un arreglo o array de variables dwords. Cuando todas las ventanas son creadas, porn el foco de la ventana a la primera ventana. En este punto ya tienes 6 controles de edicin que slo aceptan dgitos hexadecimales. Los procedimientos de ventanas reemplazados manejan el filtro. Realmente, es idntico al procedimiento de ventana en la subclasificacin. Como puedes ver, ya no tienes que hacer el trabajo extra de la subclasificacin. Me he metido en un recorte de cdigo para manejar controles de navegacin con tab y hacer ms rico este ejemplo. Normalmente, si quieres poner controles en una caja de dilogo, el propietario de la caja de dilogo maneja las teclas de navegacin para t, de manera que puedas usar la tecla tab para ir al prximo control o shift-tab para regresar al control previo. Alas, esta funcionalidad no est disponible si colocas los controles sobre una ventana. Tienes que subclasificarlos de manera que puedas manejar las teclas Tab por t mismo. En nuestro ejemplo no necesitamos subclasificar los controles uno por uno porque los hemos superclasificado, as que podemos proveer un "propietario del control central de navegacin" para ellos.

.elseif al==VK_TAB invoke GetKeyState,VK_SHIFT test eax,80000000 .if ZERO? invoke GetWindow,hEdit,GW_HWNDNEXT .if eax==NULL invoke GetWindow,hEdit,GW_HWNDFIRST .endif .else invoke GetWindow,hEdit,GW_HWNDPREV .if eax==NULL invoke GetWindow,hEdit,GW_HWNDLAST .endif .endif invoke SetFocus,eax xor eax,eax ret El recorte de cdigo de arriba es tomado del procedimiento EditWndClass. Chequea si el usuario presion la tecla Tab, si es as, llama a GetKeyState para chequear si la tecla SHIFT tambin est presionada. GetKeyState regresa un valor en eax que determina si la tecla

especfica ha sido presionada o no. Si ha sido presionada, el bit alto de eax est establecido, vale 1. Si no, el bit alto est en blanco, vale 0. As que probamos el valor de retorno contra 80000000h. Si el bit alto est establecido, significa que el usuario presion shift+tab lo cual debemos manejar por separado. Si el usuario presiona slo la tecla Tab, llamamos a GetWindow para recuperar el manejador del prximo control. Usamos la bendera GW_HWNDNEXT para decir a GetWindow que obtenga el manejador a la ventana prxima a la lnea del actual hEdit. Si esta funcin regeras NULL, lo interpretamos como si no ms manejadorespara obtenert as que el actual hEdit es el ltimo control en la lnea. We will "wrap around" al primer control llamando a GetWindow con la bendera GW_HWNDFIRST. Similar al caso Tab, shift-tab trabaja de manera inversa.

Tutorial 23: Icono de Bandeja [Tray Icon]


En este tutorial, aprenderemos cmo poner iconos en la banadeja de sistema y cmo crear/usar un men emergente. Bajate el ejemplo aqu. Teora: La bandeja del sistema es la regin rectangular en la barra de tareas donde residen la mayora de los iconos. Normalmente, vers como mnimo un reloj digital aqu. Tambin puedes poner iconos en la barra de sistema. Debajo estn los pasos que tienes que seguir para poner un icono en la barra de sistema:

1. rellena la estructura NOTIFYICONDATA que tiene los siguientes miembros:


cbSize El tamao de esta estructura. hwnd Manejador (Handle) de la ventana que recibir la notificacion cuando ocurran eventos de ratn sobre el icono de la bandeja. o uID La constante que es usada como identificador del icono. Tu eres el nico que decide este valor. En caso de que tengas mas de un icono de bandeja, podrs chequear aqu desde cul icono de bandeja proviene la notificacin del ratn. o uFlags Especifica qu miembros son vlidos NIF_ICON El miembro hIcon es vlido. NIF_MESSAGE El miembro uCallbackMessage es vlido. NIF_TIP El miembro szTip es vlido. o uCallbackMessage El mensaje comn que Windows mandar a la ventana especificado por el miembro hwnd cuando ocurren eventos sobre el icono de bandeja. Creas este mensaje t mismo. o hIcon El manejador (handle) del icono que quieres poner en la barra de sistema o szTip Un arreglo [array] de 64-bytes que contiene la cadena que ser usada como texto tooltip cuando el ratn est sobre el icno de bandeja. Llama a Shell_NotifyIcon que est definida en shell32.inc. Esta funcin tiene el siguiente prototipo: o o

2. 3.

Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD dwMessage Es el tipo de mensaje a enviar al shell de Windows. NIM_ADD Aade un icono al area de estado.

NIM_DELETE Borra un icono del area de estado. NIM_MODIFY Modifica un icono en el area de estado. pnid es el puntero a la estructura NOTIFYICONDATA rellenada con valores apropiados Si quieres aadir un icono a la bandeja, usa el mensage NIM_ADD, si quieres borrar un icono , usa NIM_DELETE. Eso es todo lo que hay en ella. Pero la mayora de las veces no ests contento con slo poner un icono aqu. Necesitas ser capaz de responder a eventos de ratn sobre el icono de bandeja. Puedes hacer esto procesando el mensaje que especificas en el miembro uCallbackMessage de la estructura NOTIFYICONDATA. Este mensaje tiene los siguientes valores en wParam y lParam (agradecimientos especiales a s__d por esta informacion): wParam contiene el ID del icono. Este es el mismo valor que pones en el miembro uID de la estructura NOTIFYICONDATA. lParam La palabra baja (low word) contiene el mensage de ratn. Por ejemplo, si el usuario pulsa el botn derecho en el icono, lParam contendra WM_RBUTTONDOWN.

La mayora de los iconos de bandeja, como siempre, muestran un men emergente cuando el usuario pulsa el botn derecho sobre ste. Podemos implementar esta funcin creando un men emergente y entonces llamando a TrackPopupMenu para mostrarlo. Los pasos estn descritos abajo:

1. Crear un menu emergente llamando a CreatePopupMenu. Esta funcin crea un menu 2. 3.


vaco. Esto devuelve un manejador (handle) al men en eax si el resultado es satisfactorio. Aade elementos de men con AppendMenu, InsertMenu o InsertMenuItem. Cuando quieres mostrar el men emergente donde est el cursor del ratn, llama a GetCursorPos para obtener las coordenadas de pantalla del cursor y entonces llama a TrackPopupMenu para mostrar el men. Cuando el usuario selecciona un elemento del men emergente, Windows manda el mensaje WM_COMMAND a tu procedimiento de ventana como si fuera una seleccin de men normal.

Nota: Precaucin con dos molestos comportamientos cuando usas menues emergentes con un icoo de bandeja:

1. Cuando el men emergente es mostrado, si pulsas fuera del men, el men emergente
no desaparecer inmediatamente, como debiera. Este comportamiento ocurre porque la ventana que recibir la notificacin desde el men emergente DEBER ser la ventana de primer plano. As que la llamada a SetForegroundWindow corregir esto. Despues de llamar a SetForegroundWindow, encontrars que la primera vez que se muestra el men emergente, este trabaja bien pero en las siguientes ocaciones, el men emergente se mostrar y cerrar inmediatamente. Este comportamiento es "intencionado", par citar a MSDN. El cambio de tarea al programa que es dueo del icono de bandeja en un futuro cercano es necesario. Puedes forzar este cambio de tarea enviando cualquier mensage a la ventana del programa. Usa PostMessage, no SendMessage!

2.

Ejemplo: .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\shell32.inc

includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\shell32.lib WM_SHELLNOTIFY equ WM_USER+5 IDI_TRAY equ 0 IDM_RESTORE equ 1000 IDM_EXIT equ 1010 WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD .data ClassName db "TrayIconWinClass",0 AppName db "TrayIcon Demo",0 RestoreString db "&Restore",0 ExitString db "E&xit Program",0 .data? hInstance dd ? note NOTIFYICONDATA <> hPopupMenu dd ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE 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 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEB OX+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,350,200,NULL,NULL,\ hInst,NULL mov hwnd,eax .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg

invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL pt:POINT .if uMsg==WM_CREATE invoke CreatePopupMenu mov hPopupMenu,eax invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString .elseif uMsg==WM_DESTROY invoke DestroyMenu,hPopupMenu invoke PostQuitMessage,NULL .elseif uMsg==WM_SIZE .if wParam==SIZE_MINIMIZED mov note.cbSize,sizeof NOTIFYICONDATA push hWnd pop note.hwnd mov note.uID,IDI_TRAY mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP mov note.uCallbackMessage,WM_SHELLNOTIFY invoke LoadIcon,NULL,IDI_WINLOGO mov note.hIcon,eax invoke lstrcpy,addr note.szTip,addr AppName invoke ShowWindow,hWnd,SW_HIDE invoke Shell_NotifyIcon,NIM_ADD,addr note .endif .elseif uMsg==WM_COMMAND .if lParam==0 invoke Shell_NotifyIcon,NIM_DELETE,addr note mov eax,wParam .if ax==IDM_RESTORE invoke ShowWindow,hWnd,SW_RESTORE .else invoke DestroyWindow,hWnd .endif .endif .elseif uMsg==WM_SHELLNOTIFY .if wParam==IDI_TRAY .if lParam==WM_RBUTTONDOWN invoke GetCursorPos,addr pt invoke SetForegroundWindow,hWnd invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL invoke PostMessage,hWnd,WM_NULL,0,0 .elseif lParam==WM_LBUTTONDBLCLK invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0 .endif .endif .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp

end start

Anlisis: El programa mostrar una simple ventana. Cuando aprietes el botn minimizar, se ocultar y pondr un icono en la bandeja del sistema. Cuando hagas una doble pulsacin sobre el icono, el programa se restablecer y borrara el icono de la bandeja de sistema. Cuando pulses el botn derecho sobre este, se mostrar un men emergente. Puedes elegir si restaurar el programa o salir de l. .if uMsg==WM_CREATE invoke CreatePopupMenu mov hPopupMenu,eax invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString Cuando se crea la ventana principal, crea un menu emergente y le aade dos elementos. AppendMenu tiene la siguiente sintaxis:

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD

hMenu es el manejador (handle) del men al que quieres aadir el elemento uFlags le dice a Windows sobre el elemento de men a aadir al men ,tanto si es un bitmap o una cadena o un elemento que se auto dibuja, activado, difuminado o desactivado etc. Puedes conseguir la lista completa en la referencia de la api de win32. En nuestro ejemplo, usamos MF_STRING, lo que significa que el elemento del men es una cadena. uIDNewItem es el ID (identidad) del menu item. Este es un valor definido por el usuario que es usado para representar el elemento del menu. lpNewItem especifica el contenido del elemento, dependiendo de lo que especifiques en el miembro uFlags. Como nosotros hemos especificado MF_STRING en uFlags, lpNewItem deber contener el puntero a la cadena a mostrar en el men emergente.

Despus de que es creado el men emergente, la ventana principal espera pacientemente a que el usuario pulse el botn de minimizar. Cuando la ventana es minimizada, recibe el mensage WM_SIZE con el valor SIZE_MINIMIZED en wParam. .elseif uMsg==WM_SIZE .if wParam==SIZE_MINIMIZED mov note.cbSize,sizeof NOTIFYICONDATA push hWnd pop note.hwnd mov note.uID,IDI_TRAY mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP mov note.uCallbackMessage,WM_SHELLNOTIFY invoke LoadIcon,NULL,IDI_WINLOGO mov note.hIcon,eax invoke lstrcpy,addr note.szTip,addr AppName invoke ShowWindow,hWnd,SW_HIDE invoke Shell_NotifyIcon,NIM_ADD,addr note .endif

Aprovechamos esta oportunidad para rellenar la estructura NOTIFYICONDATA. IDI_TRAY es una constante definida al principio del cdigo fuente. Puedes ponerlo al valor que quieras. Esto no importa porque tienes un nico icoo de bandeja. Pero si vas a poner varios iconos en la bandeja del sistema necesitars un ID nico para cada icono de bandeja. Especificamos todas las banderas en el miembro uFlags porque especificamos un icono (NIF_ICON), especificamos un mensaje comn (NIF_MESSAGE) y especificamos el texto tooltip (NIF_TIP). WM_SHELLNOTIFY es un mensaje comn definido como WM_USER+5. El valor actual no es importante siempre que sea nico. Yo uso aqu el icono winlogo como icono de bandeja pero puedes usar cualquier icono en tu programa. Crgalo desde el recurso con LoadIcon y pon el manejador (handle) devuelto en el miembro hIcon. Finalmente, rellenamos el szTip con el texto que queremos que muestre el shell cuando el ratn est sobre el icono. Ocultamos la ventana padre para dar la ilusin de parecer minimizar al icono de bandeja. Despus podemos llamar a Shell_NotifyIcon con el mensage NIM_ADD para aadir el icono a la barra del sistema. Ahora nuestra ventana principal est oculta y el icono est en la banedja del sistema. Si mueves el ratn sobre l vers el tooltip que muestra el texto que ponemos dentro del miembro szTip. Despus, si haces una pulsacin doble en el icono, la ventana principal reaparecer y el icono de bandeja se ir. .elseif uMsg==WM_SHELLNOTIFY .if wParam==IDI_TRAY .if lParam==WM_RBUTTONDOWN invoke GetCursorPos,addr pt invoke SetForegroundWindow,hWnd invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL invoke PostMessage,hWnd,WM_NULL,0,0 .elseif lParam==WM_LBUTTONDBLCLK invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0 .endif .endif Cuando ocurre algn evento de ratn sobre el icono de bandeja, tu ventana recibe el mensaje WM_SHELLNOTIFY que es el mensage comn que especificas en el miembro uCallbackMessage. Vuelve a llamar a esta recibiendo el mensaje, wParam contiene la ID del icono de bandeja y lParam contiene el mensaje de ratn actual. En el cdigo de arriba, primero chequeamos si este mensaje viene del icono de bandeja que nos interesa. Si esto ocurre, chequeamos el mensaje del ratn. Como estamos interesados nicamente en la pulsacin derecha o en la doble pulsacin izquierda, procesamos slo los mensages WM_RBUTTONDOWN y WM_LBUTTONDBLCLK. Si el mensaje de ratn es WM_RBUTTONDOWN llamamos a GetCursorPos para obtener las coordenadas actuales en la pantalla del ratn. Cuando la funcin vuelve, la estructura POINT es rellenada con las coordenadas en pantalla del ratn. Port coordenada de pantalla, quiero decir la coordenada de toda la pantalla sin considerar a ninguno de los bordes de ninguna ventana. Por ejemplo, si la resolucin de la pantalla es 640*480, la esquina inferior derecha de la pantalla es x==639 e y==479. Si quieres convertir la coordenada de pantalla a coordenada de ventana usa la funcin ScreenToClient. Como siempre, para nuestro propsitos, queremos mostrar el men emergente en la posicin actual del ratn con la llamada a TrackPopupMenu y esto requiere coordenadas de pantalla, podemos usar las coordenadas rellenadas directamente en GetCursorPos. TrackPopupMenu tiene la siguiente sintaxis:

TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD

hMenu es el manejador (handle) del men emergente a mostrar uFlags especifica las opciones de la funcion. Dice donde posicionar el men relativo a las coordenadas especificadas despus y qu botn del ratn ser usado para sacar el men. En nuestro ejemplo usamos TPM_RIGHTALIGN para posicionar el men emergente a la izquierda de las coordenadas. x e y especifican la localizacin del men en las coordenadas de pantalla. nReserved deber ser NULL hWnd es el manejador (handle) de la ventana que recibir los mensajes desde el men. prcRect es el rectngulo en la pantalla donde es posible pulsar sin desconsiderar el men. Normalmente ponemos NULL aqu, de manera que cuando el usuario pulse en cualquier sitio fuera del men emergente, el men no es desconsiderado.

Cuando el usuario hace una doble pulsacin sobre el icono de la bandeja mandamos el mensaje WM_COMMAND a nuestra ventana especificando IDM_RESTORE para emular la seleccin del usuario del elemento Restore [Restaurar] del men emergente, restaurando la ventana principal y borrando el icono de la bandeja del sistema. Para poder estar preparados para recibir el mensaje de doble pulsacin, la ventana principal deber tener el estilo CS_DBLCLKS. invoke Shell_NotifyIcon,NIM_DELETE,addr note mov eax,wParam .if ax==IDM_RESTORE invoke ShowWindow,hWnd,SW_RESTORE .else invoke DestroyWindow,hWnd .endif Cuando el usuario selecciona el elemento del men Restore, borramos el icono de la bandeja llamando otra vez a Shell_NotifyIcon, ahora especificamos como mensaje NIM_DELETE. Lo siguiente es restaurar la ventana principal a su estado original. Si el usuario selecciona Exit en el menu, tambin borramos el icono de la barra y destruimos la ventana principal llamando a DestroyWindow.

Tutorial 24: Ganchos de Windows


En este tutorial aprenderemos sobre los ganchos [hooks] en Windows. Los ganchos de Windows son muy poderosos. Con ellos, puedes "pescar" [pook] dentro de otros procesos e incluso modificar su conducta. Bajar el ejemplo aqu.

Teora:
Los ganchos de Windows pueden considerarse uno de los rasgos ms poderosos de Windows. Con ellos podemos atrapar eventos que ocurrirn en tus procesos o en otros remotos. A travs del "enganche" [hooking], dices cosas a Windows sobre una funcin, una funcin filtro llamada tambin procedimiento del gancho, que ser llamado cada vez que ocurra un evento que te interese. Hay dos tipos de ellos: ganchos locales y ganchos remotos. Ganchos Locales atraparn eventos que ocurrirn en tus propio proceso.

Ganchos Remotos atraparn eventos que ocurrirn en otro(s) proceso(s). Hay dos tipos de ganchos remotos o especficos-a-un-hilo [thread-specific] atrapan eventos que ocurren en un hilo especfico de otro proceso. En pocas palabras, quieres observar un hilo especfico en un proceso especfico. o ancho-de-sistema [system-wide] atrapa todos los eventos de todos los hilos en todos los procesos en el sistema.

Cuando instales ganchos, recuerda que ellos afectan el sistema de desempeo. Los ganchos de ancho de sistema [system-wide] son los ms notorios en este aspecto. Como TODOS los eventos relacionados sern dirigidos a travs de tu funcin filtro, tu sistema puede ralentizarse un poco. As que si quieres usar un gancho de ancho de sistema, deberas usarlo juiciosamente y desactivarlo tan pronto ya no lo necesites. Tambin tienes una probabilidad ms alta de quebrar [crashing] los otros procesos, ya que puedes mediar [meddle] con otros procesos y si algo va mal en tu funcin filtro, se pueden derribar los otros procesos y lanzarlos al olvido. Recuerda: el poder viene con responsabilidades. Tienes que entender como trabaja un gancho antes de que puedas usarlo con eficiencia. Cuando creas un gancho, Windows crea una estructura de datos en la memoria, que contiene informacin sobre el gancho, y lo agrega a una lista enlazada de ganchos existentes. El gancho nuevo es agregado en frente de los ganchos antiguos. Cuando un evento ocurre, si instalas un gancho local, la funcin filtro en tu proceso es llamada de una manera directa. Pero si es un gancho remoto, el sistema debe inyectar el cdigo para el procedimiento del gancho dentro del espacio de direcciones del(os) otro(s) proceso(s). Y el sistema puede hacer eso slo si la funcin reside en una DLL. De esta manera, si quieres usar un gancho remoto, tu procedimiento de gancho debe residir en una DLL. Hay dos excepciones a esta regla: ganchos de grabacin diaria [journal record] y ganchos de ejecucin diaria [journal playback]. El procedimiento de gancho para estos dos ganchos debe residir en el hilo que instala los ganchos. La razn por la que debe ser as, es porque ambos ganchos tienen que ver con la intercepcin de bajo-nivel de los eventos de entrada del hardware. Los eventos de entrada deben ser grabados/ejecutados [recorded/playbacked] en el orden que aparecen. Si el cdigo de estos dos ganchos est en un DLL, los eventos de entrada pueden dispersarse entre varios hilos y es imposible saber su orden. As que la solucin es: el procedimiento de gancho de esos dos gancho deben estar solamente en un hilo, en el hilo que instala los ganchos. Hay 14 tipos de ganchos: WH_CALLWNDPROC llamado cuando SendMessage es llamado WH_CALLWNDPROCRET llamado cuando regresa el mensaje SendMessage WH_GETMESSAGE llamado cuando GetMessage o PeekMessage son llamados WH_KEYBOARD llamado cuando GetMessage o PeekMessage regresan WM_KEYUP o WM_KEYDOWN desde la cola de mensajes WH_MOUSE llamado cuando GetMessage o PeekMessage regresan un mensaje de ratn desde la cola de mensajes WH_HARDWARE llamado cuando GetMessage o PeekMessage regresa algn mensaje hardware que no est relacionado con el teclado ni con el ratn. WH_MSGFILTER llamado cuando una caja de dilogo, el men o la barra de pergamino [scrollbar] est apunto de [is about] procesar un mensaje. Este gancho es local. Se usa especficamente para esos objetos que tienen sus propios bucles de mensajes internos. WH_SYSMSGFILTER lo mismo que WH_MSGFILTER pero de ancho de sistema [system-wide] WH_JOURNALRECORD llamado cuando Windows regresa mensajes desde hardware input queue WH_JOURNALPLAYBACK llamado cuando es solicitado un evento del hardware desde la cola de entrada del sistema. WH_SHELL llamado cuando algo interesante sobre el shell ocurre, tal como cuando la barra de tareas necesita redibujar su botn.

WH_CBT usado especficamente para entrenamiento [computer-based training (CBT)]. WH_FOREGROUNDIDLE usado internamente por Windows. Poco usada para aplicaciones generales WH_DEBUG usado para depurar el procedimiento de enganche

Ahora que sabemos algo de teora, podemos ver ahora cmo instalar/desinstalar los ganchos. Para instalar un gancho, llamas a SetWindowsHookEx que tiene la siguiente sintaxis: SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD HookType es uno de los valores en la lista de arriba, e.g., WH_MOUSE, WH_KEYBOARD pHookProc es la direccin del procedimiento de gancho que ser llamada para procesar los mensajes para el gancho especfico. Si el gancho es remoto, debe residir en una DLL. Si no, debe estar en tu proceso. hInstance es el manejador de instacia de la DLL en la cual reside el procedimiento de gancho. Si el gancho es local, este valor debe ser NULL ThreadID es el ID del hilo para el cual quieres instalar el gancho que lo espe. Este parmetro es el que determina si el gancho es local o remoto. Si este parmetro es NULL, Windows interpretar el gancho como un gancho remoto de ancho de sistema [system-wide] que afecta todos los hilos del sistema. Si quieres especificar el ID de un hilo en tu propio proceso, este hilo es local. Si especificas el ID del hilo de otro proceso, el gancho es thread-specific remote one. Hay dos excepciones a esta regla: WH_JOURNALRECORD y WH_JOURNALPLAYBACK siempre son ganchos de ancho de sistema [system-wide] locales que no se requieren que estn en una DLL. Y WH_SYSMSGFILTER siempre es un gancho remoto de ancho de sistema [systemwide]. Realmente es idntica al gancho WH_MSGFILTER con ThreadID==0.

Si la llamada tiene xito, regresa el manejador de gancho en eax. Si no, regresa NULL. Debes salvar el manejador del gancho para poder desinstalarlo despus. Puedes desinstalar un gancho llamando a UnhookWindowsHookEx que slo acepta un parmetro, el manejador del gancho que quieres desinstalar. Si la llamada tiene xtito, regresa un valor diferente a cero en eax. En caso contrario, regresa NULL. Ahora que sabes cmo instalar/desinstalar ganchos, podemos examinar el procedimiento de gancho. El procedimiento de gancho ser llamado cada vez que ocurre un evento asociado con el tipo de gancho que haz instalado. Por ejemplo, si instalas el gancho WH_MOUSE, cuando ocurre un evento de ratn, ser llamado tu procedimiento de gancho. Independientemente del tipo de gancho que instalaste, el procedimiento de gancho siempre tiene el siguiente prototipo: HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD

o o

nCode especifica el cdigo del gancho. wParam y lParam contienen informacin adicional sobre el evento

HookProc es realmente un comodn [placeholder] para el nombre de la funcin. Puedes llamarla de la forma que quieras siempre que conserves el prototipo de arriba. La interpretacin de nCode, wParam y lParam depende del tipo de gancho que instales, as como del valor de retorno del procedimiento de gancho. Por ejemplo: WH_CALLWNDPROC

nCode slo puede ser HC_ACTION lo cual significa que hay un mensaje enviado a la ventana wParam contiene el mensaje que est siendo enviado, si no lo tiene es cero lParam apunta a una estructura CWPSTRUCT return value: no se usa, regresa cero

WH_MOUSE nCode puede ser HC_ACTION o HC_NOREMOVE wParam contiene el mensaje del ratn lParam apunta a una estructura MOUSEHOOKSTRUCT return value: cero si el mensaje debera ser procesado. 1 si el mensaje deberia ser descartado.

La lnea de abajo es: debes consultar tu referencia de la api de win32 para detalles sobre los significados de los parmetros y regresar un valor al gancho que quieres instalar. Ahora hay un poco de catch sobre el procedimiento de gancho. Recuerda que los ganchos estan encadenados en una lista enlazada con el gancho instalado ms recientemente colocado en la cabeza de la lista. Cuando ocurre un evento, Windows llamar slo al primer gancho de la cadena. Es responsabilidad de tu procedimiento de gancho llamar al siguiente gancho en la cadena. T no eliges llamar al siguiente gancho, pero mejor deberas saber qu ests haciendo. Muchas veces, es una buena prctica llamar al siguiente procedimiento de manera que otros ganchos puedan tener una impresin [shot] del evento. Puedes invocar al siguiente gancho llamando a CallNextHookEx que tiene el siguiente prototipo: CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD hHook es tu propio manejador de gancho. La funcin usa este manejador para atravesar la lista enlazada y buscar el siguiente procedimiento de gancho que debera ser llamado. nCode, wParam y lParam puedes pasar estos tres valores que recibes de Windows a CallNextHookEx.

Una nota importante sobre los ganchos remotos: el procedimiento de gancho debe residir en una DLL que ser proyectada dentro de otro proceso. Cuando Windows proyecta la DLL dentro de otros procesos, no proyectar la(s) seccin(es) de datos(s) dentro de otros procesos. En pocas palabras, todos los procesos comparten una copia sencilla del cdigo, pero ellos tendrn su propia copia privada de la seccin de datos de la DLL! Esto puede resultar una gran sorpresa para el incuto. Puedes pensar que cuando almacenas un valor dentro de una variable en la seccin de datos de una DLL, ese valor ser compartido entre todos los procesos que cargan la DLL dentro de su espacio de direcciones. No es tan cierto. En una situacin normal, esta conducta es deseable ya que provee la ilusin de que cada proceso tiene su propia copia de la DLL. Pero no cuando hay involucrados ganchos de Windows. Queremos que la DLL sea idntica en todos los procesos, incluyendo los datos. La solucin: debes marcar la seccin de datos como compartida. Puedes hacer esto especificando el atributo de la(s) seccin(es) en el conmutador [switch] del enlazador [linker]. Para MASM, necesitas usar este conmutador: /SECTION:<section name>, S El nombre de la seccin de datos inicializada es .data y el de la de los datos no-inicializados es .bss. Por ejemplo, si quieres ensamblar una DLL que contiene un procedimiento de gancho y quieres que la seccin de datos no inicialoizados sea compartida entre procesos, debes usar la siguiente lnea: link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........

El atributo S marca la seccin como compartida.

Ejemplo:
Hay dos mdulos: uno es el programa principal que har la parte de GUI y otra es la DLL que instalar/desinstalar el gancho. ;--------------------------------------------- Este es el cdigo fuente del programa principal ----------------------------------.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include mousehook.inc includelib mousehook.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib wsprintfA proto C :DWORD,:DWORD,:VARARG wsprintf TEXTEQU <wsprintfA> .const IDD_MAINDLG IDC_CLASSNAME IDC_HANDLE IDC_WNDPROC IDC_HOOK IDC_EXIT WM_MOUSEHOOK

equ 101 equ 1000 equ 1001 equ 1002 equ 1004 equ 1005 equ WM_USER+6

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD .data HookFlag dd FALSE HookText db "&Hook",0 UnhookText db "&Unhook",0 template db "%lx",0 .data? hInstance dd ? hHook dd ? .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL invoke ExitProcess,NULL DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD LOCAL hLib:DWORD LOCAL buffer[128]:byte LOCAL buffer1[128]:byte LOCAL rect:RECT .if uMsg==WM_CLOSE .if HookFlag==TRUE invoke UninstallHook

.endif invoke EndDialog,hDlg,NULL .elseif uMsg==WM_INITDIALOG invoke GetWindowRect,hDlg,addr rect invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW .elseif uMsg==WM_MOUSEHOOK invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128 invoke wsprintf,addr buffer,addr template,wParam invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer .endif invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128 invoke GetClassName,wParam,addr buffer,128 invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer .endif invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128 invoke GetClassLong,wParam,GCL_WNDPROC invoke wsprintf,addr buffer,addr template,eax invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer .endif .elseif uMsg==WM_COMMAND .if lParam!=0 mov eax,wParam mov edx,eax shr edx,16 .if dx==BN_CLICKED .if ax==IDC_EXIT invoke SendMessage,hDlg,WM_CLOSE,0,0 .else .if HookFlag==FALSE invoke InstallHook,hDlg .if eax!=NULL mov HookFlag,TRUE invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText .endif .else invoke UninstallHook invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText mov HookFlag,FALSE invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL .endif .endif .endif .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgFunc endp

end start ;----------------------------------------------------- Este es el cdigo fuente de la DLL -------------------------------------.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib include \masm32\include\user32.inc includelib \masm32\lib\user32.lib .const WM_MOUSEHOOK equ WM_USER+6 .data hInstance dd 0 .data? hHook dd ? hWnd dd ? .code DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD .if reason==DLL_PROCESS_ATTACH push hInst pop hInstance .endif mov eax,TRUE ret DllEntry Endp MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD invoke CallNextHookEx,hHook,nCode,wParam,lParam mov edx,lParam assume edx:PTR MOUSEHOOKSTRUCT invoke WindowFromPoint,[edx].pt.x,[edx].pt.y invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0 assume edx:nothing xor eax,eax ret MouseProc endp InstallHook proc hwnd:DWORD push hwnd pop hWnd invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL mov hHook,eax ret InstallHook endp UninstallHook proc invoke UnhookWindowsHookEx,hHook ret UninstallHook endp End DllEntry

;---------------------------------------------- Est es el makefile de la DLL ---------------------------------------------NAME=mousehook $(NAME).dll: $(NAME).obj Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj $(NAME).obj: $(NAME).asm ml /c /coff /Cp $(NAME).asm

Anlisis:
El ejemplo desplegar una caja de dilogo [dialog box] con tres controles de edicin que sern llenados con el nombre de la clase, el manejador [handle] de ventana y la direccin del procedimiento de ventana asociada con la ventana bajo el cursor del ratn. Hay dos botones, Hook (gancho) y Exit (Salir). Cuando presionas el botn Hook, el programa engancha la entrada del ratn y el texto en el botn cambia a Unhook (desenganchar). Cuando mueves el cursor del ratn sobre la ventana, la info acerca de esa ventana ser desplegada en la ventana principal del ejemplo. Cuando presionas el botn Unhook, el programa remueve el ganchjo del ratn. El programa principal usa una caja de dilogo [dialog box] como su ventana principal. Define un mensaje hecho a la medida [custom message], WM_MOUSEHOOK que ser usado entre el programa principal y la DLL de gancho. Cuando el programa principal recibe este mensaje, wParam contiene el manejador de la ventana sobre la cual est el cursor del ratn. Por supuesto, este es un plan arbitrario. Yo decido enviar un manejador en wParam por razones de simplicidad. T puedes escoger tu propio mtodo de comunicacin entre la ventana principal y la DLL de gancho. .if HookFlag==FALSE invoke InstallHook,hDlg .if eax!=NULL mov HookFlag,TRUE invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText .endif El programa mantiene una bandera, HookFlag, para monitorear el estado del gancho. Es FALSE si no se instala el gancho y TRUE si el gancho es instalado. Cuando el usuario presiona el botn Hook, el programa chequea si el gancho ya est instalado. Si no lo est, se llama a la funcin InstallHook en la DLL de gancho para instalarlo. Nota que pasamos el manejador de la ventana principal como parmetro de la funcin de manera que la DLL de gancho pueda enviar mensajes WM_MOUSEHOOK a la ventana correcta,es decir la tuya propia. Cuando el programa es cargado, la DLL de gancho tabmbin es cargada. Realmente, las DLLs son cargadas inmediatamente despus de que el programa est en memoria. El punto de entrada de la DLL es llamado incluso antes de que se ejecute la primera instruccin del programa principal. As que cuando el programa principal ejecuta la(s) DLL(s) es/son inicializada(s). Ponemos el siguiente cdigo en el punto de entrada de la DLL de gancho: .if reason==DLL_PROCESS_ATTACH push hInst pop hInstance .endif

El cdigo salva el manejador de instancia de la DLL de gancho misma a una variable global llamada hInstance para usar dentro de la funcin InstallHook. Ya que la funcin del punto de entrada de la DLL es llamada antes de que sean llamadas otras funciones de la DLL, hInstance siempre es vlido. Ponemos hInstance en la seccin .data, as que este valor es guardado en la base de la seccin por proceso [is kept on per-process basis]. Debido a que cuando el cursor del ratn pasa sobre una ventana, la DLL de gancho es proyectada en el proceso. Imagina que ya hay una DLL que ocupa las direcciones de la DLL de gancho que se intent cargar, la DLL de gancho debera ser re-proyectada a otra direccin. El valor de hInstance ser actualizado para las de las nuevas direcciones cargadas. Cuando el usuario presiona el botn Unhook y luego el botn Hook, SetWindowsHookEx ser llamada de nuevo. Sin embargo, esta vez, se usar el nuevo espacio de direcciones cargado como el manejador de instacia lo cual ser errneo porque en este proceso ejemplo la direccin de carga de la DLL de gancho no ha sido cambiada. El gancho ser local donde puedes enganchar slo los eventos de ratn que ocurren en tu propia ventana. Difcilmente deseable [hardly desirable]. InstallHook proc hwnd:DWORD push hwnd pop hWnd invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL mov hHook,eax ret InstallHook endp La funcin InstallHook es muy simple. Salva el manejador de ventana pasado como su parmetro a una variable global llamada hWnd para ser usada luego. Luego llama a SetWindowsHookEx para instalar un gancho de ratn. El valor de retorno de SetWindowsHookEx es almacenado en una variable global llamada hHook para usar con UnhookWindowsHookEx. Despus de que es llamada SetWindowsHookEx, el gancho del ratn es funcional. Cada vez que ocurre un evento de ratn del sistema, es llamada MouseProc (tu procedimiento de ventana). MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD invoke CallNextHookEx,hHook,nCode,wParam,lParam mov edx,lParam assume edx:PTR MOUSEHOOKSTRUCT invoke WindowFromPoint,[edx].pt.x,[edx].pt.y invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0 assume edx:nothing xor eax,eax ret MouseProc endp Lo primero que hace es llamar a CallNextHookEx para dar a otros ganchos el chance de procesar el evento del ratn. Despus de eso, llma a la funcin WindowFromPoint para regresar el manejador de la ventana en la coordenada especificada del monitor. Nota que usamos la estructura POINT en la estructura MOUSEHOOKSTRUCT apuntada por lParam como la coordenada actual del ratn. Despus de que enviamos el manejador de ventana a la ventana principal a travs de PostMessage con el mensaje WM_MOUSEHOOK. Algo que deberas recordar es que: no deberas usar SendMessage dentro del procedimiento de gancho, ya que puede causar estancamiento de mensajes. Es ms recomendable PostMessage. La estructura MOUSEHOOKSTRUCT se define abajo: MOUSEHOOKSTRUCT STRUCT DWORD pt POINT <> hwnd DWORD ? wHitTestCode DWORD ? dwExtraInfo DWORD ?

MOUSEHOOKSTRUCT ENDS

pt es la coordenada del monitor actual dnde est el cursor del ratn hwnd es el manejador de la ventana que recibir el mensaje del ratn. Usualmente es la ventana debajo del cursor del ratn pero no siempre. Si una ventana llama a SetCapture, la entrada del ratn ser desviada ahora ms bien a la ventana. Debido a esta razn, no uso el miembro hwnd de esta estructura sino que ms bien elijo llamara a WindowFromPoint. wHitTestCode especifica la prueba del valor hit-test. Este valor da ms informacin sobre la posicin actual del cursor del ratn. Especifica en qu parte de la ventana est el cursor del ratn. Para una lista completa, chequea en tu referencia de la api de win32 el tpico sobre el mensaje WM_NCHITTEST. dwExtraInfo contiene informacin extra asociada con el mensaje. Normalmente, este valor se establece llamando a mouse_event y regresado por la llamada a GetMessageExtraInfo.

Cuando la ventana principal recibe un mensaje WM_MOUSEHOOK, usa el manejador de ventana en wParam para regresar la informacin acerca de la ventana. .elseif uMsg==WM_MOUSEHOOK invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128 invoke wsprintf,addr buffer,addr template,wParam invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer .endif invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128 invoke GetClassName,wParam,addr buffer,128 invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer .endif invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128 invoke GetClassLong,wParam,GCL_WNDPROC invoke wsprintf,addr buffer,addr template,eax invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer .endif Para evitar parpadeos [flickers], chequeamos el texto que est todava en los controles de edicin y el texto que se pondr dentro de ellos para comprobar si on idnticos. Si lo son, los saltamos. Regresamos el nombre de la clase llamando a GetClassName, la direccin del procedimiento de ventana llamando a GetClassLong con GCL_WNDPROC y luego los formateamos dentro de cadenas y los ponemos dentro de los controles de edicin apropiados. invoke UninstallHook invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText mov HookFlag,FALSE invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL Cuando el usuario presiona el botn Unhook, el programa llama a la funcin UninstallHook en la DLL de gancho. UninstallHook llama a UnhookWindowsHookEx. Despus de eso, cambia el texto del botn una vez ms a "Hook", HookFlag a FALSE y se limpia el contenido de los

controles de edicin. Nota que el conmutador [switch] del enlazador en el makefile. Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS Especifica a la seccin .bss como una seccin compartida para hacer que todos los procesos compartan la seccin de datos no inicializados de la DLL de gancho. Sin este conmutador [switch], tu DLL de gancho no funcionar correctamente.

Tutorial 25: Simple Bitmap


En este tutorial, aprenderemos como usar bitmaps en nuestros programas. Para ser exactos, Aprenderemos a desplegar in bitmap en el rea cliente de una ventana. Baja el ejemplo.

Teora
Los bitmaps pueden ser vistos como pinturas almacenadas en la computadora. Hay muchos formatos de pinturas usados con los computadores pero Windows slo soporta como nativos los archivos Grficos de Bitmap de Windows (.bmp). Los bitmaps a que me referir en este tutorial son archivos grficos de Windows. La manera ms fcil de usar un bitmap es emplearlo como recurso. Hay dos maneras de hacer eso. Puedes incluir el bitmap en el archivo de definicin del recurso (.rc) de la siguiente manera:

#define IDB_MYBITMAP 100 IDB_MYBITMAP BITMAP "c:\project\example.bmp"


Este mtodo usa una constante para representar el bitmap. La primera lnea crea una constante llamada IDB_MYBITMAP que tiene el valor de 100. Usaremos esta etiqueta para referirnos al bitmap en el programa. La siguiente lnea declara un recurso bitmap. Dice al compilador de recursos dnde encontrar el actual archivo bmp. El otro mtodo usa un nombre para representar el bitmap de la siguiente manera:

MyBitMap BITMAP "c:\project\example.bmp"


Este mtodo requiere que te refieras al bitmap en tu programa usando la cadena "MyBitMap" en vez de un valor. Cualquiera de estos mtodos trabaja bien siempre que sepas cul ests usando. Ahora que ponemos el bitmap en el archivo de recurso, podemos continuar con los pasos a seguir para desplegarlo en el rea cliente de nuestra ventana.

1. Llamar a LoadBitmap para obtener el manejador [handle] al bitmap. LoadBitmap tiene


la siguiente definicin:

LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR


Esta funcin regresa un manejador al bitmap. hInstance es el manejador de instancia de nuestro programa. lpBitmapName es un puntero a la cadena con el nombre del bitmap (en caso de que uses el segundo mtodo para referirte al bitmap). Si usas una

constante para referirte al bitmap (como IDB_MYBITMAP), puedes poner este valor aqu. (En el ejemplo de arriba sera 100). Un pequeo ejemplo, en orden:

Primer Mtodo: .386 .model flat, stdcall ................ .const IDB_MYBITMAP equ 100 ............... .data? hInstance dd ? .............. .code ............. invoke GetModuleHandle,NULL mov hInstance,eax ............ invoke LoadBitmap,hInstance,IDB_MYBITMAP ........... Segundo Mtodo: .386 .model flat, stdcall ................ .data BitmapName db "MyBitMap",0 ............... .data? hInstance dd ? .............. .code ............. invoke GetModuleHandle,NULL mov hInstance,eax ............ invoke LoadBitmap,hInstance,addr BitmapName ...........

1. Obtener un manejador al contexto del dispositivo (DC). Puedes obtener este manejador 2.
llamando a BeginPaint en respuesta al mensaje WM_PAINT o llamando a GetDC en algn lado. Crear un contexto de dispositivo de memoria que tenga el mismo atributo que el contexto de dispositivo que obtuvimos. La idea aqu es crear un tipo de superficie oculta para dibujo [hidden drawing surface] sobre la cual podamos dibujar el bitmap. Cuando terminamos con al operacin, copiamos el contenido de la hidden drawing surface al contexto de dispositivo actual en una llamada a funcin. Es un ejemplo de tcnica de doble-buffer usada para desplegar con rapidez imgenes sobre la pantalla. Puedes crear esta superficie oculta para dibujo [hidden drawing surface] llamando a CreateCompatibleDC.

CreateCompatibleDC proto hdc:HDC


Si esta funcin tiene xito, regresa el manejador del contexto de dispositivo de

memoria en eax. hdc es el manejador al contexto de dispositivo con que quieres que el DC de memoria sea compatible.

1. Ahora que obtuviste una superficie oculta para dibujo, puedes dibujar sobre ella
seleccionando el bitmap dentro de ella. Esto se hace llamando a SelectObject con el manejador al DC de memoria como primer parmetro y el manejador al bitmap como segundo parmetro. SelectObject tiene la siguiente definicin: SelectObject proto hdc:HDC, hGdiObject:DWORD

1. El bitmap ahora es dibujado sobre el contexto de dispositivo de memoria. Todo lo que


necesitamos hacer aqu es copiarlo al dispositivo de despliegue actual, realnmente el verdadero contexto de dispositivo. Hay varias funciones que pueden realizar esta operacin tales como BitBlt y StretchBlt. BitBlt copia el contenido de un DC a otro de manera que es ms rpido mientras que StretchBlt pueda estrechra o comprimir el bitmap para fijar el rea de salida. Usaremos aqu BitBlt por simplicidad. BitBlt tiene la siguiente definicin: BitBlt proto hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD, hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD

hdcDest es el manejador del contexto de dispositivo que sirve como destino de la operacin de transferencia del bitmap nxDest, nyDest son las coordenadas de la esquina superior izquierda del rea de salida nWidth, nHeight son el ancho y la altura del rea de salida hdcSrc es el manejador del contexto de dispositivo que sirve como fuente de la operacin de transferencia del bitmap nxSrc, nySrc son las coordenadas de la esquina superior izquierda del rectngulo de origen. dwROP es el cdigo de la operacin-raster (de ah las siglas ROP) que gobierna cmo combinar los datos de los colores del bitmap con los datos de los colores existentes en el rea de salida para alcanzar el resultado final. Muchas veces, slo quieres sobreescribir los datos de colores existentes con los nuevos.

1. Cuando ya hayas hecho lo que ibas a hacer con el bitmap, suprmelo con una llamada
a la API DeleteObject. Eso es todo! Para recapitular, necesitas poner el bitmap dentro del guin de recursos. Luego crgalo desde el recurso con LoadBitmap. Obtendrs un manejador al bitmap. Luego obtienes el manejador al contexto de dispositivo del rea sobre el cual quieres pintar el bitmap. Luego creas un contexto de dispositivo de memoria compatible con el contexto de dispositivo que obtuviste. Selecciona el bitmap dentro del DC de memoria y luego copia el contenido de la memoria del DC al verdadero DC.

Cdigo de Ejemplo:
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\user32.lib

includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD IDB_MAIN equ 1 .data ClassName db "SimpleWin32ASMBitmapClass",0 AppName db "Win32ASM Simple Bitmap Example",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? hBitmap dd ? .code start: invoke mov invoke mov invoke invoke

GetModuleHandle, NULL hInstance,eax GetCommandLine CommandLine,eax WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 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,SW_SHOWNORMAL invoke UpdateWindow, hwnd .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam

ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL ps:PAINTSTRUCT LOCAL hdc:HDC LOCAL hMemDC:HDC LOCAL rect:RECT .if uMsg==WM_CREATE invoke LoadBitmap,hInstance,IDB_MAIN mov hBitmap,eax .elseif uMsg==WM_PAINT invoke BeginPaint,hWnd,addr ps mov hdc,eax invoke CreateCompatibleDC,hdc mov hMemDC,eax invoke SelectObject,hMemDC,hBitmap invoke GetClientRect,hWnd,addr rect invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY invoke DeleteDC,hMemDC invoke EndPaint,hWnd,addr ps .elseif uMsg==WM_DESTROY invoke DeleteObject,hBitmap invoke PostQuitMessage,NULL .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start ;--------------------------------------------------------------------; El guin de recursos ;--------------------------------------------------------------------#define IDB_MAIN 1 IDB_MAIN BITMAP "tweety78.bmp"

Anlisis:
No hay mucho que analizar en este tutorial ;)

#define IDB_MAIN 1 IDB_MAIN BITMAP "tweety78.bmp" Definir una constante llamada IDB_MAIN, asignando 1 como su valor. Y luego usar esa constante como el identificador del recurso del bitmap. El archivo bitmap a ser incluido en el recuros "tweety78.bmp" que reside en la misma carpeta como guin de recursos. .if uMsg==WM_CREATE invoke LoadBitmap,hInstance,IDB_MAIN mov hBitmap,eax En respuesta a WM_CREATE, llamamos a LoadBitmap para cargar al bitmap desde el recurso, pasando el identificador del recurso del bitmap como segundo parmetro a la API. Obtenemos el manejador al bitmap cuando regresa la funcin.

Ahora que el bitmap est cargado, podemos pintarlo en el rea cliente de nuestra ventana principal. .elseif uMsg==WM_PAINT invoke BeginPaint,hWnd,addr ps mov hdc,eax invoke CreateCompatibleDC,hdc mov hMemDC,eax invoke SelectObject,hMemDC,hBitmap invoke GetClientRect,hWnd,addr rect invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY invoke DeleteDC,hMemDC invoke EndPaint,hWnd,addr ps Elegimos pintar el bitmap en respuesta al mensaje WM_PAINT. Primero llamamos a BeginPaint para obtener el manejador al contexto de dispositivo. Luego creamos un DC de memoria compatible con CreateCompatibleDC. Lo siguiente es seleccionar el bitmap dentro del DC de memoria con SelectObject. Determinamos la dimensin del rea cliente con GetClientRect. Ahora podemos desplegar el bitmap en el rea cliente llamando a BitBlt que copia el bitmap desde el DC de memoria al DC real. Cuando la imagen ya est hecha, ya no tenemos necesidad del DC de memoria, as que lo suprimimos con DeleteDC. Terminamos la seccin de pintura con EndPaint. .elseif uMsg==WM_DESTROY invoke DeleteObject,hBitmap invoke PostQuitMessage,NULL Cuando no necesitemos ya el bitmap, lo suprimimos [delete] con DeleteObject

Tutorial 26: Splash Screen


Ahora que sabemos cmo usar un bitmap, podemos progresar hacia un uso ms creativo de l. Splash screen. Baja el ejemplo.

Teora
Una splash screen [pantalla de salpicadura] es una ventana que no tiene barra de ttulo, ni caja de men de sistema, ni borde, que despliega un bitmap por un lapso de tiempo y luego desaparece automticamente. Usualmente es usada durante el inicio de un programa, para desplegar el logo del programa o distraer la atencin del usuario mientras el programa hace alguna inicializacin extendida. En este tutorial implementaremos un splash screen. El primer paso es incluir el bitmap en el archivo de recursos. Sin embargo, si piensas un poco en esto, vers que hay un consumo precioso de memoria cuando se carga un bitmap que ser usado slo una vez y se mantiene en la memoria hasta que el programa es cerrado. Una mejor solucin es crear una DLL de *recursos* que contenga el bitmap y que tenga el nico propsito de desplegar la splash screen. De esta manera, puedes cargar la DLL cuando quieras desplegar la splash screen y descargarla cuando ya no sea necesaria. As que tendremos dos mdulos: El programa principal y la DLL con el splash. Pondremos el bitmap dentro de los recursos de la DLL. El esquema general es como sigue:

1. Poner el bitmap dentro de la DLL como un recurso bitmap 2. El programa principal llama a LoadLibrary para cargar la dll en memoria

3. Se llama a la funcin del punto de entrada de la DLL. Se crear un temporizador y se


establecer cunto tiempo permanecer desplegada la splash screen. Luego se registrar y crear una ventana sin encabezado y sin borde, y desplegar el bitmap en el rea cliente. Cuando se cumple el lapso de tiempo establecido, la splash screen es removida de la pantalla y el control es regresado a la ventana principal El programa principal llama a FreeLibrary para descargar la DLL de la memoria y luego se dirigir a realizar la tarea que se supone que har.

4. 5.

Examinaremos los mecanismos en detalle.

Cargar/Descargar una DLL


Puedes cargar dinmicamente una DLL con la funcin LoadLibrary que tiene la siguiente sintaxis:

LoadLibrary proto lpDLLName:DWORD


Toma slo un parmetro: la direccin del nombre de la DLL que quieres cargar en memoria. Ssi la llamada es satisfactoria, regresa el manejador del mdulo de la DLL sino regresa NULL. Para descargar una DLL, llama a FreeLibrary:

FreeLibrary proto hLib:DWORD


Toma un parmetro: el manejador del mdulo de la DLL que quieras descargar. Normalmente, obtienes el manejador a partir de LoadLibrary

Cmo usar un temporizador [timer]


Primero, debes crear un temporizador con SetTimer: SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD hWnd es el manejador de una ventana que recibir el mensaje de notificacin del temporizador. Este parmetro puede ser NULL para especificar que no hay ventana asociada con el temporizador. TimerID es un valor definido por el usuario empleado para el ID del temporizador. uElapse es el valor del lapso de tiempo en milisegundos. lpTimerFunc Es la direccin de una funcin que procesar los mensajes de notificacin del temporizador. Si pasas NULL, los mensajes del temporizador sern enviados a la ventana especificada por el parmetro hWnd. SetTimer regresa el ID del temporizador si tiene xito. De otra manera regresa NULL. As que es mejor usar el ID del temporizador de 0. Puedes crear un temporizador de dos maneras : Si tienes una ventana y quieres los mensajes de notificacin del temporizador para ir a esa ventana, debes pasar todos los cuatro parmetros a SetTimer (el valor de lpTimerFunc debe ser NULL). Si no quieres una ventana o si no quieres procesar los mensajes del temporizador en el procedimiento de ventana, debes pasar NULL a la funcin en lugar de un manejador de ventana. Debes especificar el valor de la direccin del temporizador que procesar los mensajes del temporizador.

Usaremos la primera aproximacin en este ejemplo. Cuando se cumple el perodo de tiempo, se enva el mensaje WM_TIMER a la ventana asociada con el temporizador. Por ejemplo, si especificas un uElapse de 1000, tu ventana recibir un mensaje WM_TIMER cada segundo. Cuando ya no necesites el temporizador, lo destruyes con KillTimer: KillTimer proto hWnd:DWORD, TimerID:DWORD

Ejemplo:
;---------------------------------------------------------------------; El programa principal ;---------------------------------------------------------------------.386 .model flat,stdcall option casemap:none 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 WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .data ClassName db "SplashDemoWinClass",0 AppName db "Splash Screen Example",0 Libname db "splash.dll",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke LoadLibrary,addr Libname .if eax!=NULL invoke FreeLibrary,eax .endif invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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 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,SW_SHOWNORMAL invoke UpdateWindow, hwnd .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start ;-------------------------------------------------------------------; La DLL Bitmap ;-------------------------------------------------------------------.386 .model flat, stdcall include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib .data BitmapName db "MySplashBMP",0 ClassName db "SplashWndClass",0 hBitMap dd 0 TimerID dd 0 .data hInstance dd ?

.code DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD .if reason==DLL_PROCESS_ATTACH ; When the dll is loaded push hInst pop hInstance call ShowBitMap .endif mov eax,TRUE ret DllEntry Endp ShowBitMap proc LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND 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,0 invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\ WS_POPUP,CW_USEDEFAULT,\ CW_USEDEFAULT,250,250,NULL,NULL,\ hInstance,NULL mov hwnd,eax INVOKE ShowWindow, hwnd,SW_SHOWNORMAL .WHILE TRUE INVOKE GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) INVOKE TranslateMessage, ADDR msg INVOKE DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret ShowBitMap endp WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD LOCAL ps:PAINTSTRUCT LOCAL hdc:HDC LOCAL hMemoryDC:HDC LOCAL hOldBmp:DWORD LOCAL bitmap:BITMAP LOCAL DlgHeight:DWORD LOCAL DlgWidth:DWORD LOCAL DlgRect:RECT LOCAL DesktopRect:RECT .if uMsg==WM_DESTROY .if hBitMap!=0 invoke DeleteObject,hBitMap .endif

invoke PostQuitMessage,NULL .elseif uMsg==WM_CREATE invoke GetWindowRect,hWnd,addr DlgRect invoke GetDesktopWindow mov ecx,eax invoke GetWindowRect,ecx,addr DesktopRect push 0 mov eax,DlgRect.bottom sub eax,DlgRect.top mov DlgHeight,eax push eax mov eax,DlgRect.right sub eax,DlgRect.left mov DlgWidth,eax push eax mov eax,DesktopRect.bottom sub eax,DlgHeight shr eax,1 push eax mov eax,DesktopRect.right sub eax,DlgWidth shr eax,1 push eax push hWnd call MoveWindow invoke LoadBitmap,hInstance,addr BitmapName mov hBitMap,eax invoke SetTimer,hWnd,1,2000,NULL mov TimerID,eax .elseif uMsg==WM_TIMER invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL invoke KillTimer,hWnd,TimerID .elseif uMsg==WM_PAINT invoke BeginPaint,hWnd,addr ps mov hdc,eax invoke CreateCompatibleDC,hdc mov hMemoryDC,eax invoke SelectObject,eax,hBitMap mov hOldBmp,eax invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap invoke StretchBlt,hdc,0,0,250,250,\ hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY invoke SelectObject,hMemoryDC,hOldBmp invoke DeleteDC,hMemoryDC invoke EndPaint,hWnd,addr ps .elseif uMsg==WM_LBUTTONDOWN invoke DestroyWindow,hWnd .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp End DllEntry

Anlisis:

Primero examinaremos el cdigo en la ventana principal. invoke LoadLibrary,addr Libname .if eax!=NULL invoke FreeLibrary,eax .endif Llamamos a LoadLibrary para cargar la DLL llamada "splash.dll". Y despus de eso, descargarla de la memoria con FreeLibrary. LoadLibrary no regresar hasta que la DLL haya terminado con su inicializacin. Eso es todo lo que hace le programa principal. La parte interesante est en la DLL. .if reason==DLL_PROCESS_ATTACH push hInst pop hInstance call ShowBitMap ; Cuando la dll es cargada

Cuando la DLL es cargada, Windows llama a su punto de entrada con la bandera DLL_PROCESS_ATTACH. Aprovechamos esta oportunidad para desplegar la splash screen. Primero almacenamos el manejador de instacia de la DLL para usarla luego. Luego llamamos a una funcin llamada ShowBitMap para realizar la tarea. ShowBitMap registra una clase de ventana, crea una ventana e introduce el bucle de mensaje como es usual. La parte interesante est en la llamada a CreateWindowEx: INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\ WS_POPUP,CW_USEDEFAULT,\ CW_USEDEFAULT,250,250,NULL,NULL,\ hInstance,NULL Nota que el estilo de la ventana es slo WS_POPUP lo cual har que la ventana no tenga bordes ni tampoco encabezamiento [caption]. Tambin limitamos el ancho y la altura de la ventana a 250x250 pixeles. Ahora cuando la ventana es creada durante en el manejador del WM_CREATE, movemos la ventana al centro del monitor con el siguiente cdigo. invoke GetWindowRect,hWnd,addr DlgRect invoke GetDesktopWindow mov ecx,eax invoke GetWindowRect,ecx,addr DesktopRect push 0 mov eax,DlgRect.bottom sub eax,DlgRect.top mov DlgHeight,eax push eax mov eax,DlgRect.right sub eax,DlgRect.left mov DlgWidth,eax push eax mov eax,DesktopRect.bottom sub eax,DlgHeight shr eax,1 push eax mov eax,DesktopRect.right sub eax,DlgWidth shr eax,1 push eax

push hWnd call MoveWindow Regresan las siguientes dimensiones del escritorio y la ventana luego calcula la coordenada apropiada de la esquina izquierda superior de la ventana para convertirse en centro. invoke LoadBitmap,hInstance,addr BitmapName mov hBitMap,eax invoke SetTimer,hWnd,1,2000,NULL mov TimerID,eax Lo siguiente es cargar el bitmap desde el recurso con LoadBitmap y crea un temporizador con el ID de temporizador de 1 y el intervalo de tiempo de 2 segundos. El temporizador enviar mensajes WM_TIMER a la ventana cada 2 segundos. .elseif uMsg==WM_PAINT invoke BeginPaint,hWnd,addr ps mov hdc,eax invoke CreateCompatibleDC,hdc mov hMemoryDC,eax invoke SelectObject,eax,hBitMap mov hOldBmp,eax invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap invoke StretchBlt,hdc,0,0,250,250,\ hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY invoke SelectObject,hMemoryDC,hOldBmp invoke DeleteDC,hMemoryDC invoke EndPaint,hWnd,addr ps Cuando la ventana recibe el mensaje WM_PAINT, crea un DC de memoria, selecciona el bitmap dentro del DC de memoria, obtiene el tamao del bitmap con GetObject luego pone el bitmap en la ventana llamando a StretchBlt que se ejecuta como BitBlt pero puede estrechar o comprimir el bitmap a la dimensin deseada. En este caso, queremos que el bitmap se fije dentro de la ventana as que usamos StretchBlt en vez de BitBlt. Despus de eso, borramos el DC de memoria. .elseif uMsg==WM_LBUTTONDOWN invoke DestroyWindow,hWnd Sera frustrante para el usuario tener que esperar hasta que la splash screen desaparezca. Podemos suministrarle al usuario un eleccin. Cuando haga click sobre la splash screen, desaparecer. Por eso es que necesitamos procesar el mensaje WM_LBUTTONDOWN en la DLL. Durante la recepcin del mensaje, la ventana es destruida por la llamada a DestroyWindow. .elseif uMsg==WM_TIMER invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL invoke KillTimer,hWnd,TimerID Si el usuario elige esperar, la splash screen desaparecer cuando el lapso de tiempo especificado se haya cumplido (en nuestro ejemplo, es 2 segundos). Podemos hacer esto procesando el mensaje WM_TIMER. Al recibir este mensaje, cerramos la ventana enviando el mensaje WM_LBUTTONDOWN a la ventana. Esto es para evitar duplicacin de cdigo. No tenemos que emplear luego el temporizador, as que lo destruimos llamando a KillTimer. Cuando la ventana es cerrada, la DLL regresar el control al programa principal.

Tutorial 27: Control 'Tooltip'


Aprenderemos sobre el control tooltip [sugerencia]: Qu es, y cmo crearlo y usarlo. Baja el ejemplo.

Teora:
Un 'tooltip' [una sugerencia] es una pequea ventana rectangular que se despliega cuando el ratn pasa sobre algn rea especfica. Una ventana 'tooltip' contiene algn texto que el programador quiere que sea desplegado. En este aspecto, un 'tooltip' tiene el mismo rol que una ventana de estado pero desaparece cuando el usuario mueve el ratn o hace click lejos del rea diseada. Probablemente estars familiarizado con los 'tooltips' asociados a los botones de barras de herramientas. Esos 'tooltips' son funcionalidades convenientes suministradas por las barras de herramientas. Si quieres 'tooltips' para otras ventanas/controles, necesitas crear tu propio control 'tooltip'. Ahora que sabes qu es un 'tooltip', veamos cmo podemos crearlo y usarlo. Los pasos estn esbozados aqu: 1. Crear un control 'tooltip' con CreateWindowEx 2. Definir una regin que el control 'tooltip' monitorear para el movimiento del puntero del ratn. 3. Subsumir la regin del control 'tooltip' 4. Transmitir los mensajes del ratn del rea subsumida al control 'tooltip' (este paso puede ocurrir antes, dependiendo del mtodo usado para to transmitir los mensajes) Ahora examinaremos cada paso en detalle.

Creacin del Tooltip


El control 'tooltip' es un control comn. Por eso, necesitas llamar InitCommonControls en algn lugar de tu cdigo fuente de manera que MASM implcitamente enlace tu programa a comctl32.dll. Creas un control 'tooltip' con CreateWindowEx. El escenario tpico sera ms o menos este: .data TooltipClassName db "Tooltips_class32",0 .code ..... invoke InitCommonControls invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL Nota el estilo de ventana: TIS_ALWAYSTIP. Este estilo especifica que el 'tooltip' ser mostrado cuando el puntero del ratn est sobre el rea designada independientemente del esrtdo de la ventana que contiene el rea. Para ponerlo con mayor simplicidad, si usas esta bandera, cuando el puntero del ratn pase sobre el rea que registraste para el control 'tooltip', la ventana 'tooltip' aparecer incluso si la ventana debajo del puntero del ratn est activa. No tienes que incluir los estilos WS_POPUP y WS_EX_TOOLWINDOW en CreateWindowEx porque el procedimiento de ventana del control 'tooltip' la agrega automticamente. Tampoco necesitas especificar, la altura y el ancho de la ventana del control 'tooltip': el control 'tooltip' los ajustar automticamente para fijar el texto del 'tooltip' que ser desplegado, as que suministramos CW_USEDEFAULT en todos los cuatro parmetros. Los restantes parmetros no son tan relevantes.

Especificando la herramienta
El control 'tooltip' es creado pero no mostrado de inmediato. Queremos que la ventana 'tooltip' se muestre cuando el puntero del ratn pase sobre algn rea. Ahora es el momento de especificar ese rea. Llamamos a tal rea "herramienta" [tool]. Una herramienta es un rea rectangular sobre el rea cliente de una ventana que el control tooltip monitorear para el puntero del ratn. Si el puntero del ratn pasa sobre la herramienta, aparecer la ventana 'tooltip'. El rea rectangular puede cubrir todo el rea cliente o slo parte de ella. As que podemos dividir la herramienta en dos tipos: una implementada como una ventana y otra implementada como un rea rectangular en el rea cliente de alguna ventana. Ambas tienen sus usos. La herramienta que cubre todo el rea cliente de una ventana es usada con mucha frecuencia con controles tales como botones, controles de edicin, etc. No necesirtas especificar la coordenada y las dimensiones de la herramienta: se asume que estar sobre todo el rea cliente de la ventana. La herramienta que es implementada como un rea rectangular sobre el rea cliente es til cuando quieres dividir el rea cliente de una ventana en varias regiones sin usar ventanas hijas. Con este tipo de herramienta, necesitas especificar la coordenada de la esquina superior izquierda y el ancho y la altura de la herramienta. Con este tipo de herramienta, no necesitas especificar la esquina superior izquierda y el ancho y la altura de la herramienta. Especificas la herramienta con la estructura TOOLINFO que tiene la siguiente definicin:
TOOLINFO STRUCT cbSize DWORD ?

uFlags hWnd uId rect hInst lpszText lParam TOOLINFO ENDS

DWORD DWORD DWORD RECT DWORD DWORD LPARAM

? ? ? <> ? ? ?

Nombre del Campo cbSize

Explicacin El tamao de la estructura TOOLINFO. DEBES llenar este miembro. Windows no monitorear el error si este campo no es llenado debidamente y recibirs extraos e impredecibles resultados. Los bits de bandera que especifican . Este valor puede ser una combinacin de las siguientes banderas:

uFlags

TTF_IDISHWND "ID is hWnd". Si especificas esta bandera, significa que quieres usar una herramienta que cubre todo el rea cliente de una ventana (el primer tipo de herramienta de arriba). Si usas esta bandera, debes llenar el miembro uId de esta estructura con el manejador de la ventana que quieres usar. Si no especificas esta bandera, significa que quieres el segundo tipo de herramienta, la que es implementada como rea rectangular sobre la ventana cliente. En ese caso, necesitas llenar el miembro rect con la dimensin del rectngulo. TTF_CENTERTIP Normalmente la ventana 'tooltip' a la derecha y abajo del puntero del ratn. Si especificas esta bandera, la ventana 'tooltip' aparecer directamente debajo de la herramienta y ser centrada independientemente de la posicin del puntero del ratn. TTF_RTLREADING Pudes olvidar esta bendera si tu programa no est diseado especficamente para sistemas arbicos o hebreos. Esta bandera despliega el texto de la sugerencia [tooltip] de derecha a izquierda. No trabaja bajo otros sistemas. TTF_SUBCLASS Si usas esta bandera, significa que le dices al control 'tooltip' que subclasifique la ventana que la herramienta sobre la cul est la herramienta de manera que el control 'tooltip' pueda interceptar mensajes de ratn que son enviados a la ventana. Esta manera es muy conveniente. Si no usas esta bandera, tienes que trabajar ms para transmitir los mensajes del ratn al control 'tooltip'.

hWnd

Manejador a la ventana que contiene la herramienrta. Si

especificas la bendera TTF_IDISHWND, este campo es ignorado ya que Windows usar el valor en el miembro uId como manejador de ventana. Necesitas llenar este campo si:

No usas la bendera TTF_IDISHWND (en otras palabras, usas una herramienta rectangular) Especificas el valor LPSTR_TEXTCALLBACK en el mimebro lpszText. Este valor le dice al control 'tooltip' que, cuando necesita desplegar la ventana 'tooltip', debe consultar a la ventana que contiene la herramienta para el texto a ser desplegado. Si quieres cambiar tu texto de sugerencia [tooltip] dinmicamente, deberas especificar el valor LPSTR_TEXTCALLBACK en el miembro lpszText. El control 'tooltip' enviar el mensaje de notificacin TTN_NEEDTEXT a la ventana identificada por el manejador en el campo hWnd.

El valor en este campo puede tener dos significados, dependiendo si el miembro uFlags contiene la bandera TTF_IDISHWND.

uId

ID de la herramienta definido para la aplicacin si la bandera TTF_IDISHWND no est especificada. Puesto que esto significa que usas una herramienta que cubre slo una parte del rea cliente, es lgico que puedas tener muchas de tales herramientas sobre el mismo rea cliente (sin superponerse). El control 'tooltip' necesita una manera de diferenciarlas. En este caso, el manejador de ventana en el miembro hWnd no es suficiente ya que todas las herramientas est sobre la misma ventana. Los IDs definidos para la aplicacin son entonces necesarios. Los IDs pueden ser de cualquier valor con tal de que su valor sea nico entre ellos. El manejador de la ventana cuya totalidad del rea cliente es usada como herramienta si la bandera TTF_IDISHWND es especificada. Te puedes preguntar por qu este campo se usa para almacenar el manejador de ventana en vez del campo hWnd de arriba. La respuesta es: el miembro hWnd ya puede ser llenado si se especifica el valor LPSTR_TEXTCALLBACK en el miembro lpszText y la ventana que es responsable de suministrar el texto del 'tooltip' y la ventana que contiene la herramienta NO no puede ser la misma (Puedes disear tu programa de manera que una ventana pueda servir para ambos roles pero esto restringe bastante. En este caso, Microsoft te da mayor libertad. nimo.)

rect

Una estructura RECT que especifica la dimensin de la herramienta. Esta estructura define un rectngulo relativo a la esquina izquierda superior del rea cliente de la ventana especificada dpor el miembro hWnd. En pocas palabras, debes llenar esta estructura si quieres especificar una herramienta que cubra slo una parte del rea cliente. El control 'tooltip' ignorar este miembro si especificas la bandera TTF_IDISHWND (eliges usar una herramienta que cubra todo el rea cliente) El manejador de la instancia que contiene el recurso de cadena que ser usado como texto de sugerencia [tooltip text] si el valor en el miembro lpszText especifica el identificador del recurso de cadena. Esto puede sonar confuso. Lee primero la explicacin del miembro lpszText y entenders para qu es usado este campo. El control 'tooltip' ignorar este campo si el campo lpszText no contiene un identificador del recursos de cadena.

hInst

lpszText Este campo puede tener diversos valores:

Si especificas el valor LPSTR_TEXTCALLBACK en este campo, el control tooltip enviar el mensaje de notificacin TTN_NEEDTEXT a la ventana identificada por el manejador en el campo hWnd para la cadena de texto a ser desplegada en la ventana tooltip. Este es el mtodo ms dinmico de actualizacin del texto de sugerencia: puedes cambiar el texto de sugerencia cada vez que la ventana 'tooltip' sea desplegada. Si especificas un identificador de recursos de cadena en este campo, cuando el control 'tooltip' necesite desplegar el texto de sugerencia en la ventana 'tooltip', busca por le cadena en la tabla de cadenas de la instancia especificada por el mimbro hInst. El control 'tooltip' identifica un identificador de cadena de recurso chequeando la palabra alta de este campo. Ya que un identificador de cadena es un valor de 16-bits, la palabra alta de este campo siempre ser cero. Este mtodo es til si planificas portear [to port] tu programa a otros lenguajes. Puesto que el recurso de cadena es definido en el guin de recursos, no necesitas modificar el cdigo fuente. Slo tienes que modificar las tablas de cadenas y los textos de sugerencia cambiarn sin el riesgo de introducir errores en tu programa Si el valor de este campo no es LPSTR_TEXTCALLBACK y la palabra alta no es cero, el control 'tooltip' interpreta el valor como el puntero a una cadena de texto que ser usada como texto de

sugerencia. Este mtodo es el ms fcil de usar pero el menos flexible. Para recapitular, necesitas llenar la estructura TOOLINFO antes de subsumirla al control 'tooltip'. Esta estructura describe las caractersticas de la herramienta que deseas.

Registrar la herramienta [tool] con el control 'tooltip'


Despus de que llenes la estructura TOOLINFO, debes subsumirla al control 'tooltip'. Un control 'tooltip' puede servirse de muchas herramientas, por lo que es innecesario crear ms de un control 'tooltip' para una ventana. Para registrar una herramienta con un control 'tooltip', envas el mensaje TTM_ADDTOOL al control 'tooltip'. El valor de wParam no se usa y lParam debe contener la direccin de la estructura TOOLINFO que quieres registrar. .data? ti TOOLINFO <> ....... .code ....... <fill the TOOLINFO structure> ....... invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti SendMessage para este mensaje regresar TRUE si la herramienta es satisfactoriamente registrada con el control 'tooltip', FALSE si ese no es el caso. Puedes cancelar el registro de la herramienta enviando el mensaje TTM_DELTOOL al control 'tooltip'.

Transmitiendo los Mensajes del Ratn al Control 'Tooltip'


Cuando el paso de arriba es completado, el control 'tooltip' sabe qu rea debera monitorearse para los mensajes del ratn y qu texto debera desplegar el conrtrol tooltip. Lo nico que falta es el *gatillo* [trigger] para la accin. Piensa en ello: el rea especificada por la herramienta est sobre el rea cliente de la otra ventana. Cmo puede interceptar el control 'tooltip' los mensajes del ratn para esa ventana? Necesita hacerlo as con el fin de que pueda medir la cantidad de tiempo que el puntero del ratn se halla sobre un punto en la herramienta de manera que cuando caduque el lapso de tiempo especificado, el control 'tooltip' muestre la ventana con la sugerencia [tooltip window]. Hay dos mtodos para alcanzar esta meta, una requiere la cooperacin de la ventana que contiene la herramienta y la otra sin la cooperacin sobre la parte de esa ventana.

La ventana que contiene la herramientea debe transmitir los mensajes del ratn al control 'tooltip' enviando mensajes TTM_RELAYEVENT al control. El valor lParam debe contener la direccin de una estructura MSG que especifica el mensaje a ser transmitido al control 'tooltip'. Un control 'tooltip' procesa slo los siguientes mensajes del ratn: o WM_LBUTTONDOWN o WM_MOUSEMOVE o WM_LBUTTONUP o WM_RBUTTONDOWN o WM_MBUTTONDOWN o WM_RBUTTONUP o WM_MBUTTONUP Todos los mensajes son ignorados. As que en el procedimiento de ventana de la ventana que contiene la herramienta, debe haber un conmutador [switch] que haga algo como esto: WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD ....... if uMsg==WM_CREATE ............. elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg ..........

Puedes especificar la bandera TTF_SUBCLASS en el miembro uFlags de la estructura TOOLINFO. Esta bandera le dice al cintrol 'tooltip' que subclasifique la ventana que contiene la herramienta de manera que pueda interceptar los mensajes del ratn sin la cooperacin de la ventana. Este mtodo es ms fcil de usar ya que no requiere ms cdigo que la especificacin de la bandera TTF_SUBCLASS y el control 'tooltip' maneja l mismo todas las intercepciones de mensajes.

Eso es. En este paso, tu control tooltip es completamente funcional. Hay varios mensajes tiles relativos al 'tooltip' sobre los cules deberas tener conocimiento.

TTM_ACTIVATE. Si quieres deshabilitar/habilitar el control 'tooltip' dinmicamente, este mensaje es para t. Si el valor wParam es TRUE, es habilitado el control 'tooltip'. Si el valor wParam es FALSE, el control 'tooltip' es deshabilitado. Un

control 'tooltip' es habilitado cuando es creado as que no necesitas enviar este mensaje para activarlo. TTM_GETTOOLINFO and TTM_SETTOOLINFO. Si quieres obtener/cambiar los valores en la estructura TOOLINFO despus de que fue subsumida al control 'tooltip', usa estos mensajes. Necesitas especificar esta herramienta con los valores correctos de uId y hWnd. Si slo quieres cambiar el miembro rect, usa el mensaje TTM_NEWTOOLRECT. Si slo quieres cambiar el texto, usa TTM_UPDATETIPTEXT. TTM_SETDELAYTIME. Con este mensaje, puedes especificar el tiempo de retardo que usa el control 'tooltip' cuando est desplegando el texto de sugerencia y mucho ms.

Ejemplo:
El siguiente ejemplo es una simple caja de dilogo con dos botones. El rea cliente de la caja de dilogo es dividida en 4 reas: superior izquierda, superior derecha, baja izquierda, baja derecha. Cada rea es especificada como una herramienta con sus propios textos de sugerencia [tooltip text]. Los dos botones tambin tienen sus propios textos de sugerencia.
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD EnumChild proto :DWORD,:DWORD SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD .const IDD_MAINDIALOG equ 101 .data ToolTipsClassName db "Tooltips_class32",0 MainDialogText1 db "This is the upper left area of the dialog",0 MainDialogText2 db "This is the upper right area of the dialog",0 MainDialogText3 db "This is the lower left area of the dialog",0 MainDialogText4 db "This is the lower right area of the dialog",0 .data? hwndTool dd ? hInstance dd ? .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL invoke ExitProcess,eax DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD LOCAL ti:TOOLINFO

LOCAL id:DWORD LOCAL rect:RECT .if uMsg==WM_INITDIALOG invoke InitCommonControls invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\ TTS_ALWAYSTIP,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInstance,NULL mov hwndTool,eax mov id,0 mov ti.cbSize,sizeof TOOLINFO mov ti.uFlags,TTF_SUBCLASS push hDlg pop ti.hWnd invoke GetWindowRect,hDlg,addr rect invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect inc id invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect inc id invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect inc id invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect invoke EnumChildWindows,hDlg,addr EnumChild,addr ti .elseif uMsg==WM_CLOSE invoke EndDialog,hDlg,NULL .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgProc endp EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD LOCAL buffer[256]:BYTE mov edi,lParam assume edi:ptr TOOLINFO push hwndChild pop [edi].uId or [edi].uFlags,TTF_IDISHWND invoke GetWindowText,hwndChild,addr buffer,255 lea eax,buffer mov [edi].lpszText,eax invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi assume edi:nothing ret EnumChild endp SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD mov edi,lpti mov esi,lprect assume esi:ptr RECT assume edi:ptr TOOLINFO .if id==0 mov [edi].rect.left,0 mov [edi].rect.top,0

mov eax,[esi].right sub eax,[esi].left shr eax,1 mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top shr eax,1 mov [edi].rect.bottom,eax .elseif id==1 mov eax,[esi].right sub eax,[esi].left shr eax,1 inc eax mov [edi].rect.left,eax mov [edi].rect.top,0 mov eax,[esi].right sub eax,[esi].left mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top mov [edi].rect.bottom,eax .elseif id==2 mov [edi].rect.left,0 mov eax,[esi].bottom sub eax,[esi].top shr eax,1 inc eax mov [edi].rect.top,eax mov eax,[esi].right sub eax,[esi].left shr eax,1 mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top mov [edi].rect.bottom,eax .else mov eax,[esi].right sub eax,[esi].left shr eax,1 inc eax mov [edi].rect.left,eax mov eax,[esi].bottom sub eax,[esi].top shr eax,1 inc eax mov [edi].rect.top,eax mov eax,[esi].right sub eax,[esi].left mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top mov [edi].rect.bottom,eax .endif push lpText pop [edi].lpszText invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti assume edi:nothing assume esi:nothing ret SetDlgToolArea endp end start

Anlisis:
Despus de que es creada la ventana del dilogo principal, creamos el control 'tooltip ' con CreateWindowEx.
invoke InitCommonControls invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\ TTS_ALWAYSTIP,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInstance,NULL mov hwndTool,eax

Despus de eso procedemos a definir cuatro herramientas para cada esquina de la caja de dilogo.
mov id,0 ; usado como el ID de la herramienta mov ti.cbSize,sizeof TOOLINFO mov ti.uFlags,TTF_SUBCLASS ; dice al control tooltip que subclasifique
ventana del dilogo.

la

push hDlg pop ti.hWnd ; manejador para la ventana que contiene la herramienta invoke GetWindowRect,hDlg,addr rect; obtener la dimensin del rea cliente invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

Inicializamos los miembros de la estructura TOOLINFO. Nota que queremos dividir el rea cliente en 4 herramientas, as que necesitamos saber la dimensin del rea cliente. Esa es la razn por la cual llamamos a GetWindowRect. No queremos transmitir mensajes de ratn al control 'control' nosotros mismos, as que especificamos la bandera TIF_SUBCLASS. SetDlgToolArea es una funcin que calcula el rectngulo asociado de cada herramienta y registra la herramienta para el control 'tooltip'. No entrar en detalles engorrosos sobre el clculo, es suficiente decir que divide el rea cliente en 4 reas con los mismos tamaos. Luego enva el mensaje TTM_ADDTOOL al control 'tooltip', pasando la direccin de la estructura TOOLINFO en el parmetro lParam.
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

Despus de que son registradas las 4 herramientas, podemos ocuparnos en los botones de la caja de dilogo. Podemos manejar cada botn a travs de su ID pero esto es muy tedioso.En vez de eso usaremos la llamada a la API EnumChildWindows para enumerar todos los controles en la caja de dilogo y luego registrarlos para el control 'tooltip'. EnumChildWindows tiene la siguiente sintaxis: EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD

hWnd es el manejador de la ventana padre. lpEnumFunc es la direccin de la funcin EnumChildProc que ser llamada para cada control enumerado. lParam es el valor definido de la aplicacin [application-defined value] que ser pasado a la funcin EnumChildProc. La a funcin EnumChildProc tiene la siguiente definicin: EnumChildProc proto hwndChild:DWORD, lParam:DWORD hwndChild es el manejador a un control enumerado por EnumChildWindows. lParam es el mismo valor lParam que pasas a 0 EnumChildWindows. En nuestro ejemplo, llamamos a EnumChildWindows ms o menos as:
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti

Pasamos la direccin de la estructura TOOLINFO en el parmetro lParam porque registraremos cada control de ventana hija para el control 'tooltip' en la funcin EnumChild. Si no queremos usar este mtodo, necesitaremos declarar ti como una variable global que puede introducir errores [bugs]. Cuando llamamos a EnumChildWindows, Windows enumerar los controles de ventanas hijas sobre nuestra caja de dilogo y llamamos a la funcin EnumChild una vez para cada control enumerado. De esta manera, si nuestra caja de dilogo tiene dos controles, EnumChild ser llamada dos veces. La funcin EnumChild llena los miembros relevantes de la estructura TOOLINFO y luego registra la herramienta con el control 'tooltip'.
EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD LOCAL buffer[256]:BYTE mov edi,lParam assume edi:ptr TOOLINFO push hwndChild pop [edi].uId ; usamos todo el rea cliente del control como herramienta or [edi].uFlags,TTF_IDISHWND invoke GetWindowText,hwndChild,addr buffer,255 lea eax,buffer ; usar el texto de la ventana como texto de sugerencia mov [edi].lpszText,eax invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi assume edi:nothing ret EnumChild endp

Nota que en este caso, un tipo diferente de herramienta: una que cubre todo el rea cliente de la ventana. As que necesitamos llenar todo el campo uID con el manejador de ventana que contiene la herramienta. Tambin debemos especificar la benadera TTF_IDISHWND en el miembrio uFlags.

Tutorial 28: API de Depuracin de Win32 Parte 1


En este tutorial, aprenders que ofrece Win32 a los desarrolladores interesados en las primitivas de depuracin. Sabrs como depurar un proceso cuando hayas finalizado con este tutorial. Bajar el ejemplo. Teora: Win32 tiene varias funciones en su API que permiten a los programadores usar algunas de las potencialidades de un depurador. Son llamadas las Apis de depuracin de Win32 o primitivas. Con ellas puedes: Cargar un programa o enganchar un programa que est corriendo para su depuracin Obtener informacin de bajo nivel sobre el programa que ests depurando, tal como el ID, la direccin del punto de entrada, la base de la imagen ,etc.. Ser notificado sobre los eventos de depuracin relacionados tales como cuando un proceso/hilo inicia/termina, cundo las DLLs son cargadas/descargadas, etc. Modificar el proceso/hilo que est siendo depurado

En pocas palabras, puedes escribir el cdigo de un depurador sencillo con estas APIs. Como este tema es amplio, lo he dividido en varias partes: este tutorial ser la primera. Explicar los conceptos bsicos y dar un marco general para usar las APIs de depuracin de Win32 en este tutorial. Los pasos al usar las APIs de depuracin de Win32 son:

1. Crear un proceso o enganchar un proceso en curso. Este es el primer


paso al usar las APIs de depuracin de Win32. Como tu programa va actuar como un depurador, necesitas un programa a depurar. El programa que est siendo depurado ser llamado un "depurando" [debuggee]. Puedes adquirir un "depurando" [debugee] de dos maneras: o Puedes crear t mismo el proceso "depurando" con CreateProcess. Con el fin de crear un proceso para depuracin, debes especificar la bandera DEBUG_PROCESS. Esta bandera le dice a Windows que queremos depurar el proceso. Windows enviar notificaciones de los eventos de importantes para la depuracin [important debuggingrelated (debug events)] que ocurren en el "depurando". El proceso en depuracin ser suspendido inmediatamente hasta que tu programa est listo. Si el proceso en depuracin crea tambin un proceso hijo, Windows enviar tambin eventos de depuracin que ocurran en todos esos procesos hijos a tu programa. Usualmente, esta conducta es indeseada. Puedes deshabilitar esta conducta especificando la bandera DEBUG_ONLY_THIS_PROCESS en combinacin con la bandera DEBUG_PROCESS. o Puedes anexar tu programa a un proceso que est corriendo con DebugActiveProcess. Esperar por eventos de depuracin. Despus de que tu programa ha adquirido un proceso para su depuracin, el hilo primario del proceso en depuracin es suspendido hasta que tu programa llame a WaitForDebugEvent. Esta funcin trabaja como otras funciones WaitForXXX,, es decir, bloquea el hilo que llama hasta que ocurre el evento

2.

por el cual se espera. En este caso, espera por eventos de depuracin a ser enviados por Windows. Veamos su definicin: WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD lpDebugEvent es la direccin de una estructura DEBUG_EVENT que ser llenada con informacin sobre el evento de depuracin que ocurre dentro del depurando. dwMilliseconds es el lapso de tiempo en milisegundos que esta funcin esperar hasta que ocurra el evento de depuracin. Si este perodo caduca y no ocurre ningn evento de depuracin, WaitForDebugEvent regresa al programa que ha hecho la llamada. Pero si especificas la constante INFINITE en este argumento, la funcin no regresar hasta que ocurra un evento de depuracin. Ahora examinemos con ms detalles la estructura DEBUG_EVENT. DEBUG_EVENT STRUCT dwDebugEventCode dd ? dwProcessId dd ? dwThreadId dd ? u DEBUGSTRUCT <> DEBUG_EVENT ENDS dwDebugEventCode contiene el valor que especifica qu tipo de evento de depuracin ocurre. En pocas palabras, pueden haber muchos tipos de eventos, tu programa necesita chequear el valor en este campo para conocer qu tipo de evento ocurre y responder apropiadamente. Los valores posibles son: Valor Significado

Un proceso ha sido creado. Este evento ser enviado cuando el proceso en depuracin es creado (y todava no est CREATE_PROCESS_DEBUG_EVE correindo) o cuando tu programa se anexe NT a un proceso con DebugActiveProcess. Este es el primer evento que recibir tu programa. EXIT_PROCESS_DEBUG_EVENT Un proceso termina. Se ha creado un nuevo hilo en el proceso en depuracin o tu programa se anexa a CREATE_THREAD_DEBUG_EVEN un proceso que ya est corriendo. Nota T que no recibirs esta notificacin cuando el hilo primario del proceso en depuracin sea creado. EXIT_THREAD_DEBUG_EVENT Termina un hilo en el proceso en depuracin. Tu programa no recibir este evento para el hilo primario. En pocas palabras, puedes pensar en el hilo primario del proceso en depuracin como un equivalente del mismo proceso en depuracin. As que, cuando tu programa ve CREATE_PROCESS_DEBUG_EVENT, es realmente el

CREATE_THREAD_DEBUG_EVENT del hilo primario.

LOAD_DLL_DEBUG_EVENT

El proceso en depuracin carga una DLL. Recibirs este evento cuando el cargador del PE resuelva primero los enlaces a las DLLs (llamas a CreateProcess para cargar el depurando) y cuando el proceso en depuracin llama a LoadLibrary. Una DLL es descargada del proceso en depuracin. Ocurre una excepcin en el proceso en depuracin. Importante: Este evento ocurrir una vez justo antes de que el proceso en depuracin comience a ejecutar su primera instruccin. La excepcin realmente es una ruptura de depuracin [a debug break] (int 3h). Cuando quieres resumir el proceso en depuracin, llamas a ContinueDebugEvent con la bandera DBG_CONTINUE. No uses la bandera DBG_EXCEPTION_NOT_HANDLED sino el proceso en depuracin rehusar correr bao NT (en Win98, trabaja bien).

UNLOAD_DLL_DEBUG_EVENT

EXCEPTION_DEBUG_EVENT

Este evento es generado cuando el proceso en depuracin llama a la funcin OUTPUT_DEBUG_STRING_EVEN DebugOutputString para eviar una T cadena de caracteres con un mensaje a tu programa. RIP_EVENT Ocurre un error en el sistema al depurar...

dwProcessId y dwThreadId son los id del proceso y del hilo del proceso donde ocurre el evento de depuracin. Puedes usar estos valores como identificadores del proceso/hilo en el cual ests interesado. Recuerda que si usas CreateProcess para cargar el proceso en depuracin, tambin obtienes los IDs del proceso y del hilo del proceso en depuracin en la estructura PROCESS_INFO. Puedes usar estos valores para diferenciar entre los eventos de depuracin que ocurren en el proceso en depuracin y su proceso hijo (en caso de que no hayas especificado la bandera DEBUG_ONLY_THIS_PROCESS). u es una union que contiene ms informacin sobre el proceso en depuracin. Puede ser una de las siguientes estructuras dependiendo del valor de dwDebugEventCode arriba. valor en dwDebugEventCode Interpretacin de u

Una estructura CREATE_PROCESS_DEBUG_EVE CREATE_PROCESS_DEBUG_INFO NT llamada CreateProcessInfo

EXIT_PROCESS_DEBUG_EVENT

Una estructura EXIT_PROCESS_DEBUG_INFO llamada ExitProcess

Una estructura CREATE_THREAD_DEBUG_EVEN CREATE_THREAD_DEBUG_INFO llamada T CreateThread EXIT_THREAD_DEBUG_EVENT LOAD_DLL_DEBUG_EVENT UNLOAD_DLL_DEBUG_EVENT Una estructura EXIT_THREAD_DEBUG_EVENT llamada ExitThread Una estructura LOAD_DLL_DEBUG_INFO llamada LoadDll Una estructura UNLOAD_DLL_DEBUG_INFO llamada UnloadDll Una estructura EXCEPTION_DEBUG_INFO llamada Exception

EXCEPTION_DEBUG_EVENT

Una estructura OUTPUT_DEBUG_STRING_EVEN OUTPUT_DEBUG_STRING_INFO llamada T DebugString RIP_EVENT A RIP_INFO llamada RipInfo

En este tutorial no entrar en detalles sobre todas las estructuras, aqu slo ser cubierta la estructura CREATE_PROCESS_DEBUG_INFO. Asumiendo que nuestro programa llama a WaitForDebugEvent y regresa . Lo primero que deberamos hacer es examinar dwDebugEventCode para ver qu tipo de evento de depuracin ocurri en el proceso en depuracin. Por ejemplo, si el valor en dwDebugEventCode es CREATE_PROCESS_DEBUG_EVENT, puedes interpretar el miembro en u como CreateProcessInfo y acceder a l con u.CreateProcessInfo.

3. Hacer lo que quieras hacer con tu programa para ese evento de


depuracin. Cuando regresa WaitForDebugEvent, signifca que acaba de ocurrir un evento de depuracin en el depurando o ha ocurrido una pausa. Tu programa necesita examinar el valor en dwDebugEventCode con el fin de reaccionar apropiadamente al evento. En este sentido, es como procesar mensajes de Windows: eliges manejar algunos e ignorar otros. Dejar que el proceso en depuracin contine la ejecucin. Cuando ocurre un evento de depuracin, Windows suspende el proceso en depuracin. Cuando hayas terminado la manipulacin del evento, necesitas poner en movimiento el proceso en depuracin de nuevo. Haces esto llamando a la funcin ContinueDebugEvent. ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD Esta funcin resume el hilo que fue suspendido previamante porque ocurri un evento de depuracin. dwProcessId y dwThreadId los IDs de proceso y de hilo del hilo que ser resumido. Usualmente tomas estos dos valores de los miembros dwProcessId y dwThreadId de la estructura DEBUG_EVENT. dwContinueStatus especifica cmo continuar el hilo que report el evento de depuracin. Hay dos valores posibles: DBG_CONTINUE y

4.

DBG_EXCEPTION_NOT_HANDLED. Para los otros eventos de depuracin, esos dos valores hacen lo mismo: resumen el hilo. La excepcin es el EXCEPTION_DEBUG_EVENT. Si el hilo reporta un evento de depuracin excepcin, significa que ocurri una excepcin en el hilo del proceso en depuracin. Si especificas DBG_CONTINUE, el hilo ignorar su manipulacin de la excepcin y continuar con la ejecucin. En este escenario, tu programa debe examinar y resolver la excepcin misma antes de resumir el hilo con DBG_CONTINUE sino la excepcin ocurrir una vez ms, una vez ms.... Si especificas DBG_EXCEPTION_NOT_HANDLED, tu programa est diciendo a Windows que no manejar la excepcin: Windows usara el manejador de excepcin por defecto del proceso en depuracin para manejar la excepcin. En conclusin, si el evento de depuracin refiere a una excepcin en el proceso en depuracin, deberas llamar a ContinueDebugEvent con la bandera DBG_CONTINUE si tu programa ya removi la causa de la excepcin. De otra manera, tu programa debe llamar a ContinueDebugEvent con la bendera DBG_EXCEPTION_NOT_HANDLED. Excepto en un caso en el que siempre debes usar la bandera DBG_CONTINUE: el primer EXCEPTION_DEBUG_EVENT que tiene el valor EXCEPTION_BREAKPOINT en el miembro ExceptionCode. Cuando el proceso en depuracin vaya a ejecutar su primera instruccin, tu programa recibir el evento de depracin exepcin. Realmente es un quiebre de depuracin [debug break] (int 3h). Si respondes llamando a ContinueDebugEvent con la bandera DBG_EXCEPTION_NOT_HANDLED, Windows NT reusar correr el proceso en depuracin (porque nada cuida de l). Siempre debes usar la bandera DBG_CONTINUE en este caso para decir a Windows que quieres que el hilo contine.

5. Continuar este ciclo en un bucle infinito hasta que termine el


proceso en depuracin. Tu programa debe estar en un bucle infinito muy parecido al bucle de mensajes hasta que el proceso en depuracin termine. El bucle tiene ms o menos el siguiente aspecto: .while TRUE invoke WaitForDebugEvent, addr DebugEvent, INFINITE .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT <Handle the debug events> invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw Aqu est el truco: una vez que empiezas a depurar un programa, ya no puedes desprenderte del proceso en depuracin hasta que termine. Resumamos los pasos de nuevo:

1. Crear un procesos o anexar tu programa a un proceso que est 2. 3. 4. 5.


corriendo. Esperar por los eventos de depuracin Hacer lo que tu programa quiere hacer en respuesta al evento en depuracin. Dejar que el proceso en depuracin contine su ejecucin. Continuar este ciclo en un bucle infinito hasta que el proceso en depuracin termina

Ejemplo:

Este ejemplo depura un programa win32 y muestra informacin importante tal como el manejador del proceso, el Id del proceso, la base de la imagen , etc. .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.1",0 ofn OPENFILENAME <> FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 ExitProc db "The debuggee exits",0 NewThread db "A new thread is created",0 EndThread db "A thread is destroyed",0 ProcessInfo db "File Handle: %lx ",0dh,0Ah db "Process Handle: %lx",0Dh,0Ah db "Thread Handle: %lx",0Dh,0Ah db "Image Base: %lx",0Dh,0Ah db "Start Address: %lx",0 .data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> .code start: mov ofn.lStructSize,sizeof ofn mov ofn.lpstrFilter, offset FilterString mov ofn.lpstrFile, offset buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT

.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .endif .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread .endif invoke ExitProcess, 0 end start

Anlisis:
El programa llena la estructura OPENFILENAME y luego llama a GetOpenFileName para pernitir que el usuario elija un programa para su depuracin. invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi Cuando el usuario elige uno, llama a CreateProcess para cargar el programa. Llama a GetStartupInfo para llenar la estructura STARTUPINFO con sus valores por defecto. Nota que usamos la bandera DEBUG_PROCESS combinada con DEBUG_ONLY_THIS_PROCESS con el fin de depurar solamente este programa, sin incluir sus procesos hijos. .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE Cuando es cargado el proceso en depuracin, introducimos el bucle infinito de depuracin, llamando a WaitForDebugEvent. WaitForDebugEvent no regresar hasta que ocurra un evento de depuracin en el 'proceso en depuracin' porque especificamos INFINITE como su segundo parmetro. Cuando ocurre un evento de depuracion, WaitForDebugEvent regresa y DBEvent es llenada con informacin sobre el evento de depuracin. .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break Primero chequeamos el valor en dwDebugEventCode. Si es EXIT_PROCESS_DEBUG_EVENT, desplegamos una caja de mensaje que dice "The debuggee exits" [El evento de depuracin ha culminado] y luego salimos del bucle de depuracin.

.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION Si el valor en dwDebugEventCode es CREATE_PROCESS_DEBUG_EVENT, entonces desplegamos cierta informacin interesante sobre el proceso en depuracin en una caja de mensaje. Obtenemos esa informacin a partir de u.CreateProcessInfo. CreateProcessInfo es una estructura del tipo CREATE_PROCESS_DEBUG_INFO. Puedes obtener ms info sobre esta estructura en la referencia de la API de Win32. .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .endif Si el valor en dwDebugEventCode es EXCEPTION_DEBUG_EVENT, debemos chequear luego por el tipo exacto de excepcin. Es una lnea larga de referencia de estructura anidada pero puedes obtener el tipo de excepcin del miembro ExceptionCode. Si el valor en ExceptionCode es EXCEPTION_BREAKPOINT y ocurre por primera vez (o si estamos seguros de que el proceso en depuracin no tiene incrustado int 3h), podemos asumir con seguridad que esta excepcin ocurri cuando el proceso en depuracin iba a ejecutar la primera instruccin. Cuando hayamos hecho lo que ibamos a hacer con el procesamiento, debemos llamar a l ContinueDebugEvent con la bandera DBG_CONTINUE para dejar que corra de nuevo el proceso en depuracin. Luego volvemos a esperar el siguiente evento de depuracin. .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION .endif Si el valor en dwDebugEventCode es CREATE_THREAD_DEBUG_EVENT o EXIT_THREAD_DEBUG_EVENT, desplegamos una caja de mensaje que diga eso. invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw Excepto para el caso EXCEPTION_DEBUG_EVENT de arriba, llamamos a ContinueDebugEvent con la bandera DBG_EXCEPTION_NOT_HANDLED para resumir el proceso en depuracin. invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread

Cuando termina el proceso en depuracin, estamos fuera del bucle de depuracin y debemos cerrar los manejadores del proceso y del hilo del proceso en depuracin. Cerrar los manejadores no significa que estamos matando el proceso/hilo. Slo significa que no queremos usar ms esos manejadores para referir al proceso/hilo.

Tutorial 29: API de Depuracin de Win32 Parte 2


Continuamos con el tema de win32 debug API. En este tutorial, aprenderemos como depurar el depurando [debugee]. Baja el ejemplo

Teora:
En el tutorial previo, aprendimos como cargar el debuggee y a manejar eventos de depuracin que ocurren en su proceso. Para que sea til, nuestro programa debe ser capaz de modificar el proceso depurado. Hay varias APIs para realizar este propsito. ReadProcessMemory Esta funcin permite leer la memoria en el proceso especificado. El prototipo de la funcin es: ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD hProcess es el manejador al proceso que quieres leer. lpBaseAddress es la direccin del proceso objeto [target process] que quieres comenzar a leer. Por ejemplo, si quieres leer 4 bytes del proceso en depuracin comenzando en 401000h, el valor en este parmetro debe ser 401000h. lpBuffer es la direccin del buffer que recibir los bytes ledos del proceso. nSize es el nmero de bytes que quieres leer lpNumberOfBytesRead es la direccin de la variable de tamao dword que recibe el nmero de bytes realmente ledos. Si no esrelevante para t, puedes usar NULL. WriteProcessMemory es la contraparte de ReadProcessMemory. Te permite escribir la memoria del proceso objeto. Sus parmetros son exactamente los mismos que los de ReadProcessMemory Las siguientes dos funciones de la API necesitan un poco de explicacin. Bajo un sistema operativo multitarea como Windows, pueden haber varios programas corriendo al mismo tiempo. Windows da a cada hilo un pedazo de tiempo. Cuando expira ese lapso, Windows paraliza el hilo presente y conmuta a otro hilo que tiene la prioridad ms alta. Justo antes de conmutar al otro hilo, Windows salva los valores de los registros del hilo presente de manera que cuando llegue el momento de resumir el hilo, Windows pueda restaurar el ltimo *entorno* del hilo. Los valores salvados de los registros son llamados colectivamente en un contexto. Volvamos a nuestro asunto. Cuando ocurre un evento de depuracin, Windows suspende el proceso en depuracin. El contexto del proceso en

depuracin es salvado. Como el proceso en depuracin es suspendido, podemos estar seguros de que los valores en el contexto se mantendr sin alteracin. Podemos obtener los valores en el contexto con GetThreadContext y podemos cambiarlos con SetThreadContext. Estas dos APIs son muy poderosas. Con ellas tienes en tus dedos [fingertips] el poder de una VxD [the VxD-like power] sobre los procesos en depuracin: puedes alterar los valores del registro salvado y justo antes de que el proceso en depuracin resuma la ejecucin, los valores del contexto sern re-escritos dentro de los registros. Cualquier cambio que hagas al contexto es reflejado en el proceso en depuracin. Piensa en ello: incluso puedes alterar el valor del registro eip y desviar el flujo de la ejecucin a donde quieras! No sers capaz de hacer eso bajo circunstancias normales. GetThreadContext proto hThread:DWORD, lpContext:DWORD hThread es el manejador del hilo del que quieres obtener el contexto lpContext es la direccin de la estructura CONTEXT que ser llenada cuando la funcin regresa satisfactoriamente. SetThreadContext tiene exactamente los mismos parmetros. Vemos que la estructura CONTEXT se ve ms o menos como:

CONTEXT STRUCT ContextFlags dd ? ;--------------------------------------------------------------------------------------------------------; Esta seccin es regresada si ContextFlags contiene el valor ; CONTEXT_DEBUG_REGISTERS ;---------------------------------------------------------------------------------------------------------iDr0 dd ? iDr1 dd ? iDr2 dd ? iDr3 dd ? iDr6 dd ? iDr7 dd ? ;--------------------------------------------------------------------------------------------------------; Esta seccin es regresada si ContextFlags contiene el valor ; CONTEXT_FLOATING_POINT ;---------------------------------------------------------------------------------------------------------FloatSave FLOATING_SAVE_AREA <> ;--------------------------------------------------------------------------------------------------------; Esta seccin es regresada si ContextFlags contiene el valor ; CONTEXT_SEGMENTS ;---------------------------------------------------------------------------------------------------------regGs dd ? regFs dd ? regEs dd ? regDs dd ? ;--------------------------------------------------------------------------------------------------------; Esta seccin es regresada si ContextFlags contiene el valor CONTEXT_INTEGER ;----------------------------------------------------------------------------------------------------------

regEdi dd ? regEsi dd ? regEbx dd ? regEdx dd ? regEcx dd ? regEax dd ? ;--------------------------------------------------------------------------------------------------------; Esta seccin es regresada si ContextFlags contiene el valor ; CONTEXT_CONTROL ;---------------------------------------------------------------------------------------------------------regEbp dd ? regEip dd ? regCs dd ? regFlag dd ? regEsp dd ? regSs dd ? ;--------------------------------------------------------------------------------------------------------; Esta seccin es regresada si ContextFlags contiene el valor ; CONTEXT_EXTENDED_REGISTERS ;---------------------------------------------------------------------------------------------------------ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS Como puedes observar, los miembros de esta estructuras son imitaciones de los registros del procesador real. Antes de que puedas usar esta estructura, necesitas especificar cules grupos de registros quieres leer/escribir en el miembro ContextFlags. Por ejemplo, si quieres leer/escribir todos los registros, debes especificar CONTEXT_FULL en ContextFlags. Si quieres slo leer/escribir regEbp, regEip, regCs, regFlag, regEsp or regSs, debes especificar CONTEXT_CONTROL en ContextFlags. Una cosa que debes recordar cuando uses la estrucura CONTEXT: debe ser alineada en el lmite de una dword sino obtendrs extraos resultados bajo NT. Debes especificar "align dword" justo arriba de la lnea que lo declara, como este: align dword MyContext CONTEXT <>

Ejemplo:
El primer ejemplo demuestra el uso de DebugActiveProcess. Primero necesitas correr un proceso objeto llamado win.exe que va en un bucle infinito justo antes de que la ventana sea mostrada en el monitor. Luego corres el ejemplo, se anexar a win.exe y modificar el cdigo de win.exe de manera que win.exe salga del bucle infinito y muestre su ventana. .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc

include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.2",0 ClassName db "SimpleWinClass",0 SearchFail db "Cannot find the target process",0 TargetPatched db "Target patched!",0 buffer dw 9090h .data? DBEvent DEBUG_EVENT <> ProcessId dd ? ThreadId dd ? align dword context CONTEXT <> .code start: invoke FindWindow, addr ClassName, NULL .if eax!=NULL invoke GetWindowThreadProcessId, eax, addr ProcessId mov ThreadId, eax invoke DebugActiveProcess, ProcessId .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw .else invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif invoke ExitProcess, 0 end start ;-------------------------------------------------------------------; El cdigo fuente parcial de win.asm, nuestro debuggee. Es realmente ; el ejemplo de ventyana simple en el tutorial 2 con un bucle infinito ; insertado justo antes de que entre el bucle de mensajes. ;----------------------------------------------------------------------

...... mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax jmp $ <---- Aqu est nuestro bucle infinito. Ensambla a EB FE invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp

Anlisis:
invoke FindWindow, addr ClassName, NULL Nuestro programa necesita anexarse l mismo al depurando [debuggee] con DebugActiveProcess lo cual requiere el Id del proceso del depurando. Podemos obtener el Id del proceso llamando a GetWindowThreadProcessId que en cambio necesita del manejador [handle] de ventana como parmetro. As que necesitamos obtener primero el manejador de ventana. Con FindWindow, podemos especificar el nombre de la clase de ventana que necesitamos. Regresa el manejador de la ventana creada por esa clase. Si regresa NULL, no hay ninguna ventana de esa clase. .if eax!=NULL invoke GetWindowThreadProcessId, eax, addr ProcessId mov ThreadId, eax invoke DebugActiveProcess, ProcessId Despus de que obtengamos el Id del proceso, podemos llamar a DebugActiveProcess. Luego introducimos el bucle de depuracin que espere por los eventos de depuracin. .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context Cuando obtenemos CREATE_PROCESS_DEBUG_INFO, significa que el depurando est suspendido, listo para que nosotros hagamos la ciruga sobre l. En este

ejemplo, podemos sobreescribir la instruccin del bucle infinito en el depurando (0EBh 0FEh) con NOPs ( 90h 90h). Primero, necesitamos obtener la direccin de la instruccin. Puesto que el depurando est ya en el bucle por el tiempo que nuestro programa est anexo a l, eip siempre apuntar a la instruccin. Todo lo que necesitamos hacer es obtener el valor de eip. Usamos GetThreadContext para alcanzar esa meta. Ponemos el miembro ContextFlags a CONTEXT_CONTROL para decirle a GetThreadContext que queremos llenar los miembros del registro de control "control" de la estructura CONTEXT. invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL Ahora que obtenemos el valor de eip, podemos llamar a WriteProcessMemory para sobreescribir la instruccin "jmp $" con NOPs, ayudar efectivamente de esta manera a que el depurando salga del bucle infinito. Despus de que desplegamos el mensaje al usuario y luego llmamos a ContinueDebugEvent para resumir el debuggee. Puesto que la instruccin "jmp $" est sobreescrita por NOPs, el debuggee ser capaz de continuar mostrando su ventana e introducir su bucle de mensajes. La evidencia es que veremos su ventana en el monitor. El otro ejemplo usa una aproximacin al problema levemente diferente para detener [break] el debuggee fuera del bucle infinito.

....... ....... .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context add context.regEip,2 invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION ....... .......
Todava se llama a GetThreadContext para obtener el valor actual de eip pero en vez de sobreescribir la instruccin "jmp $", se incrementa en 2 el valor de regEip para "saltar por encima" ["skip over"] de la instruccin. El resultado es que cuando el archivo en depuracin [debuggee] vuelve a ganar el control, resume la ejecucin en la siguiente instruccin despus de "jmp $". Ahora puedes ver el poder de Get/SetThreadContext. Tambin puedes modificar las otras imgenes de registros y sus valores sern reflejados de regerso al depurando. Incluso puedes insertar la instruccin int 3h para poner puntos de quiebre [breakpoints] en el proceso debuggee.

Tutorial 30: API de Depuracin de Win32 Parte 3

En este tutorial, continuaremos la exploracin de la api de depuracin de win32. Especficamente, aprenderemos como trazar el depurando [debugee]. Baja el ejemplo. Historia de revisiones: 12/2/2000: Se olvid el alineamiento dword de la estructura CONTEXT

Teora:
Si haz usado antes un depurador, estars ya familiarizado con el trazado. Cuando "trazas" un programa, se detiene despus de ejecutar cada funcin, dndote la oportunidad de examinar los valores de registros/memoria. Paso simple [singlestepping] es el nombre oficial del trazado. El rasgo de paso simple [single-step] es provedo por el propio CPU. El 8vo bit de la bandera de registro es llamado la bandera bit de trampa [trap flag]. Si esta bandera (bit) est establecida, el CPU se ejecuta en modo paso simple [single-step]. El CPU generar una excepcin de depuracin despus de cada instruccin. Despus de que se genera la excepcin de depuracin, la bandera de trampa es limpiada automticamente. Tambin podemos aplicar paso simple [single-step] a los depurandos [debuggee], usando la api de win32. Los pasos son los siguientes:

1. Llamar a GetThreadContext, especificando CONTEXT_CONTROL en


ContextFlags, para obtener el valor del registro de bandera.

2. Poner el bit de trampa en el miembro regFlag de la estructura CONTEXT 3. Llamar a SetThreadContext 4. Esperar por los eventos de depuracin, como es usual. El depurando
[debuggee] se ejecutar en modo de paso simple [single-step]. Despus de que se ejecuta cada instruccin, obtendremos EXCEPTION_DEBUG_EVENT con el valor EXCEPTION_SINGLE_STEP en u.Exception.pExceptionRecord.ExceptionCode Si necesitas trazar la prxima instruccin , necesitars poner de nuevo el bit de trampa.

5.

Ejemplo:
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.4",0 ofn OPENFILENAME <>

FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 ExitProc db "The debuggee exits",0Dh,0Ah db "Total Instructions executed : %lu",0 TotalInstruction dd 0 .data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> align dword context CONTEXT <> .code start: mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .break .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION _BREAKPOINT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION _SINGLE_STEP

inc TotalInstruction invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw .endif invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread invoke ExitProcess, 0 end start

Anlisis:
El programa muestra la caja de dilogo openfile. Cuando el usuario elige un archivo ejecutable, ejecuta el programa en modo de paso simple, contando el nmero de instrucciones ejecutadas hasta que el depurando [debugee] sale a Windows.

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION _BREAKPOINT


Aprovechamos esta oportunidad para poner el depurando [debuggee] en modo de paso simple [single-step mode]. Recuerda que Windows enva un EXCEPTION_BREAKPOINT justo antes de que se ejecute la primera instruccin del depurando.

mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context


Llamamos a GetThreadContext para llenar la estructura CONTEXT con los valores actuales en los registros del depurando. Ms especficamente, necesitamos el valor actual del registro de bandera.

or context.regFlag,100h
Ponemos el bit de trampa (8vo bit) en la imagen del registro de bandera.

invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue

Luego llamamos a SetThreadContext para sobreescribir los valores en la estructura CONTEXT con el (los) nuevo(s) y llamar a ContinueDebugEvent con la bandera DBG_CONTINUE para resumir el archivo en depuracin [debuggee].

.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION _SINGLE_STEP inc TotalInstruction


Cuando se ejecuta una funcin en el archivo de depuracin, recibimos un EXCEPTION_DEBUG_EVENT. Debemos examinar el valor de u.Exception.pExceptionRecord.ExceptionCode. Si el valor es EXCEPTION_SINGLE_STEP, entonces es generado este evento de depuracin debido al modo de paso simple [single-step mode]. En este caso podemos incrementar en uno la variable TotalInstruction porque sabemos que se ha ejecutado exactamente una instruccin en el archivo bajo depuracin.

invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue
Como la bandera de trampa es limpiada despus que se genera la excepcin de depuracin, debemos poner la bandera de trampa de nuevo si queremos continuar en modo de paso simple [single-step mode]. Adevertencia: No uses el ejemplo en este tutorial con un programa muy grande: el trazado sera muy LENTO. Podras tener que esperar hasta diez minutos antes de que puedas cerrar el archivo en depuracin.

También podría gustarte