curso lenguaje ensamblador

267
Tutorial 1: Lo básico En este tutorial asumo que el lector sabe como usar MASM. Si no estás familiarizado con MASM, descarga win32asm.exe y estudia el texto dentro del paquete antes de seguir con este tutorial. Bueno. Ahora estás listo. Ok vamos ! Teoría: 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 física, sino que el programa puede direccionar cualquier dirección en ese rango. Windows hará cualquier cosa que sea necesaria para hacer que la memoria y las referencias del programas sean válidas. Por supuesto, el programa debe adherirse a las reglas impuestas por Windows, si no, causará un error de protección general. Cada programa está solo en su espacio de direcciones. Esto contrasta con la situación en Win16. Todos los programas de Win16 podían *verse* unos a otros. No es lo mismo en Win32. Esto reduce la posibilidad de que un programa escriba sobre el código/datos de otros programas. El modelo de la memoria es también drásticamente diferente al de los antiguos días del mundo de 16-bits. Bajo Win32, ya no necesitamos meternos nunca más 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 también significa que no tendrás 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 memoría. Eso es una GENIAL ayuda para los programadores. Esto hace la programación de ensamblador para Win32 tan fácil 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 también. Asi que recuerda esta regla primero: si usas cualquiera de estos cuatro registros en tu función callback, nunca olvides restaurarlos antes de regresar el control a Windows. Una función callback es una función escrita por tí que Windows llama cuando algún evento específico se produce. El ejemplo mas obvio es el procedimiento de ventana. Esto no significa

Transcript of curso lenguaje ensamblador

Page 1: curso lenguaje ensamblador

Tutorial 1: Lo básicoEn este tutorial asumo que el lector sabe como usar MASM. Si no estás familiarizado con MASM, descarga win32asm.exe y estudia el texto dentro del paquete antes de seguir con este tutorial. Bueno. Ahora estás listo. Ok vamos !

Teoría:

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 física, sino que el programa puede direccionar cualquier dirección en ese rango.

Windows hará cualquier cosa que sea necesaria para hacer que la memoria y las referencias del programas sean válidas. Por supuesto, el programa debe adherirse a las reglas impuestas por Windows, si no, causará un error de protección general. Cada programa está solo en su espacio de direcciones. Esto contrasta con la situación en Win16. Todos los programas de Win16 podían *verse* unos a otros. No es lo mismo en Win32. Esto reduce la posibilidad de que un programa escriba sobre el código/datos de otros programas.

El modelo de la memoria es también drásticamente diferente al de los antiguos días del mundo de 16-bits. Bajo Win32, ya no necesitamos meternos nunca más 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 también significa que no tendrás 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 memoría. Eso es una GENIAL ayuda para los programadores. Esto hace la programación de ensamblador para Win32 tan fácil 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 también. Asi que recuerda esta regla primero: si usas cualquiera de estos cuatro registros en tu función callback, nunca olvides restaurarlos antes de regresar el control a Windows. Una función callback es una función escrita por tí que Windows llama cuando algún evento específico se produce. El ejemplo mas obvio es el procedimiento de ventana. Esto no significa que no puedas usar estos cuatro registros; sí puedes. Solo asegúrate de restaurarlos antes de pasarle el control a Windows.

Contenido:

Aquí hay un esqueleto de un programa. Si no entiendes algo de los códigos, que no cunda el pánico. Los explicaré cada uno de ellos mas abajo.

.386

.MODEL Flat, STDCALL

.DATA    <Tu data (información) inicializada>

Page 2: curso lenguaje ensamblador

    .......DATA?   <Tu data NO inicializada>   .......CONST   <Tus constantes>   .......CODE   <Etiqueta>    <Tu código>   .....    end <Etiqueta>

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

.386Esto es una directiva para el ensamblador, que le dice que vamos a usar el conjunto de instrucciones del 80386. También puedes usar .486,.586 pero es mas seguro ponerle .386. Hay actualmente dos formas casi idénticas 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 están en modo protegido. Solamente pueden ser usadas por un código privilegiado, asi como los virtual device drivers (controladores de dispositivos virtuales = VXD).

.MODEL FLAT, STDCALL

.MODEL es una directiva para el ensamblador que especifíca el modelo de memoria de tu programa. Bajo Win32, hay un solo tipo de memoria, la PLANA(FLAT).STDCALL Indica al ensamblador la convención de paso de los parámetros. La convención de paso de parámetros especifíca la orden que debe seguirse para pasar parámetros, izquierda-a-derecha o derecha-a-izquierda, y también equilibrará la pila después de una llamada (call)

Bajo Win16, hay dos tipos de convenciones para las llamadas a funciones: C y PASCAL La convención para pasar parámetros de derecha a izquierda en cada llamada (call), es decir, el parámetro de más 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 después de la llamada (call). Por ejemplo, si vamos a llamar a una función con nombre foo(int primera_parte, int segunda_parte, int tercera_parte) en lenguaje C la convención sería así en asm: push  [tercera_parte]              ; Empuja el tercer parámetropush  [segunda_parte]            ; Seguido por el segundopush  [primera_parte]             ; Y aqui se pone de primerocall    fooadd    sp, 12                                ; La llamada equilibra el marco de la pila La convención de llamadas en PASCAL es totalmente al revés de la convención de C. Pasa los parámetros de izquierda a derecha y la rutina que llama es responsable de equilibrarse después de la llamada.

El sistema Win16 adopta la convención de PASCAL porque produce códigos más pequeños. La convención de C es eficiente cuando no sabes cuántos parámetros serán pasados a la función como es el caso de wsprintf(). En el

Page 3: curso lenguaje ensamblador

caso de wsprintf(), no hay manera de determinar cuántos parámetros serán empujados por esta función a la pila, así que no se puede hacer el balanceo de la pila.

STDCALL es la convención híbrida entre C y PASCAL. Pasa parámetros de derecha a izquierda, pero la llamada es la responsable por el balance de la pila después de la llamada. La plataforma de Win32 usa el modelo STDCALL exclusivamente. Excepto en un caso: wsprintf(). Debes usar la convención de llamada C con la función wsprintf().

.DATA

.DATA?

.CONST

.CODEEstas 4 directivas son las llamadas 'secciones'. No tienes segmentos en Win32, ¿recuerdas?, pero puedes dividir todo el espacio de direcciones en secciones lógicas. El comienzo de una sección demuestra el fin de la otra sección previa. Hay dos grupos de secciones: data y code. Las secciones Data están divididas en tres categorías:

.DATA    Esta sección contiene la información inicializada de tu programa.

.DATA?  Esta sección contiene la información no inicializada de tu programa. A veces quieres solamente prelocalizar alguna memoria pero no quieres iniciarla. Ese es el propósito de esta sección.  La ventaja de la información no inicializada es que no toma espacio en el ejecutable. Por ejemplo, si tu localizas 10,000 bytes en tu sección .DATA?, tu ejecutable no se infla 10,000 bytes. Su tamaño se mantiene muy homogéneo. Tu sólo le dices al ensamblador cuánto espacio necesitas cuando el programa se carga en la memoria, eso es todo.

.CONST  Esta sección contiene declaraciones de constantes usadas por tu programa. Las constantes nunca pueden ser modificadas en tu programa. Sólo son *constantes*

No tienes que usar las tres secciones en tu programa. Declara solo la(s) sección(es) que quieres usar.

Existe solo una sección para el código (code):.CODE. Aquí es donde tu código reside.<etiqueta>end <etiqueta>Donde está la <etiqueta> se puede usar cualquier nombre como etiqueta para especificar la extensión de tu código.. Ambas etiquetas deben ser idénticas. Todos tus códigos 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í.

Teoría:

Page 4: curso lenguaje ensamblador

Windows tiene preparado una gran cantidad de recursos para sus programas. En el centro de esta concepción se ubica la API (Application Programming Interface = Interface de Programación de Aplicaciones) de Windows. La API de Windows es una enorme colección de funciones muy útiles que residen en el propio sistema Windows, listas para ser usadas por cualquier programa de Windows. Estas funciones están almacenadas en varias librerías de enlace dinámico [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 gráficas. Además de estas "tres funcones principales", hay otras DLLs que nuestros programas pueden emplear, siempre y cuando tengas la suficiente información sobre las funciones de la API que te interesan.

Los programas de Windows se enlazan dinámicamente a estas DLLs, es decir, las rutinas de las funciones de la API no están incluidas en el archivo ejecutable del programa de Windows. Con el fin de que tu programa pueda encontrar en tiempo de ejecución las funciones de la API deseadas, tienes que meter esa información dentro del archivo ejecutable. Esta información se encuentra dentro de archivos .LIB. Debes enlazar tus programas con las librerías de importación correctas o no serán capaces de localizar las funciones de la API.

Cuando un programa de Windows es cargado en la memoria, Windows lee la información almacenada en el programa. Esa información incluye el nombre de las funciones que el programa usa y las DLLs donde residen esas funciones. Cuando Windows encuentra esa información 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 función correcta.

Hay dos categorías 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 tamaño de 1 byte. Si bien el código ANSI es suficiente para los lenguajes europeos, en cambio no puede manejar algunos lenguajes orientales que tienen millares de caracteres únicos. Esa es la razón por la cual apareció UNICODE. Un caracter UNICODE tiene un tamaño de 2 bytes, haciendo posible tener 65536 caracteres únicos en las cadenas.

Sin embargo, la mayoría de las veces, usarás un archivo include que puede determinar y seleccionar las funciones de la API apropiadas para tu plataforma. Sólo 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 ejecución se inicia inmediatamente después de la etiqueta especificada después de la directiva end. En el esqueleto de arriba, la ejecución iniciará en la primera instrucción inmediatamante debajo de la etiqueta start. Le ejecución procederá instrucción por instrucción hasta encontrar algunas instrucciones de control de flujo tales como jmp, jne, je, ret etc. Esas

Page 5: curso lenguaje ensamblador

instrucciones redirijen el fujo de ejecución a algunas otras instrucciones. Cuando el programa necesite salir a Windows, deberá llamar a una fucnión de la API, ExitProcess.

ExitProcess proto uExitCode:DWORD

A la línea anterior la llamamos prototipo de la función. Un prototipo de función define los atributos de una función para que el ensamblador/enlazador pueda tipear y chequear para tí. El formato de un prototipo de función es:

FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...

En pocas palabras, el nombre de la función seguido por la palabra clave PROTO y luego por la lista de tipos de datos de los parámetros, separados por comas. En el ejemplo de arriba de ExitProcess, se define ExitProcess como una función que toma sólo un parámetro 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í. Sólo lo notarás 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 evitarás el error. Recomiendo que uses invoke en vez de call. La sintaxis de invoke es como sigue:

INVOKE  expresión [,argumentos]

expresión puede ser el nombre de una función o el nombre de un puntero de función. Los parámetros de la función están 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 encontrarás en la carpeta MASM32/include. Los archivos include tienen extensión .inc y los prototipos de función 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 función para ExitProcess está almacenado en kernel32.inc.

También puedes crear prototipos para tus propias funciones.

A través de mis ejemplos, usaré windows.inc de hutch que puedes bajar desde http://win32asm.cjb.net

Ahora regresemos a ExitProcess, el parámetro uExitCode es el valor que quieres que el programa regrese a Windows después de que el programa termina. Puedes llamara ExitProcess de esta manera:

invoke ExitProcess, 0

Pon esa línea inmediatamente abajo de la etiqueta de inicio, y obtendrás un programa win32 que saldrá inmediatamente a Windows, pero no obstante será un programa válido.

Page 6: curso lenguaje ensamblador

.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 opción casemap:none dice a MASM que haga a las etiquetas sensibles a mayusculas-minusculas, 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 línea 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 programación de win32. No contiene ningún prototipo de función. windows.inc es totalmente incomprensible. hutch y yo tratamos de poner tantas constantes y estructuras como sea posible pero quedan todavía muchas por incluir. Será constantemente actualizada. Chequea el homepage de hutch o el mío por actualizaciones.

De windows.inc, tus programas obtendrán las definiciones de constantes y estructuras. Pero para los prototipos de la funciones, necesitarás incluir otros archivos include. Puedes generar a partir de librerías de importación los archivos include que contienen sólo prototipos de funciones. Esbozaré ahora los pasos para generar los archivos include:

1. Baja las librerías para el paquete MASM32 del homepage de hutch o del mío. Contiene una colección de librerías de importación que necesitas para programar para win32. también baja la utilidad l2inc.

2. Desempaca (unzip) ambos paquetes dentro del mismo archivo. Si instalaste MASM32, desempácalos dentro del directorio MASM32\Lib

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

l2inca /M *.lib

l2inca.exe extraerá información de las librerías de importación 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 deberán moverlos a la carpeta MASM32\include.

En nuestro ejemplo, llamamos la función 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, verás que está lleno de prototipos de funciones de kernel32.dll. Si no incluyes kernel32.inc, puedes llamar todavía a call ExitProcess pero sólo con una sintaxis simple de llamada. No podrás usar invoke para "invocar" la función. El punto aquí es: para invocar una función, tienes que poner el prototipo de la función en alguna parte del código fuente. En el ejemplo anterior, si incluyes kernel32.inc, puedes definir los prototipos de funciones para

Page 7: curso lenguaje ensamblador

ExitProcess en cualquier parte del código fuente antes del comando invoke para que trabaje. Los archivos include están 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 librerías 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 cuáles librerías necesita importar tu programa que deben ser enlazadas. Sin embargo, no estás obligado a usar includelib. Puedes especificar los nombres de las librerías de importación en la línea de comando del enlazador pero, créeme, es tedioso y la línea de comando sólo 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

/c dice a MASM que sólo ensamble, que no invoque a link.exe. Muchas veces, no querrás llamar automáticamente a link.exe ya que quizás tengas que ejecutar algunas otras tareas antes de llamar a link.exe.

/coff dice a MASM que cree un archivo objeto en formato COFF. MASM usa una variación de COFF (Common Object File Format = Formato de Archivo Objeto Común) 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 mayúsculas y minúsculas de los identificadores usados. Si usas el paquete MASM32 de hutch, puedes poner "option casemap:none" en la cabeza del código fuente, justo debajo de la directiva .model para alcanzar el mismo efecto.

Después de haber ensamblado satisfactoriamente msgbox.asm, obtendrás msgbox.obj. msgbox.obj es un archivo objeto. Un archivo objeto está a sólo un paso del archivo ejecutable. Contiene las instrucciones/datos en forma binaria. Sólo 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 dónde se encuentran las librerías de importación. Si usas MASM32, estarán en el archivo MASM32\lib.

Link lee en el archivo objeto y lo fija con las direcciones de las librerías de importación. Cuando el proceso termina obtienes msgbox.exe.

Obtienes msgbox.exe. Vamos, córrelo. Encontrarás que no hace nada. Bien, todavía no hemos puesto nada interesante en él. Sin embargo, es un programa de Windows. ¡Y mira su tamaño! En mi PC, es de 1,536 bytes.

Vamos a ponerle ahora una caja de mensaje [Dialog Box]. Su prototipo de función 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 número que representa la ventana a la cual te refieres. Su valor no es tan importante para tí. Sólo

Page 8: curso lenguaje ensamblador

recuerda que representa la ventana. Cuando quieres hacer algo con la ventana, debes referirte a ella por su manjador. lpText es el puntero al texto que quieres desplegar en el área cliente de la caja de mensaje. En realidad, un puntero es la dirección de lago: Puntero a cadena de texto==Dirección de esa cadena. lpCaption es un puntero al encabezado de la caja de mensaje uType especifica el icono, el número 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

Ensámblalo y córrelo. Verás un cuadro de mensaje desplegando el texto "Win32 Assembly is Great!".

Veamos de nuevo el código fuente.

Definimos dos cadenas terminadas en cero en la sección .data. Recuerda que toda cadena ANSI en Windows debe terminar en NULL (0 hexadecimal).

Usamos dos constantes, NULL y MB_OK. Esas constantes están documentadas en windows.inc. Así que nos referiremos a ellas por nombres y no por valores. Esto facilita la lectura de nuestro código fuente.

El operador addr es usado para pasar la dirección de una etiqueta a la función. Es válido sólo en el contexto de la directiva invoke. No puedes usarla para asignar la dirección 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 más adelante del código fuente que la directiva invoke, entonces addr no trabajará.

invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK ......

Page 9: curso lenguaje ensamblador

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 código 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 algún lugar de la pila. No conocerás su dirección durante el tiempo de ejecución. 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 dirección 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 función:

lea eax, LocalVar push eax

Puesto que "lea" puede determinar la dirección de una etiqueta en tiempo de ejecución, 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í

Teoría:

Los programas de Windows realizan la parte pesada del trabajo de programación a través funciones API para sus GUI (Graphic User Interface = Interface de Usuario Gráfica). Esto beneficia a los usuarios y a los programadores. A los ususarios, porque no tienen que aprender cómo navegar por la GUI de cada nuevo programa, ya que las GUIs de los programas Windows son semejantes. A los programadores, porque tienen ya a su disposición las rutinas GUI, probadas y listas para ser usadas. El lado negativo para los programadores es la creciente complejidad involucrada. Con el fin de crear o de manipular cualquiera de los objetos GUI, tales como ventanas, menúes o iconos, los programadores deben seguir un "récipe" estricto. Pero esto puede ser superado a través de programación modular o siguiendo el paradigma de Progranación orientada a Objetos (OOP = Object Oriented Programming).

Esbozaré los pasos requeridos para crear una ventana sobre el escritorio:

1. Obtener el manejador [handle] de instancia del programa (requerido) 2. Obtener la línea de comando (no se requiere a menos que el programa vaya a

procesar la línea 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 diálogo) 4. Crear la ventana (requerido) 5. Mostrar la ventana en el escritorio (requerido al menos que se quiera mostrar la

ventana inmediatamente)

Page 10: curso lenguaje ensamblador

6. Refrescar el área cliente de la ventana 7. Introducir un bucle (loop) infinito, que chequée los mensajes de Windows 8. Si llega un mensaje, es procesado por una función 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 más 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 pacíficamente uno junto a otro. Por eso deben seguir reglas estrictas. Tú (o Usted), como programador, debes ser más estricto con tus estilos y hábitos de programación.

Contenido:

Abajo está el código fuente de nuestro programa de ventana simple. Antes de entrar en los sangrientos detalles de la programación Win32 ASM, adelantaré algunos puntos delicados que facilitarán la programación.

Se deberían 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 más completo para MASM es windows.inc de hutch, el cual puedes bajar desde su página o mi página. Puedes definir tus propias constantes y estructuras pero deberías ponerlas en un archivo include separado.

Usa la directiva includelib para especificar la librería de importación usada en tu programa. Por ejemplo, si tu programa llama a MessageBox, deberías poner la línea:

  includelib user32.lib

al comienzo de tu archivo .asm. Esta directiva dice a MASM que tu programa hará uso de funciones es esa librería de importación. Si tu programa llama funciones en más de una librería, entonces hay que agregar una línea includelib para cada librería que se vaya a usar. Usando la directiva includelib no tendrás que preocuparte de las librerías de importación en el momento de enlazar. Puedes usar el conmutador del enlazador /LIBPATH para decirle a Link donde están todas las librerías.

Cuando declares prototipos de funciones de la API, estructuras, o constantes en un archivo include, trata de emplear los nombres originales usados en archivos include de Windows, cuidando siempre la diferencia entre mayúsculas y minúsculas. Esto te liberará de dolores de cabeza cuando necesites buscar información sobre algún elemento en la referencia de la API de Win32.

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

.386

.model flat,stdcall option casemap:none

Page 11: curso lenguaje ensamblador

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        ; el nombre de nuestra clase de ventana AppName db "Our First Window",0        ; el nombre de nuestra ventana

.DATA?                                           ; data no inicializada hInstance HINSTANCE ?              ; manejador de instancia de nuestro programa CommandLine LPSTR ? .CODE                                             ; Aquí comienza nuestro código start: invoke GetModuleHandle, NULL ; obtener el manejador de instancia del programa.                                                          ; En Win32, hmodule==hinstance

mov hInstance,eax invoke GetCommandLine    ; Obtener la línea de comando. No hay que llamar esta función                                               ; si el programa no procesa la línea de comandomov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT   ; llamar la función principal invoke ExitProcess      ; quitar nuestro programa. El código de salida es devuelto en eax desde WinMain.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD     LOCAL wc:WNDCLASSEX                  ; crear variables locales en la pila (stack)

    LOCAL msg:MSG     LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX      ; Llenar los valores de los miembros de wc     mov   wc.style, CS_HREDRAW or CS_VREDRAW     mov   wc.lpfnWndProc, OFFSET WndProc     mov   wc.cbClsExtra,NULL     mov   wc.cbWndExtra,NULL     push  hInstance     pop   wc.hInstance     mov   wc.hbrBackground,COLOR_WINDOW+1     mov   wc.lpszMenuName,NULL     mov   wc.lpszClassName,OFFSET ClassName

Page 12: curso lenguaje ensamblador

    invoke LoadIcon,NULL,IDI_APPLICATION     mov   wc.hIcon,eax     mov   wc.hIconSm,eax     invoke LoadCursor,NULL,IDC_ARROW     mov   wc.hCursor,eax     invoke RegisterClassEx, addr wc                       ; registrar nuestra clase de ventana     invoke CreateWindowEx,NULL,\                 ADDR ClassName,\                 ADDR AppName,\                 WS_OVERLAPPEDWINDOW,\                 CW_USEDEFAULT,\                 CW_USEDEFAULT,\                 CW_USEDEFAULT,\                 CW_USEDEFAULT,\                 NULL,\                 NULL,\                 hInst,\                 NULL     mov   hwnd,eax     invoke ShowWindow, hwnd,CmdShow     ; desplegar nuestra ventana en el escritorio     invoke UpdateWindow, hwnd                   ; refrescar el area cliente

    .WHILE TRUE                                         ; Introducir en bucle (loop) de mensajes                 invoke GetMessage, ADDR msg,NULL,0,0                 .BREAK .IF (!eax)                 invoke TranslateMessage, ADDR msg                 invoke DispatchMessage, ADDR msg    .ENDW     mov     eax,msg.wParam                             ; Regresar el código de salida en eax     ret WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM     .IF uMsg==WM_DESTROY                      ; si el usuario cierra nuestra ventana         invoke PostQuitMessage,NULL             ; quitar nuestra aplicación     .ELSE         invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Procesar el mensaje por defecto        ret     .ENDIF     xor eax,eax     ret WndProc endp

end start

Análisis:

Page 13: curso lenguaje ensamblador

Puede parecer desconcertante que un simple programa de Windows requiera tanto código. Pero muchas de estas rutinas son verdaderamente un código *plantilla* que puede copiarse de un archivo de código fuente a otro. O si prefieres, podrías ensamblar algunas de estas rutinas en una librería para ser usadas como rutinas de prólogo o de epílogo. Puedes escribir solamente las rutinas en la función WinMain. En realidad, esto es lo que hacen los compiladores en C: permiten escribir las rutinas en WinMain sin que tengas que preocuparte de otros asuntos ruitinarios y domésticos. Todo lo que hay que hacer es tener una función llamada WinMain, sino los compiladores C no serán capaces de combinar tus rutinas con el prólogo y el epílogo. No exiten estas restricciones con lenguaje ensamblador. Puedes usar otros nombres de funciones en vez de WinMain o no emplear esta función en ninguna parte.

Bueno, ahora prepárate. Esto va a ser largo, un largo tutorial ¡Vamos a analizar este programa hasta la muerte!!

.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 líneas son "necesarias". .386 dice a MASM que intentamos usar en nuestro programa el conjunto de instrucciones para los porcesadores 80386 o superiores. .model flat,stdcall a MASM que nuestro programa usa el modelo de direccionamiento de memoria plana (flat). También usaremos la convención de paso de parámetros stdcall como la convención por defecto del programa.

Lo siguiente constituye el prototipo para la función WinMain. Ya que llamaremos más tarde a WinMain, debemos definir su prototipo de función primero para que podamos invocarle. Debemos incluir windows.inc al comienzo del codigo fuente. Contiene estructuras y constantes importantes que son usadas por nuestro programa. El archivo include, windows.inc, es un archivo de texto. Puedes abrirlo con cualquier editor de texto. Por favor, nota que windows.inc no contiene todas las estructuras y constantes (todavía). hutch y yo estamos trabajando en ello. Puedes agregarle elementos nuevos si ellos no están en el archivo.

Nuestro programa llama las funciones API que residen en user32.dll (CreateWindowEx, RegisterWindowClassEx, por ejemplo) y kernel32.dll (ExitProcess), así que debemos enlazar nuestro programa a esas librerías de importación. La próxima cuestión es: ¿cómo podemos saber cuál librería debe ser enlazada con nuestro programa? La respuesta es : debes saber donde residen las funciones API llamadas por el programa. Por ejemplo, si llmas una función API en gdi32.dll, debes enlazarla con gdi32.lib.

Page 14: curso lenguaje ensamblador

Esta es la manera como lo hace MASM. El método de TASM para importar librerías a través del enlace es mucho más simple: sólo hay que enlazar un archivo : import32.lib.

.DATA     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 están inicializadas.

En .DATA?, son declaradas tres variables: hInstance (manejador [handle] de instancia de nuestro programa), CommandLine (linea de comando de nuestro programa), y CommandShow (estado de nuestro programa en su primera aparición). Los tipos no familiares de datos, HINSTANCE y LPSTR, realmente son nombres nuevos para DWORD. Puedes verificarlo en windows.inc. Nota que todas las variables en la sección .DATA? no están inicializadas, es decir, no tienen que tener ningún valor especíifico al inicio, pero queremos reservarle un espacio para su usarlas en el futuro.

.CODE  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 instrucción llama a GetModuleHandle para recuperar el manejador de instancia de nuestro programa. Bajo Win32, el manejador de la instancia y el manejador del módulo son una y la misma cosa. Se puede pensar en el manejador de instancia como el ID de nuestro programa. Es usado como parámetro en algunas funciones API que nuestro programa debe llamar, así que generalmente es una buena idea obtenerlo al comienzo del programa.

Page 15: curso lenguaje ensamblador

Nota: Realmente bajo win32, el manejador de instancia es la dirección lineal de nuestro programa en la memoria.

Al regresar a una función de Win32, el valor regresado, si hay alguno, puede encontrarse en el registro eax. Todos los demás valores son regresados a través de variables pasadas en la lista de parámetros de la función que va a ser llamada.

Cuando se llama a una función Win32, casi siempre preservará los registros de segmento y los registros ebx, edi, esi y ebp. Al contrario, los registros eax, ecx y edx son considerados como registros dinámicos y siempre sus valores son indeterminados e impredecibles cuando retorna una función Win32.

Nota: No esperes que los valores de eax, ecx, edx sean preservados durante las llamadas a una función API.

La línea inferior establece que: cuando se llama a una fucnión API, se espera que regrese el valor en eax. Si cualquiera de las funciones que creamos es llamada por Windows, también debe seguir la siguiente regla: preservar y restablecer los valores de los registros de segmentos ebx, edi, esi y ebp cuando la función regrese, sino el programa se quebrará (crash) de inmediato, esto incluye el procedimiento de ventana y las funciones callback de ventanas.

La llamada a GetCommandLine es inecesaria si el programa no procesa la línea de comando. En este ejemplo muestro como llamarla en caso que sea necesario en un programa.

Lo siguiente es la llamada a WinMain. Aquí recibe cuatro parámetros: el manejador de instancia de nuestro programa, el manejador de instancia de la instancia previa del programa, la línea de comando y el estado de la ventana en su primera aparición. Bajo Win32, no hay instancia previa. Cada programa está aislado en su espacio de direcciones, así que el valor de hPrevInst siempre es 0. Esto es uno de los restos de los días de Win16 cuando todas las instancias de un programa corrían en el mismo espacio de direcciones y una instancia necesitaba saber si era la primera. En win16, si hPrevInst es NULL entonces es la primera instancia.

Nota: Esta función no tiene que ser declarada como WinMain. En realidad, hay completa libertad a este respecto. Ni siquiera hay que usar siempre una función equivalente a WinMain. Se puede pegar el código dentro de la función WinMain inmediatamente después de GetCommandLine y el programa funcionará perfectamente.

Al regresar de WinMain, eax tiene el código de salida. Pasamos el código de salida como parámetro de ExitProcess, que terminará nuestra aplicación.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

La línea de arriba forma la declaración de la función WinMain. Nota que los pares parámetro:tipo que siguen a la directiva PROC. Son parámetros queWinMain recibe desde la instrucción que hace la llamada [caller]. Puedes referirte a estos parámetros por nombre en vez de a través de la manipulación de la pila. Además, MASM generará los

Page 16: curso lenguaje ensamblador

códigos de prólogo y epílogo para la función. Así que no tenemos que preocuparnos del marco de la pila cuando la función entre (enter) y salga (exit).

    LOCAL wc:WNDCLASSEX     LOCAL msg:MSG     LOCAL hwnd:HWND

La directiva LOCAL localiza memoria de la pila para las variables locales usadas en la función. El conjunto de directivas LOCAL debe estar ubicado inmediatamente abajo de la directiva PROC.

La directiva LOCAL es seguida inmediatamente por <nombre de la variable local>:<tipo de variable>. Así que LOCAL wc:WNDCLASSEX le dice a MASM que localize memoria de la pila con un espacio equivalente al tamaño de la estructura WNDCLASSEX para la variable llamada wc. Podemos hacer referencia a wc en nuestro código sin ninguna dificultad en la manipulación de la pila. Creo que esto es realmente una bendición. El aspecto negativo de esto es que las variables locales no pueden ser usadas fuera de la función porque ellas son creadas para ser destruidas inmediatamante cuando la función retorna a la rutina desde la cual fue llamada. Otra contrapartida es que no se pueden inicializar variables locales automáticamente porque ellas son localizadas dinámicamente en la memoria de la pila cuando la función es introducida (entered). Hay que asignarlas manualmente con los valores deseados después de las directivas LOCAL.

    mov   wc.cbSize,SIZEOF WNDCLASSEX     mov   wc.style, CS_HREDRAW or CS_VREDRAW     mov   wc.lpfnWndProc, OFFSET WndProc     mov   wc.cbClsExtra,NULL     mov   wc.cbWndExtra,NULL     push  hInstance     pop   wc.hInstance     mov   wc.hbrBackground,COLOR_WINDOW+1     mov   wc.lpszMenuName,NULL     mov   wc.lpszClassName,OFFSET ClassName     invoke LoadIcon,NULL,IDI_APPLICATION     mov   wc.hIcon,eax     mov   wc.hIconSm,eax     invoke LoadCursor,NULL,IDC_ARROW     mov   wc.hCursor,eax     invoke RegisterClassEx, addr wc

Las líneas intimidantes de arriba son realmente comprensibles en cuanto concepto. Toma varias líneas de instrucciones realizar la operación ahí implicada. El concepto detrás de todas estas líneas es el de clase de ventana (window class). Una clase de ventana no es más que un anteproyecto o especificación de una ventana. Define algunas de las características importantes de una ventana tales como un icono, su cursor, la función que se resposabiliza de ella, su color etc. Una ventana se crea a partir de una clase de ventana. Este es una especie de concepto orientado a objeto. Si se quiere crear más de una ventana con las mismas características, lo razonable es almacenar todas estas características en un solo lugar y referirse a ellas cuando sea necesario. Este esquema salva gran cantidad de memoria evitando duplicación de código. Hay que

Page 17: curso lenguaje ensamblador

recordar que Windows fue diseñado cuando los chips de memoria eran prohibitivos ya que una computadora tenía apenas 1 MB de memoria. Windows debía ser muy eficiente al usar recursos de memorias escasos. El punto es: si defines tu propia ventana, debes llenar las características de tu ventana en una estructura WNDCLASS o WNDCLASSEX y llamar a RegisterClass o RegisterClassEx antes de crear la ventana. Sólo hay que registrar la clase de ventana una vez para cada tipo de ventana que se quiera crear desde una clase de ventana.

Windows tiene varias clases de ventanas predefinidas, tales como botón (button) y caja de edición (edit box). Para estas ventanas (o controles), no tienes que registrar una clase de venana, sólo hay que llamara a CreateWindowEx con el nombre de la clase predefinido.

El miembro más importante en WNDCLASSEX es lpfnWndProc. lpfn se concibe como un puntero largo a una función. Bajo Win32, no hay puntero "cercano" o "lejano" pointer, sino sólo puntero, debido al nuevo modelo de memoria FLAT. Pero esto también es otro de los restos de los días de Win16. Cada clase de ventana debe estar asociada con la función llmada procedimiento de ventana. El procedimiento de ventana es la función responsable por el manejo de mensajes de todas las ventanas creadas a partir de la clase de ventana asociada. Windows enviará mensajes al procedimiento de ventana para notificarle sobre eventos importantes concernientes a la ventana de la cual el procedimiento es responsable, tal como el uso del teclado o la entrada del ratón. Le toca al procedimiento de ventana responder inteligentemante a cada evento que recibe la ventana. Seguro que pasarás bastante tiempo escribiendo manejadores de evento en el procedimiento de ventana.

Describo abajo los miembros de WNDCLASSEX:

WNDCLASSEX STRUCT DWORD   cbSize            DWORD      ?   style             DWORD      ?   lpfnWndProc       DWORD      ?   cbClsExtra        DWORD      ?   cbWndExtra        DWORD      ?   hInstance         DWORD      ?   hIcon             DWORD      ?   hCursor           DWORD      ?   hbrBackground     DWORD      ?   lpszMenuName      DWORD      ?   lpszClassName     DWORD      ?   hIconSm           DWORD      ? WNDCLASSEX ENDS

cbSize: Tamaño de la estructura WNDCLASSEX en bytes. Podemos usar el operador SIZEOF para obtener este valor. style: El estilo para las ventanas creadas a partir de esta clase. Se pueden combinar varios tipos de estilo combinando el operador "or". lpfnWndProc: La dirección del procedimiento de ventana responsable para las ventanas creadas a partir de esta clase. cbClsExtra: Especifica el número de bytes extra para localizar la siguiente estructura

Page 18: curso lenguaje ensamblador

de clase de ventana. El sistema operativo inicializa los bytes poniéndolos en cero. Puedes almacenar aquí datos específcos de la clase de ventana. cbWndExtra: : Especifica el número de bytes extra para localizar the window instance. El sistema operativo inicializa los bytes poniéndolos en cero. Si una aplicación usa la estructura WNDCLASS para registrar un cuadro de diálogo creado al usar la directiva CLASS en el archivo de recurso, debe poner este miembro en DLGWINDOWEXTRA. hInstance: Manejador de instancia del módulo. hIcon: Manejador [handle] del icono. Se obtiene llamando a LoadIcon. hCursor: Manejador del cursor. Se obtiene llamando a LoadCursor. hbrBackground: Color de fondo de la ventana creada a partir de esta clase. lpszMenuName: Manejador del menú por defecto para la ventana creada a partir de esta clase. lpszClassName: Nombre para esta clase de ventana. hIconSm: Manejador del icono pequeño asociado con la clse de ventana. Si este miembro es NULL, el sistema busca el recurso de icono especificado por el miembro hIcon para un icono de tamaño apropiado para ser usado como icono pequeño.

    invoke CreateWindowEx, NULL,\                                                 ADDR ClassName,\                                                 ADDR AppName,\                                                 WS_OVERLAPPEDWINDOW,\                                                 CW_USEDEFAULT,\                                                 CW_USEDEFAULT,\                                                 CW_USEDEFAULT,\                                                 CW_USEDEFAULT,\                                                 NULL,\                                                 NULL,\                                                 hInst,\                                                 NULL

Después de registrar la clase de ventana, podemos llamar a CreateWindowEx para crear nuestra ventana basada en la clase de ventana propuesta. Nota que hay 12 parámetros para esta función.

CreateWindowExA proto dwExStyle:DWORD,\    lpClassName:DWORD,\    lpWindowName:DWORD,\    dwStyle:DWORD,\    X:DWORD,\    Y:DWORD,\    nWidth:DWORD,\    nHeight:DWORD,\    hWndParent:DWORD ,\    hMenu:DWORD,\    hInstance:DWORD,\    lpParam:DWORD

Veamos la descripción detallada de cada parámetro:

Page 19: curso lenguaje ensamblador

dwExStyle: Estilos extra de ventana. Es el nuevo parámetro agregado a la antigua función CreateWindow. Aquí puedes poner estilos nuevos para Windows 95 y NT. Puedes especificar tu estilo de ventana ordinario en dwStyle pero si quieres algunos estilos especiales tales como "topmost window" (nventana en el tope), debes especificarlos aquí. Puedes usar NULL si no quieres usar estilos de ventana extra.

lpClassName: (Requerido). Dirección de la cadena ASCIIZ que contiene el nombre de la clase de ventana que quieres usar como plantilla para esta ventana. La Clase puede ser una clase registrada por tí mismo o una clase de ventana predefinida. Como se estableció arriba, todas las ventanas que creas deben estar basadas en una clase de ventana.

lpWindowName: Dirección de la cadena ASCIIZ que contiene el nombre de la ventana. Será mostrada en la barra de título de la ventana. Si este parámetro es NULL, la barra de título de la ventana aparecería en blanco.

dwStyle:  Estilos de la ventana. Aquí puedes especificar la apariencia de la ventana. Pasar NULL  pero la ventana no tendrá el menú de sistema, ni botones minimizar-maximizar, y tampoco el botón cerrar-ventana. La ventana no sería de mucha utilidad. Necesitarás presionarAlt+F4 para cerrarla. El estilo de ventana más común es WS_OVERLAPPEDWINDOW. UN estilo de ventana sólo es una bandera de un bit. Así que puedes combinar varios estilos usando el operador "or" para alcanzar la apariencia deseada de la ventana. El estilo WS_OVERLAPPEDWINDOW es realmante la combinación de muchos estilos de ventana comunes empleando este método.

X,Y: La coordenada de la esquina izquierda superior de la vetana. Noramlamente este valor debería ser CW_USEDEFAULT, es decir, deseas que Windows decida por tí dónde poner la ventana en el escritorio.

nWidth, nHeight: El ancho y el alto de la ventana en pixeles. También puedes usar CW_USEDEFAULT para dejar que Windows elija por tí el ancho y la altura apropiada.

hWndParent: El manejador [handle] de la ventana padre de la ventana (si existe). Este parámetro dice a Windows si esta ventana es una hija (subordinada) de alguna otra ventana y, si lo es, cual ventana es el padre. Nota que no se trata del tipo de interrelación padre-hija de la MDI (Multiple Document Interface = Interface de Documento Múltiple). Las ventanas hijas no están restringidas a ocupar el área cliente de la ventana padre. Esta interrelación es únicamente para uso interno de Windows. Si la ventana padre es destruida, todas las ventanas hijas serán destruidas automáticamente. Es realmente simple. Como en nuestro ejemplo sólo hay una ventana, especificamos este parámetro como NULL.

hMenu: Manejador del menú de la ventana. NULL si la clase menú va a ser usada. Observa el miembro de la estructura WNDCLASSEX, lpszMenuName. lpszMenuName especifica el menú *por defecto* para la clase de ventana. Toda ventana creada a partir de esta clase de ventana tendrá por defecto el mismo menú, a menos que especifiques un nuevo menú *imponiéndolo* a una ventana específica a través de su parámetro hMenu. hMenu es realmente un parámetro de doble propósito. En caso de que la ventana que quieras crear sea de un tipo predefinido (como un control), tal control no puede ser propietario de un menú. hMenu es usado entonces más bien como un ID de control.

Page 20: curso lenguaje ensamblador

Windows puede decidir si hMenu es realmente un manejador de menú o un ID de control revisando el parámetro lpClassName. Si es el nombre de una clase de ventana predefinida, hMenu es un ID de control, sino entonces es el manejador del menú de la ventana.

hInstance: El manejador de instancia para el módulo del programa que crea la ventana.

lpParam: Puntero opcional a la estructura pasada a la ventana. Es usada por la ventana MDI para pasar los datos CLIENTCREATESTRUCT. Normalmente, este valor es puesto en NULL, que significa que ningún dato es pasado vía CreateWindow(). La ventana puede recibir el valor de este parámetro llamando a la función GetWindowLong.

    mov   hwnd,eax     invoke ShowWindow, hwnd,CmdShow     invoke UpdateWindow, hwnd

El regreso satisfactorio de CreateWindowEx, devuelve el manejador de ventana en eax. Debemos conservar este valor para usarlo luego. La ventana que creamos no es desplegada inmediatamente. Debes llamar a ShowWindow con el manejador de ventana y el *estado de despliegue* deseado para la ventana y desplegarla sobre el monitor. Luego puedes llamara a UpdateWindow con el fin de que tu ventana vuelva a dibujarse sobre el area cliente. Esta función es útil cuando se desea actualizar el contenido del area cliente. Aunque puedes omitir esta llamada.

    .WHILE TRUE                 invoke GetMessage, ADDR msg,NULL,0,0                 .BREAK .IF (!eax)                 invoke TranslateMessage, ADDR msg                 invoke DispatchMessage, ADDR msg    .ENDW

En este momento nuestra ventana está desplegada en la pantalla. Pero no puede recibir entrada del mundo exterior. Así que tendremos que *informarle* de los eventos relevantes. Hacemos esto con un bucle de mensajes. Sólo hay un bucle de mensaje para cada módulo. Este bucle de mensaje chequea continuamente los mensajes de Windows llamando a GetMessage. GetMessage pasa a Windows un puntero a una estructura MSG. Esta estructura MSG será llenada con información sobre el mensaje que Windows quiere enviar a una ventana en el módulo. La función GetMessage no regresará hasta que haya un mensaje para una ventana en el módulo. Durante ese tiempo, Windows puede darle el control a otros programas. Esto es lo que forma el esquema de multitareas cooperativas de la plataforma Win16. GetMessage regresa FALSE si el mensaje WM_QUIT es recibido en el bucle de mensaje, lo cual terminará el programa y cerrará la aplicación.

TranslateMessage es una útil función que toma la entrada desde el teclado y genera un nuevo mensaje (WM_CHAR) que es colocado en la cola de mensajes. El mensaje WM_CHAR viene acompañado del valor ASCII de la tecla presionada, el cual es más fácil de manipular que los códigos brutos de lectura de teclado. Se puede omitir esta llamada si el programa no procesa los golpes de tecla.

Page 21: curso lenguaje ensamblador

DispatchMessage envía los datos del mensaje al procedimiento de ventana responsable por la ventana específica a la cual va dirigido el mensaje.

    mov     eax,msg.wParam     ret WinMain endp

Si termina el bucle de mensaje, el código de salida es almacenado en el miembro wParam de la estructura MSG. Puedes almacenar el código de salida en eax para regresarlo a Windows. Para estos momentos, Windows no usa el valor regresado, pero es mejor hacerlo por si acaso y para jugar con las reglas.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

Este es nuestro procedimiento de ventana. No tienes que llamarlo necesariamente WndProc. El primer parámetro, hWnd, es el manejador de la ventana hacia el cual el mensaje está destinado. uMsg es el mensaje. Nota que uMsg no es una estructura MSG. Realmente sólo es un número. Windows define cientos de mensajes, muchos de los cuales carecen de interés para nuestro programa. Windows enviará un mensaje apropiado a la ventana en caso de que ocurra algo relevante a la ventana. El procedimiento de ventana recibe el mensage y recciona a él inteligentemente. wParam y lParam sólo son parámetros extra a ser utiizados por algunos mensajes. Algunos mensajes envían datos junto con ellos en adición al mensaje propiamente dicho. Estos datos son pasados al procedimiento de ventana por medio de lParam y wParam.

    .IF uMsg==WM_DESTROY         invoke PostQuitMessage,NULL     .ELSE         invoke DefWindowProc,hWnd,uMsg,wParam,lParam         ret     .ENDIF     xor eax,eax     ret WndProc endp

Aquí viene la parte crucial. Es donde reside gran parte de la inteligencia de los programas. El código que responde a cada mensaje de Windows está en el procedimiento de ventana. El código debe chequear el mensaje de Windows para ver si hay un mensaje que sea de interés. Si lo es, se hace algo que se desee en respuesta a ese mensaje y luego se regresa cero en eax. Si no es así, debe llamarse a DefWindowProc, pasando todos los parámetros recibidos para su procesamiento por defecto. DefWindowProc es una función de la API que procesa los mensajes en los que tu programa no está interesado.

El único mensaje a que DEBES responder es WM_DESTROY. Este mensaje es enviado a tu procedimiento de ventana cada vez que la ventana se va a cerrar. En el momento que tu procedimiento de diálogo reciba este mensaje, tu ventana estará ya removida del escritorio. Este mensaje sólo es una notificación de que tu ventana ha sido destruida y de que debes prepararte para regresar a Windows. En respuesta a esto, puedes ejecutar

Page 22: curso lenguaje ensamblador

alguna tarea doméstica antes de regresar a Windows. No queda más opción que quitar cuando la ventana llega a este estado. Si quieres tener la oportunidad de detener el usuario cuando éste intente cerrar la ventana, debes procesar el mensaje WM_CLOSE.

Ahora, regresando a WM_DESTROY, después de ejecutar la tareas domésticas, debes llamar al PostQuitMessage que enviará el mensaje WM_QUIT de vuelta a tu módulo. WM_QUIT hará que GetMessage regrese el valor NULL en eax, el cual terminará el bucle de mensajes y devolverá el control a Windows. Puedes enviar el mensaje WM_DESTROY a tu propio procedimiento de ventana llamando a la función DestroyWindow.

En este tutorial, aprenderemos como "pintar" texto en el área cliente de una ventana. También aprenderemos sobre el contexto del dispositivo.

Puedes bajar el código fuente desde aquí.

Teoría:

El texto en Windows es un tipo de objeto GUI.  Cada carácter está compuesto por numerosos pixeles o puntos (dots) que están 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 también 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 dimensión 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 tamaño del area cliente de una ventana tampoco es constante. El usuario puede cambiarla en cualquier momento. Así que hay que determinar dinámicamente 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 tenías con DOS.  Debes pedir permiso a Windows para pintar tu propia area cliente. Windows determinará el tamaño 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 sólo 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 gráficos 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.

Page 23: curso lenguaje ensamblador

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 después 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 envía 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 situación 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 reúnir toda la información sobre cómo volver a pintar el área cliente en la sección 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 consideración es el de rectángulo inválido. Windows define un rectángulo inválido como el área rectangular más pequeña que el área cliente necesita para volver a ser pintada. Cuando Windows detecta un rectángulo inválido en el área cliente de una ventana, envía 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 rectángulo inválido. Puedes llamar a BeginPaint en respuesta al mensaje WM_PAINT para validar el rectángulo inválido. Si no procesas el mensaje WM_PAINT, al menos debes llamar a DefWindowProc o a ValidateRect para validar el rectángulo inválido, sino Windows te enviará repetidamente el mensaje WM_PAINT.

Estos son los pasos que deberías 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 explícitamente el rectángulo inválido. Esto es realizado automáticamente 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 parámetro.

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

Page 24: curso lenguaje ensamblador

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)

Page 25: curso lenguaje ensamblador

                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

Análisis:

La mayoría del código es el mismo que el del ejemplo del tutorial 3. Sólo explicaré los cambios importantes.

    LOCAL hdc:HDC     LOCAL ps:PAINTSTRUCT     LOCAL rect:RECT

Estas variables locales son usadas por las funciones GDI en tu sección 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 función BeginPaint y Windows la llena con valores apropiados. Luego pasa ps a la función 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

Page 26: curso lenguaje ensamblador

left y top son las coordenadas de la esquina izquierda superior de un rectángulo. 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 parámetros al manejador de la ventana que quieres pintar y una estructura PAINTSTRUCT no inicializada. Después de una llamada exitosa, eax contiene el manejador al contexto de dispositivo. Luego llamas a GetClientRect para recobrar la dimensión del área cliente. La dimensión es regresada en la variable rect variable que tú pasas a DrawText como uno de sus parámetros. La sintaxis de DrawText es:

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

DrawText es una función de la API de alto-nivel para salida de texto. Maneja algunos detalles tales como ajuste de línea, centramiento, etc. así que puedes concentrarte sólo en la cadena que quieres pintar. Su hermana de bajo nivel, TextOut, será examinada en el próximo tutorial. DrawText formatea una cadena de texto para fijar dentro de los límites de un rectángulo. Emplea la fuente seleccionada en el momento, color y fondo (en el contexto de dispositivo) para dibujar texto. Las líneas son ajustadas para fijarla dentro de los límites del rectángulo. Regresa la altura del texto de salida en unidades de dispositivo, en nuestro caso, pixeles. Veamos sus parámetros:

hdc  manejador al contexto de dispositivo lpString  El puntero a la cadena que quieres dibujar en el rectángulo. La cadena debe estar terminada en NULL o sino tendrás que especificar su largo en el parámetro de texto, nCount. nCount  El número de caracteres para salida. Si la cadena es terminada en cero, nCount debe ser -1. De otra manera nCount debe contener el número de caracteres en la cadena que quieres dibujar. lpRect  El puntero al rectángulo (una estructura de tipo RECT) donde quieres dibujar la cadena. Nota que este rectángulo también es un rectángulo recortante [clipping rectangule], es decir, no podrás dibujar la cadena fuera del rectángulo. uFormat El valor que especifica como la cadena es desplegada en el rectángulo. Usamos tres valores combinados por el operador "or":

o DT_SINGLELINE  especifica una línea de texto o DT_CENTER  centra el texto horizontalmante. o DT_VCENTER centra el texto verticalmente. Debe ser usado con

DT_SINGLELINE.

Después de terminar de pintar el área cliente, debes llamar a la función EndPaint para liberar el manejador del contexto de dispositivo.

Eso es todo. Podemos hacer un sumario de los puntos relevantes:

Page 27: curso lenguaje ensamblador

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 sección WM_PAINT

Tutorial 5: Más sobre TextosExperimentaremos más sobre los atributos de los textos, es decir, sobre las fuentes y los colores.

Baja el archivo ejemplo aquí.

Teoría:

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 términos 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, deberías 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 difícil con este sistema ya que tienes que tener una buena comprensión 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 debería 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. Más bien crearemos una macro. La macro recibirá tres parámetros: 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 continuación:

RGB macro red,green,blue     xor    eax,eax     mov  ah,blue     shl     eax,8     mov  ah,green     mov  al,red endm

Page 28: curso lenguaje ensamblador

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 sólo un parámetro: un puntero a la estructura lógica de la fuente, LOGFONT. CreateFontIndirect es la más flexible de las dos, especialmente si tus programas necesitan cambiar de fuentes con frecuencia. Sin embargo, como en nuestro ejemplo "crearemos" sólo una fuente para demostración, podemos hacerlos con CreateFont. Después de llamada a CreateFont, regresará un manejador a la fuente que debes seleccionar dentro del contexto de dispositivo. Después de eso, toda función 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

Page 29: curso lenguaje ensamblador

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,\

Page 30: curso lenguaje ensamblador

                                       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  

Análisis:

        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 lógica que más coincida con los parámetros dados y con los datos de fuentes disponibles. Esta función tiene más parámetros que cualquier otra función en Windows. Regresa un manejador a la fuente lógica a ser usada por la función SelectObject. Examinaremos sus parámetros 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 tamaño por defecto. nWidth   La ancho deseado para los caracteres. Normalmente este valor debería ser 0 que permite a Windows coordinar el ancho y el alto. Sin embargo, en nuestro ejemplo, el ancho por defecto hace difícil la lectura del texto, así que usaremos mejor un ancho de 16.

Page 31: curso lenguaje ensamblador

nEscapement   Especifica la orientación del próximo caracter de salida relativa al previo en décimas de grados. Normalmente, se pone en 0. Si se pone en 900 tendremos que todos los caracteres irán por encima del primer caracter, 1800 los escribirá hacia atrás, o 2700 para escribir cada caracter desde abajo. nOrientation   Especifica cuánto debería ser rotado el caracter cuando tiene una salida en décimas 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 líneas de cada caracter. Windows define los siguientes tamaños:

FW_DONTCARE     equ 0 FW_THIN                  equ 100 FW_EXTRALIGHT  equ 200 FW_ULTRALIGHT  equ 200 FW_LIGHT                equ 300 FW_NORMAL          equ 400 FW_REGULAR         equ 400 FW_MEDIUM           equ 500 FW_SEMIBOLD       equ 600 FW_DEMIBOLD      equ 600 FW_BOLD                 equ 700 FW_EXTRABOLD   equ 800 FW_ULTRABOLD   equ 800 FW_HEAVY              equ 900 FW_BLACK              equ 900

cItalic   0 para normal, cualquier otro valor para caracteres en itálicas. cUnderline   0 para normal, cualquier otro valor para caracteres subrayados. cStrikeOut   0 para normal, cualquier otro valor para caracteres con una línea a través del centro. cCharSet  Especifica la configuración de la fuente [character set]. Normalmente debería ser OEM_CHARSET lo cual permite a Windows seleccionar la fuente dependiente del sistema operativo. cOutputPrecision  Especifica cuánto la fuente seleccionada debe coincidir con las características que queremos. Normalmente debería ser OUT_DEFAULT_PRECIS que define la conducta de proyección por defecto de la fuente. cClipPrecision  Especifica la presisión del corte. La precisión del corte define cómo recortar los caracteres que son parcialmente fuera de la región de recorte. Deberías 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 cuán cuidadosamente la GDI debe intentar hacer coincidir los atributos de la fuente lógica con los de la fuente física 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 tipografía de la fuente.

La descripción anterior no tiene nada de comprensible. Deberías revisar la referencia de la APi de Win32 API para más detalles.

        invoke SelectObject, hdc, eax         mov    hfont,eax

Page 32: curso lenguaje ensamblador

Después de obtener el manejador lógico 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 bolígrafos, brochas, y fuentes dentro del contexto de dispositivo a ser usado por las funciones GDI. Este regresa el manejador del objeto reemplazado, el cual deberíamos salvar para su uso posterior a la llamada a SelectObject. Después de llamar a SelectObject, cualquier función 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 función 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, deberíamos almacenar la fuente original dentro del contexto de dispositivo. Deberías almacenar siempre el objeto que reemplazaste en el contexto de dispositivo.

Tutorial 6: Entrada del tecladoAprenderemos como un programa de Windows recibe entrada de teclado.

Baja el ejemplo aquí.

Teoría:

Como normalmente sólo 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, sólo 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 título. La barra de título 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 colección de teclas. En este caso, si presionas una tecla, Windows envía 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 envía 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 envía un mensaje WM_CHAR a la ventana que tiene el foco de entrada, diciéndole que el usuario envía "un" caracter a ella. En realidad, Windows envía mensajes WM_KEYDOWN y WM_KEYUP a la ventana que tiene el foco de entrada y esos

Page 33: curso lenguaje ensamblador

mensajes serán traducidos a mensajes WM_CHAR por una llamada a TranslateMessage. El procedimiento de ventana puede decidir si procesa los tres mensajes o sólo los mensajes que interesan. Muchas veces, podrás ignorar WM_KEYDOWN y WM_KEYUP ya que la función 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

Page 34: curso lenguaje ensamblador

    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  

Page 35: curso lenguaje ensamblador

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 rectángulo específico en el área cliente quede invalidado para forzar a Windows para que envíe 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 rectángulo en el área clienete que queremos declarar inválida. Si este parámetro es nulo, toda el área cliente será marcada como inválida. bErase es una bandera que dice a Windows si necesita borrar el fondo. Su ventana es TRUE, luego Windows borrará el fondo del rectángulo invalidado cuando se llama a BeginPaint.

Así que la estrategia que usamos aquí es: almacenamos toda la información necesaria involucrada en la acción de pintar el área cliente y generar el mensaje WM_PAINT para pintar el área cliente. Por supuesto, el código en la sección 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 código que pinta el caracter está en la sección 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 código y los datos necesarios para que realicen la acción de pintar en WM_PAINT. Puedes enviar el mensaje WM_PAINT desde cualquier lugar de tu código cada vez que quieras volver a pintar el área cliente.

        invoke TextOut,hdc,0,0,ADDR char,1

Cuando se llama a InvalidateRect, envía un mensaje WM_PAINT de regreso al procedimiento de ventana. De esta manera es llamado el código en la sección 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, verás 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

Page 36: curso lenguaje ensamblador

nuevo tendrá todavía el caracter ahí ya que todo el código y los datos esenciales para volver a pintar son todos activados en la sección WM_PAINT.

Tutorial 7: Entrada del RatónAprenderemos a recibir y a responder a la entrada del ratón en nuestro procedimiento de ventana. El programa esperará por los clicks del botón izquierdo del ratón y desplegará una cadena de texto en exactamente el punto indicado por el ratón sobre el área cliente.

Bajar el ejemplo de aquí.

Teoría:

Como con la entrada del teclado, Windows detecta y envía notificaciones sobre las actividades del ratón que son relevantes para las ventanas. Esas actividades incluyen los clicks de los botones izquierdo y derecho del ratón, el movimiento del cursor del ratón 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 ratón son enviados a cualquier ventana sobre la cual esté el cursor del ratón, activo o no. Además, también hay mensajes del ratón sobre el área no cliente. Pero la mayoría de las veces, afortunademente podemos ignorarlas. Podemos concentrarnos en los mensajes relacionados con el área cliente.

Hay dos mensajes para cada botón el ratón: los mensajes WM_LBUTTONDOWN, WM_RBUTTONDOWN y WM_LBUTTONUP, WM_RBUTTONUP. Para un ratón con tres botones, están también WM_MBUTTONDOWN and WM_MBUTTONUP. Cuando el cursor del ratón se mueve sobre el área cliente, Windows envía mensajes WM_MOUSEMOVE a la ventana debajo del cursor. Una ventana puede recibir mensajes de doble clicks, WM_LBUTTONDBCLK o WM_RBUTTONDBCLK, si y sólo si la clase de su ventana tiene activada la bandera correspondiente al estilo CS_DBLCLKS, sino la ventana recibirá sólo una serie de mensajes del topo botón del ratón arriba o abajo.

Para todos estos mensajes, el valor de lParam contiene la posición del ratón. 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 ratón 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

Page 37: curso lenguaje ensamblador

.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

Page 38: curso lenguaje ensamblador

    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

Page 39: curso lenguaje ensamblador

El procedimiento de ventana espera a que el botón izquierdo del ratón haga un click. Cuando recibe el mensaje WM_LBUTTONDOWN, lParam contiene la coordenada del botón del ratón 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 botón izquierdo del ratón 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 tamaño 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.

Después de almacenar la posición del ratón, establecemos la bandera, MouseClick, a TRUE con el fin de dejar que el código de pintura en la sección WM_PAINT sepa que hay al menos un click en el área cliente y puede dibujar la cadena en la posición del ratón. Luego llamamos a la función 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 código de pintura en la sección WM_PAINT debe chequear si MouseClick es uno ( TRUE ), ya que cuando la ventana fue creada, recibió un mensaje WM_PAINT en ese momento, ningún click del ratón había ocurrido aún, 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 ratón.

Si ha ocurrido al menos un click de ratón, se dibuja la cadena en el área cliente en la posición del ratón. Nota que se llama a lstrlen para obtener el tamaño de la cadena a desplegar y envía este tamaño como último parámetro de la función TextOut.

En este tutorial aprenderemos como incorporar un menú a nuestra ventana. Bajar el ejemplo 1 y el ejemplo 2.

Teoría:

El menú es uno de los componentes más 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

Page 40: curso lenguaje ensamblador

el manual incluido con el programa para utilizarlo, ya que puede leerse cuidadosamente el menú para obtener una visión 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 rápidamente, se debería seguir siempre el estandard. Puesto sucintamente, los primeros dos elementos [items] del menú deberían ser Archivo [File] y Editar [Edit] y el último debería ser Ayuda [Help]. Puedes insertar tus propios elementos de menú entre Editar y Ayuda. Si un elemento de menú invoca una caja de diálogo, deberías 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 extensión .rc. Luego combinas los recursos cion el archivo fuente durante el estadío 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 están 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 más bien embarazoso. Una mejor altrenativa es usar un editor de recursos que te permita visualizar con facilidad el diseño de los recursos. Usualmente los paquetes de compiladores como Visual C++, Borland C++, etc, incluyen editores de recursos

Describes los recursos más o menos así:  

Mymenu  MENU {    [menu list here] }

Los programadores en lenguaje C pueden reconocer que es similar a la declaración de una estructura. MyMenu sería 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 carácter que le sigue sea subrayado.

Después de la cadena de texto está el ID del elemento de menú. El ID es un número 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].

Page 41: curso lenguaje ensamblador

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 línea nueva del menú. HELP  Este elemento y los siguientes están justificados a la derecha.  

Puedes usar una de las opciones de arriba o combinarlas con el operador "or". Sólo recuerda que INACTIVE y GRAYED no pueden ser combinados simultáneamente.

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, después de haber terminado con el guión 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 parámetro del manejador del menú de CreateWindowEx más o menos así:   .DATA

MenuName  db "FirstMenu",0 hMenu HMENU ?

Page 42: curso lenguaje ensamblador

...........................

...........................

.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 podrías preguntarte, ¿cuál es la diferencia entre estos dos métodos?

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 tendrán 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 función 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 información para usar el menú. Vamos a hacerlo.

Ejemplo:

El primer ejemplo muestra cómo 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

Page 43: curso lenguaje ensamblador

.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                    ; Menu IDs IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4

.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

Page 44: curso lenguaje ensamblador

    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

Page 45: curso lenguaje ensamblador

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 }  

Análisis:

vamos a analizar primero el guón de recursos.  

#define IDM_TEST 1                /* equal to IDM_TEST equ 1*/ #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4  

Las líneas de arriba definen los IDs de menú usados por el guión 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 código 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

Page 46: curso lenguaje ensamblador

Goodbye_string db "See you again, bye",0  

MenuName es el nombre del menú en el archivo de recursos. Nota que puedes definir más de un menú en el archivo de recursos así que debes especificar cual usar. Las restantes tres líneas 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                    ; Menu IDs IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4  

Definir IDs de elementos de menú para usar en el procedimiento de ventana. Estos valores DEBEN ser idénticos 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 parámetro que cerrará nuestra ventana.

Como puedes ver, especificar el nombre del menú en una clase de ventana es muy fácil y directo. Sin embargo, también puedes usar un método alternativo para cargar un menú en tu ventana. No mostraré aquí todo el código fuente. El archivo de recursos es el mismo en ambos métodos. Hay algunos cambios menores en el archivo fuente que mostraré abajo.  

.data? hInstance HINSTANCE ? CommandLine LPSTR ? hMenu HMENU ?                    ; handle of our menu  

Page 47: curso lenguaje ensamblador

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í.

Teoría:

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 diálogo por lo que ellas usualmente son llamadas controles de ventanas hijas. Los controles de ventanas hijas procesan sus propios mensajes de teclado y de ratón y notifican a las ventanas padres cuando sus estados han cambiado. Ellos liberan al programador de enormes cargas, así que deberías usarlas cada vez que sea posible. En este tutorial las pongo sobre una ventana normal para demostrar cómo puedes crearlas y usarlas, pero en realidad deberías ponerlas en una caja de diálogo.

Ejemplos de clases de ventanas predefinidas son el botón, la caja de lista [listbox], la caja de chequeo [checkbox], botón de radio [radio button], edición [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 parámetro nombre de la clase DEBE ser el nombre de la clase predefinida. Es decir, si quieres crear un botón, debes especificar "button" como nombre de la clase en CreateWindowEx. Los otros parámetros 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.

Después de que el control fue creado, enviará mensajes de notificación 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 envía mensajes WM_COMMAND a la ventana padre con su ID de control en la palabra baja de wParam, el código de notificación en la palabra alta de wParam, y su manejador de ventana en lParam. Cada conrtol de ventana hija tiene su propio código de notificación, así que debes revisar la referencia de la API de Win32 para más información.

También la ventana padre puede enviar órdenes o comandos a la ventana hija, llamando a la función SendMessage. Esta función envía el mensaje especificado acompañado de otrops

Page 48: curso lenguaje ensamblador

valores en wParam y lParam a la ventana especificada por el manejador de ventana. Es una función extremadamente útil, ya que puede enviar mensajes a cualquier ventana que conozcas su manejador.

Así que después de crear ventanas hijas, la ventana padre debe procesar los mensajes WM_COMMAND para poder recibir códigos de notificación desde las ventanas hijas.

Ejemplo:

Crearemos una ventana que contenga un control de edición y un "pushbutton". Cuando pulses el botón, un cuadro de mensaje aparecerá mostrando un texto que hayas escrito en el cuadro de diálogo. Hay también un menú con 4 elementos:

1. Say Hello  -- Pone una cadena de texto en la caja de edición 2. Clear Edit Box -- Limpia el contenido de la caja de edición 3. Get Text -- despliega una caja de mensajes con el texto en la caja de edición 4. 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 edición

.const ButtonID equ 1                                ; El ID del control botón [button] EditID equ 2                                    ; El ID del control de edición [edit] IDM_HELLO equ 1 IDM_CLEAR equ 2

Page 49: curso lenguaje ensamblador

IDM_GETTEXT equ 3 IDM_EXIT equ 4

.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

Page 50: curso lenguaje ensamblador

        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,\

Page 51: curso lenguaje ensamblador

                        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 edición, "button" para el control botón. Luego especificamos los estilos de ventanas hijas. Cada control tiene estilos extras además de los estilos de ventana normal. Por ejemplo, los estilos de botón llevan el prefijo "BS_" para "button style", los estilos del control de edición 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ú.

Después 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 edición de manera que el usuario pueda tipear texto dentro de ella immediatamente.

Ahora la parte realmente exitante. Todo control de ventana hija envía una notificación a su ventana padre con WM_COMMAND.

    .ELSEIF uMsg==WM_COMMAND         mov eax,wParam         .IF lParam==0

Recuerda que también un menu envía mensajes WM_COMMAND para notificar a su ventana sobre su estado. ¿Cómo puedes diferenciar si los mensajes WM_COMMAND son originados

desde un menú o desde un control? He aquí la respuesta:  

  Palabra baja de wParam Palabra alta de wParam lParam

Menu Menu ID 0 0

Control Control ID Código de notificación 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 idénticos y el código de notificación puede ser cero.

            .IF ax==IDM_HELLO                 invoke SetWindowText,hwndEdit,ADDR TestString             .ELSEIF ax==IDM_CLEAR                 invoke SetWindowText,hwndEdit,NULL             .ELSEIF  ax==IDM_GETTEXT

Page 52: curso lenguaje ensamblador

                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 edición llamando a SetWindowText. Limpias el contenido de la caja de edición llamando a SetWindowText con NULL. SetWindowText es una función de la API de propósito general. Puedes usar SetWindowText para cambiar el encabezamiento o título [caption] de una ventana o el texto sobre un botón.

Para obtener el texto en una caja de edición, 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 código de arriba tiene que ver con la condición de si el usuario presiona el botón. Primero, chequea la palabra baja de wParam para ver si el ID del control coincide con el del botón. Si es así, chequea la palabra alta de wParam para ver si es el código de notificación BN_CLICKED que se envía cuando el botón es pulsado.

La parte interesante es después que el código de notificación es BN_CLICKED. Queremos obtener el texto de la caja de edición y desplegarlo en la caja de edición. Podemos duplicar el código en la sección 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 duplicación de código y simplificar nuestro programa. La función SendMessage es la respuesta. Esta función envía cualquier mensaje a cualquier ventana con cualquieras wParam y lParam que desiemos. Así que e nvez de duplicar código, llamamos a SendMessage con el manejador de ventana padre, WM_COMMAND, IDM_GETTEXT, y 0. Esto tiene un efecto idéntico que seleccionar el elemento "Get Text" de nuestro menú. El procedimiento de ventana no percibirá ninguna diferencia entre los dos.

Deberías usar estas técnicas en la medida de lo posible para que tu código sea más organizado.

Por último, no olvides poner la función TranslateMessage en el bucle de mensajes, puesto que, como debes tipear algún texto en la caja de edición, tu programa debe traducir la entrada cruda del teclado a texto que pueda ser leído. Si omites esta función, no serás capáz de editar nada en la caja de edición.

Tutorial 10: Caja de Diálogo [Dialog Box] como Ventana Principal

Ahora viene la parte realmente interesante sobre GUI, la caja de diálogo. En este tutorial (y en el próximo), aprenderemos como podemos usar una caja de diálogo como programa principal.

Bajar el primer ejemplo aquí, el segundo ejemplo aquí.

Teoría:

Page 53: curso lenguaje ensamblador

Si juegas bastante con los ejemplos del tutorial anterior, encontrarás 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 situación es más bien incómoda. Otra cosa que deberías notar es que cambié el color del fondo de la ventana padre a gris en vez de a blanco, como lo había 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 fácil. Tienes que subclasificar todos los controles de ventana hija en tu ventana padre.

La razón de la existencia de tal inconveniente es que los controles de ventana hija están originalmente diseñados para trabajar dentro de cajas de diálogo, 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 diálogo normalmente es gris para que armonicen entre sí sin ninguna intervensión por parte del programador.

Antes de entrar en detalles, deberíamos saber qué es una caja de diálogo. Una caja de diálogo no es más que una ventana normal diseñada para trabajar con controles de ventanas hijas. Windows también proporciona un administrador interno de cajas de diálogo ["dialog box manager"] responsable por gran parte de la lógica del teclado tal como desplazamiento del foco de entrada cuando el ususario presiona Tab, presionar el botón por defecto si la tecla Enter es presionada, etc; así los programadores pueden ocuparse de tareas de más alto nivel. Las cajas de diálogo son usadas primero como dispositivos de entrada/salida. Como tal, una caja de diálogo puede ser considerada como una "caja negra" de entrada/salida lo que siginifica que no tienes que saber cómo funciona internamente una caja de diálogo para usarla, sólo tienes que saber cómo interactuar con ella. Es un principio de la programación orientada a objetos [object oriented programming (OOP)] llamado encapsulación u ocultamiento de la información. Si la caja negra es *perfectamente* diseñada , el usuario puede emplarla sin tener conocimiento de cómo funciona. Lo único es que la caja negra debe ser perfecta, algo difícil de alcanzar en el mundo real. La API de Win32 API también ha sido diseñada como una caja negra.

Bien, parece que nos hemos alejado de nuestro camino. Regresemos a nuestro tema. Las cajas de diálogo han sido diseñadas 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 lógica del teclado. Pero si quieres ponerlas en una caja de diálogo, Windows manejará la lógica por tí. Sólo tienes que saber cómo obtener la entrada del usuario de la caja de diálogo o como enviar órdenes a ella.

Como el menú, una caja de diálogo se define como un recurso. Escribes un plantilla describiendo las características de la caja de diálogo y sus controles y luego compilas el guión de recursos con un compilador de recursos.

Nota que todos los recursos se encuentran en el mismo archivo de guión de recursos. Puedes emplear cualquier editor de texto para escribir un guión de recursos, pero no lo recomiendo. Deberías usar un editor de recursos para hacer la tarea visualmente ya que arreglar la disposición de los controles en la caja de diálgo 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 guión de recursos para tu programa y luego cortar las líneas irrelevantes tales como las relacionadas con MFC.

Hay dos tipos principales de cajas de diálogo: modal y no-modal. Una caja de diálogo no-modal te deja cambiar de foco hacia otra ventana. Un ejempo es el diálogo Find de MS Word. Hay dos subtipos de caja de diálogo modal: modal de aplicación y modal de sistema. Una caja de diálogo modal de aplicación no permite cambiar el foco a otra ventana en la misma aplicación sino cambiar el foco de entrada a la ventana de OTRA aplicación. Una caja de diálogo modal de sistema no te permite cambiar de foco hacia otra ventana hasta que respondas a la primera.

Page 54: curso lenguaje ensamblador

Una caja de diálogo no-modal se crea llamando a la función de la API CreateDialogParam. Una caja de diálogo modal se crea llamando a DialogBoxParam. La única diferencia entre una caja de diálogo 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 diálogo, esa caja de diálogo será modal de sistema.

Puedes comunicarte con cualquier control de ventana hija sobre una caja de diálogo usando la función 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 edición, puedes hacer esto:

call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer

Con el fin de saber qué mensaje enviar, deberías consultar la referencia de la API de Win32. Windows también provee algunas funciones específicas de la API para controles que permiten obtener y poner datos en los controles rápidamente, por ejemplo, GetDlgItemText, CheckDlgButton etc. Estas funciones específicas 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, deberías usar llamadas a las funciones específicas de la API para controles cada vez que sean disponibles ya que ellas facilitan el mantenimiento del código fuente. Recurre a SendDlgItemMessage sólo si no hay disponible llamadas a funciones específicas de la API.

El manejador de Windows de cajas de diálogos envía varios mensajes a una función " callback" particular llamada procedimiento de caja de diálogo que tiene el siguiente formato:

DlgProc  proto hDlg:DWORD ,\                         iMsg:DWORD ,\                         wParam:DWORD ,\                         lParam:DWORD

El procedimiento de diálogo 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 diálogo dentro de Windows ES el verdadero procedimiento de ventana para la caja de diálogo. Llama a nuestra caja de diálogo con algunos mensajes que recibió. Así que la regla general es que: si nuestro procedimiento de diálogo 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 diálogo 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 diálogo. Puedes usarlas como ventanas principal de tu aplicación o usarla como un dispositivo de entrada. Examinaremos el primer acercamiento en este tutorial. "Usar una caja de diálogo como una ventana principal" puede ser interpretado en dos sentidos.

1. Puedes usar una plantilla de caja de diálogo como la plantilla de clase que registras al llamar a RegisterClassEx. En este caso, la caja de diálogo se comporta como una

Page 55: curso lenguaje ensamblador

ventana "normal": recibe mensajes del procedimiento de ventana referido por el miembro lpfnWndProc de la clase de ventana class, no a través de un procedimiento de caja de diálogo. 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 diálogo. También Windows maneja la lógica del teclado para tí, por ejemplo se encarga de la orden Tab, etc. Además 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 diálogo sin ninguna ventana padre. Esta aproximación 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 diálogo. ¡Ya no tienes que registrar la clase de ventana!

Este tutorial va a ser un poco largo. Presentaré la primera aproximación 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

Page 56: curso lenguaje ensamblador

IDM_CLEAR       equ 32001 IDM_EXIT        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

Page 57: curso lenguaje ensamblador

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                                       3000 #define IDC_BUTTON                                3001 #define IDC_EXIT                                       3002

#define IDM_GETTEXT                             32000 #define IDM_CLEAR                                  32001 #define IDM_EXIT                                      32003  

Page 58: curso lenguaje ensamblador

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

Análisis:

Vamos a analizar el primer ejemplo.

Este ejemplo muestra como registrar una plantilla de diálogo 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 diálogo.

MyDialog DIALOG 10, 10, 205, 60

Declarar el nombre del diálogo, en este caso, "MyDialog" seguido por la palabra clave "DIALOG". Los siguientes cuatro números son: x, y , ancho, y alto de la caja de diálogo en unidades de caja de diálogo 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 diálogo.

CAPTION "Our First Dialog Box"

Este es el texto que aparecerá en la barra de título de la caja de diálogo.

CLASS "DLGCLASS"

Esta línea es crucial. Es esta palabra clave, CLASS, lo que nos permite usar la caja de diálogo como una clase de ventana. Después de la palabra clave está el "nombre de la clase"

BEGIN     EDITTEXT         IDC_EDIT,   15,17,111,13, ES_AUTOHSCROLL | ES_LEFT

Page 59: curso lenguaje ensamblador

    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 diálogo. Están 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 código 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 diálogo como una clase de ventana, debemos poner en este miembro el valor DLGWINDOWEXTRA. Nota que el nombre de la clase debe ser idéntico al que sigue a la palabra clave CLASS en la plantilla de la caja de diálogo. Los miembros restantes se inicializan como es usual. Después 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

Después de registrar la "clase de ventana", creamos nuestra caja de diálogo. En este ejemplo, lo creo como una caja de diálogo modal con la función CreateDialogParam. Esta función toma 5 parámetros, pero sólo tienes que llenar los primeros dos: el manejador de instancia y el puntero al nombre de la plantila de la caja de diálogo. Nota que el segundo parámetro no es un puntero al nombre de la clase.

En este punto, la caja de diálogo 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

Después de que es creada la caja de diálogo, quiero poner el foco de entrada a la caja de edición. Si pongo estas instrucciones en la sección WM_CREATE, la llamada a GetDlgItem fallará ya que en ese momento, ya que en ese instante todavía no se han creado los controles de ventana hija. La única manera de hacer esto es llamarlo después de que la caja de diálogo y todos los controles de ventana hija son creados. Así que pongo estas dos lineas después de la llamada a UpdateWindow. La función 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

Page 60: curso lenguaje ensamblador

El programa introduce el bucle de mensajes y antes de que traduzcamos y despachemos mensajes, llamamos a la función IsDialogMessage para permitir que el administrador [manager] de la caja de diálogo maneje el teclado lógico de nuestra caja de diálogo. Si la función regresa TRUE, significa que el mensaje es enviado a la caja de diálogo y es procesado por el administrador de la caja de diálogo. Nota ahora la diferencia respecto al tutorial previo. Cuando el procedimiento de ventana quiere obtener el texto del control de edición, llama a la fución 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 diálogo.

Ahora vamos a la segunda aproximación de cómo usar una caja de diálogo como ventana principal. En el siguiente ejemplo, crearé una aplicación de caja de diálogo modal. ¡No encontrarás 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            equ 3000 IDC_BUTTON     equ 3001 IDC_EXIT            equ 3002 IDM_GETTEXT  equ 32000 IDM_CLEAR       equ 32001 IDM_EXIT           equ 32002  

Page 61: curso lenguaje ensamblador

.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)

Page 62: curso lenguaje ensamblador

#include "resource.h"

#define IDC_EDIT                                       3000 #define IDC_BUTTON                                3001 #define IDC_EXIT                                       3002

#define IDR_MENU1                                  3003

#define IDM_GETTEXT                              32000 #define IDM_CLEAR                                   32001 #define IDM_EXIT                                       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 continuación el análisis:

    DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

Declaramos el prototipo de función para DlgProc de manera que podamos referirnos a él con el operador addr en la línea de abajo:

    invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL

la línea de arriba llama a la función DialogBoxParam que toma 5 parámetros: el manejador de instancia, el nombre de la plantilla de la caja de diálogo, el manejador de la ventana padre, la dirección del procedimiento de de ventana, y los datos específicos del diálogo.

Page 63: curso lenguaje ensamblador

DialogBoxParam crea una caja de diálogo modal. No regresará hasta que la caja de diálogo 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 diálogo 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 código de inicialización. Nota que debes regresar el valor TRUE en eax si procesas el mensaje.

El administrador interno de la caja de diálogo no envía a nuestro procedimiento de diálogo el mensaje WM_DESTROY por defecto cuando WM_CLOSE es enviado a nuestra caja de diálogo. Así que si queremos reaccionar cuando el usuario presiona el botón cerrar [close] en nuestra caja de diálogo, 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 diálogo, la única manera es llamar a la función EndDialog. ¡No emplees DestroyWindow! EndDialog no destruye la caja de diálogo de inmediato. Sólo pone una bandera para ser revisada por el administrador interno de la caja de diálogo y continúa para ejecutar la siguiente instrucción.

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 diálog creada con DialogBoxParam. Nota que en la plantilla de caja de diálogo 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 diálgo durante WM_INITDIALOG.

Tutorial 11: Mas sobre las Caja de Diálogo [Dialog Box]

En este tutorial vamos a aprender mas sobre las cajas de diálogo [dialog box]. Especificamente, vamos a explorar la manera de como usar cajas de diálogo como nuestra entrada-salida de datos. Si leíste el tutorial anterior, este te va a resultar ligero. Encontrarás algunos cambios menores, es todo lo que necesitamos para poder usar cajas de diálogo adjuntas a nuestra ventana principal. En este tutorial también aprenderemos como usar cajas de diálogo comunes.

Bájate los ejemplos de cajas de diálogo aquí y aquí. Bajate el ejemplo de una cajas de diálogo común aquí.

 

Page 64: curso lenguaje ensamblador

Teoría:

Hay muy poco que decir sobre como usar las cajas de diálogo como entrada-salida de nuestro programa. Tu programa crea la página principal normalmente y cuando quieres mostrar la cajas de diálogo, llamas a CreateDialogParam o DialogBoxParam. Con la llamada a DialogBoxParam, no tendrás que hacer nada mas, sólo procesar los mensajes en el procedimiento de la cajas de diálogo. Con CreateDialogParam, tendrás que insertar la llamada a IsDialogMessage en el bucle de mensajes para dejar a la cajas de diálogo el control sobre la navegación del teclado en tu cajas de diálogo. 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 diálogo comunes. Windows tiene preperadas unas cajas de diálogo predefinidas que pueden ser usadas por tus aplicaciones. Estas cajas de diálogo existen para proveer un interfaz estandard de usuario. Consisten en cajas de diálogo de archivo, impresión, color, fuente, y busqueda. Deberías usarlas lo máximo posible. Las cajas de diálogo residen en comdlg32.dll. Para usarlas, tendrás que enlazar [link] el archivo comdlg32.lib. Creas estas cajas de diálogo llamando a la función apropiada en la librería de las cajas de diálogo. Para el archivo de diálogo "Abrir" [Open], se emplea la función GetOpenFileName, para la caja de diálgo "Guardar" [Save] GetSaveFileName, para dibujar un diálogo es PrintDlg y ya está. Cada una de estas funciones toma como parámetro un puntero a la estructura. Deberás mirarlo en la referencia de la API de Win32. En este tutorial, demostraré como crear y usar un diálogo "Abrir archivo" [Open file].

Debajo está la el prototipo de la función GetOpenFileName:  

GetOpenFileName proto lpofn:DWORD

Puedes ver que sólo recibe un parámetro, 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  ?

Page 65: curso lenguaje ensamblador

 lpfnHook DWORD  ?  lpTemplateName LPCSTR  ?

OPENFILENAME  ENDS

Vamos a ver el significado de los miembros mas utilizados de la estructura.  

lStructSize El tamaño de la estructura OPENFILENAME, en bytes

hwndOwner El manejador (handle) de la caja de diálogo "open file".

hInstanceManejador (handle) de la instancia de la aplicación que crea la caja de diálogo "open file".

lpstrFilter

Las cadenas de filtro en formato de pares de cadenas terninadas en null (0). La primera entre las dos es la de descripción. La segunda cadena es el patrón de filtro. por ejemplo:       FilterString   db "All Files (*.*)",0, "*.*",0                          db "Text Files (*.txt)",0,"*.txt",0,0  Date cuenta que sólo el patrón 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.

nFilterIndex

Especifica que par de cadenas de filtro que serán usadas inicialmente cuando la caja de diálogo "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 patrón, "*.txt".

lpstrFile

Puntero al buffer que contiene el nombre del archivo usado para inicializar el control de edición de nombre de archivo en la caja de diálogo. El buffer será como mínimo de 260 bytes de largo. Después de que el usuario seleccione el archivo a abrir, el nombre de archivo con toda la dirección (path) es almacenado en este buffer. Puedes extraer la información de aquí mas tarde.

nMaxFile El tamaño del buffer lpstrFile.

lpstrTitle Puntero al encabezamiento o título de la caja de diálogo "open file"

Flags Determina el estilo y características de la caja de diálogo.

nFileOffset

después 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.

nFileExtensiondespués de que el usuario seleccione el archivo a abrir, este miembro contiene el índice al primer caracter de la extensión del archivo

 

Ejemplo:

El siguiente programa muestra una caja de diálogo de abrir archivo cuando el usuario seleccione File-> Open del menu. Cuando el usuario seleccione un archivo en la caja de diálogo, el programa muestra una caja de mensaje mostrando el nombre completo, nombre de archivo, y extensión del archivo seleccionado.

.386

.model flat,stdcall

Page 66: curso lenguaje ensamblador

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

Page 67: curso lenguaje ensamblador

    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

Page 68: curso lenguaje ensamblador

                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

Análisis:

            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:

Page 69: curso lenguaje ensamblador

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 descripción de la siguiente cadena. El actual patrón es la cadena par, en este caso, "*.*" y "*.txt". Actualmente podemos especificar aquí cualquier patrón que queramos. DEBEMOS poner un cero extra después de la última cadena de patrón para denotar el final de la cadena de filtro. No olvides esto sino tu caja de diálogo funcionará de forma extraña.

            mov  ofn.lpstrFile, OFFSET buffer             mov  ofn.nMaxFile,MAXSIZE

Especificamos dónde la caja de diálogo pondrá el nombre del archivo que ha seleccionado el usuario. Date cuenta que tendremos que especificar su tamaño en el miembro nMaxFile. Podemos extraer más 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 características de la caja de diálogo.

Las banderas [flags] OFN_FILEMUSTEXIST  y OFN_PATHMUSTEXIST demandan que el nombre de archivo y dirección (path) que el usuario pone en el control de edición (edit control) DEBEN existir. La bandera OFN_LONGNAMES dice a la caja de diálogo que muestre nombres largos de archivo. La bandera OFN_EXPLORER especifica que la apariencia de la caja de diálogo debe ser parecida a la del explorador. La bandera OFN_HIDEREADONLY oculta el cuadro de selección de solo-lectura en la caja de diálogo. Hay muchas banderas más 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 diálogo.

            invoke GetOpenFileName, ADDR ofn

Llamada a la función GetOpenFileName. Pasando el puntero a la estructura ofn como parámetro. En este momento, la caja de diálogo de abrir archivo es mostrada en la pantalla. La función no volverá hasta que el usuario seleccione un archivo para abrir o presione el boton de cancelar o cierre la caja de diálogo. 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

Page 70: curso lenguaje ensamblador

OutputString y entonces usamos la función de la API, lstrcat, para entrelazar las cadenas siempre. Para poner las cadenas en varias líneas, debemos separar cada línea con el par de caracteres que alimentan el retorno de carro (13d o 0Dh) y el avance de línea (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 líneas de arriba requieren una explicación. nFileOffset contiene el índice dentro de ofn.lpstrFile. Pero no puedes añadirlo directamente porque nFileOffset es una variable de tamaño WORD y lpstrFile es de tamaño DWORD. Así que tendré que poner el valor de nFileOffset en la palabra baja [low word] de ebx y sumárselo 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 función 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 diálogo comunes como instrumento de entrada-salida.

Bája 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 categorías 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 motículo [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. tendrás el mismo resultado si llamas a GlobalAlloc o LocalAlloc.

Los pasos para ubicar y usar la memoria son los siguientes:

Page 71: curso lenguaje ensamblador

1. Ubicar el bloque de memoria llamando a GlobalAlloc. Esta función devuelve un manejador (handle) al bloque de memoria pedido.

2. "Bloquear" [lock] el bloque de memoria llamando a GlobalLock. Esta función acepta un manejador (handle) al bloque de memoria y devuelve un puntero al bloque de memoria.

3. Puedes usar el puntero para leer o escribir en la memoria. 4. Desbloquear [unlock] el bloque de memoria llamando a GlobalUnlock . Esta función

invalida el puntero al bloque de memoria. 5. Liberar el bloque de memoria llamando a GlobalFree. Esta función acepta un

manejador (handle) al bloque de memoria.

Puedes sustituir "Global" por "Local" en LocalAlloc, LocalLock,etc.

El método 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 código de otros programas.

La E/S de archivos bajo Win32 tiene una semblanza más notable que la de bajo DOS. Los pasos a seguir son los mismos. Sólo 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 función CreateFile. Esta función es muy versátil: añadiendo a los archivos, puede abrir puertos de comunicación, tuberías [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 posición deseada llamando a SetFilePointer.

3. Realiza la operación 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 tendrás que reservar un bloque de memoria suficientemente grande para alojar los datos.

4. Cierra el archivo llamando a CloseHandle. Esta función acepta el manejador (handle) de archivo.

Contenido:

El programa de abajo muestra una caja de diálogo de abrir archivo. Deja al usuario seleccionar un archivo de texto y muestra el contenido de este archivo en un control de edición en su área cliente. El usuario puede modificar el texto en el control de edición 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

Page 72: curso lenguaje ensamblador

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 edición

.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 ?                               ; manejador (handle) del control de edición hFile HANDLE ?                                   ; manejador de archivo hMemory HANDLE ?                            ; manejador del bloque de memoria reservada pMemory DWORD ?                            ; puntero al bloque de memoria reservada SizeReadWrite DWORD ?                   ; 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

Page 73: curso lenguaje ensamblador

    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

Page 74: curso lenguaje ensamblador

        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

Page 75: curso lenguaje ensamblador

                        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

Análisis:

        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 sección WM_CREATE, creamos el control de edición. Date cuenta que los parámetros que especifican x, y, anchura, altura del control son todos ceros ya que reajustaremos el tamaño 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 edición aparezca en la pantalla porque hemos incluido el estilo WS_VISIBLE. Puedes usar este truco también 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 edición, tendremos que inicializar los miembros de ofn. Como queremos reciclar ofn en la caja de diálogo para guardar, pondremos solo los miembros *comunes* que son usados por ambos GetOpenFileName y GetSaveFileName.

Page 76: curso lenguaje ensamblador

La sección 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 tamaño 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 tamaño de nuestro control de edición al mismo tamaño 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 información 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 información para reajustar el tamaño del control de edición llamando a la función MoveWindow, que además de cambiar la posición de la ventana, permite cambiar su tamaño.

            .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 función GetOpenFileName para mostrar la caja de diálogo 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

Después que el usuario ha seleccionado el archivo que desea abrir, llamamos a CreateFile para abrir el archivo. Hemos especificado que la función intentará abrir el archivo para lectura y escritura. Después de abrir el archivo, la función devuelve el manejador (handle) al archivo abierto que almacenamos en una variable global para futuros usos. Esta función es como sigue:

CreateFile proto lpFileName:DWORD,\                            dwDesiredAccess:DWORD,\                            dwShareMode:DWORD,\                            lpSecurityAttributes:DWORD,\                            dwCreationDistribution:DWORD\,                            dwFlagsAndAttributes:DWORD\,                            hTemplateFile:DWORD

Page 77: curso lenguaje ensamblador

dwDesiredAccess especifica qué operación quieres que se haga en el archivo.

0  Abrir el archivo para pedir sus atributos. Tendrás 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 acción a ejecutar por CeateFile cuando el archivo especificado en lpFileName existe o cuando no existe.

CREATE_NEW Crear un nuevo archivo. La función falla si ya existe el archivo especificado.

CREATE_ALWAYS Crea un nuevo archivo. La función sobreescribe el archivo si éste existe.

OPEN_EXISTING Abre el archivo. La función falla si el archivo no existe. OPEN_ALWAYS Abre el archivo si existe. Si el archivo no existe, la función crea el

archivo como si dwCreationDistribution fuera CREATE_NEW. TRUNCATE_EXISTING Abre el archivo. Una vez abierto, el archivo es truncado si su

tamaño es de cero bytes. El proceso llamado debe abrir el archivo como mínimo con el acceso GENERIC_WRITE. La función 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 están comprimidos. Para un directorio esto significa que la compresión es por defecto aplicada a los archivos y subdirectorios nuevos creados.

FILE_ATTRIBUTE_NORMAL El archivo no tiene otros atributos activos. Este atributo es válido sólo si esta solo, no hay ningún 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.

Page 78: curso lenguaje ensamblador

Cuando GlobalAlloc vuelve satisfactoriamente, eax contiene el manejador (handle) al bloque de memoria reservado. Le pasamos este manejador (handle) a la función 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 función 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 parámetro 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 parámetro es la direccion de la variable de tamaño DWORD que rellenaremos con el número de bytes realmente leídos del archivo.

Despues de rellenar el bloque de memoria con los datos, ponemos los datos en el control de edición mandando el mensaje WM_SETTEXT al control de edición con lParam conteniendo el puntero al bloque de memoria. Despues de esta llamada, el control de edición 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 más tiempo ya que nuestra intención es grabar los datos modificados en el control de edición en otro archivo, no el archivo original. Asi que cerramos el archivo llamando a CloseHandle con el manejador (handle) como su parámetro. 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 grabación después. Pero como demostración, yo he elegido liberarla aquí.

                invoke SetFocus,hwndEdit

Cuando la caja de diálogo "abrir archivo" es mostrada en la pantalla, el foco de entrada se centra en ella. Así que después de cerrar el diálogo de "abrir archivo", tendremos que mover el foco de entrada otra vez al control de edición.

Esto termina la operacion de lectura de archivo. En este punto, el usuario puede editar el contenido del control de edición. Y cuando quiera salvar los datos a otro archivo, deberá seleccionar File/Save en el menú que mostrará la caja de diálogo de salvar archivo. La creación de la caja de diálogo de salvar archivo no es muy diferente de la de abrir archivo. Efectivamente, se diferencian sólo en el nombre de la función, GetOpenFileName y GetSaveFileName. Puedes reciclar la mayoría 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 diálogo no nos dejara crear un archivo que no exista ya.

El parámetro dwCreationDistribution de la función CreateFile deberá ser cambiada a CREATE_NEW ya que queremos crear un nuevo archivo.

Page 79: curso lenguaje ensamblador

El resto del código es idéntico a todas las secciones de "abrir archivo" excepto las siguientes líneas:

                        invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory                         invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL

Mandamos el mensaje WM_GETTEXT al control de edición para copiar los datos del bloque de memoria, el valor devuelto en eax es la longitud de los datos dentro del buffer. Después de que los datos estén 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 cómo usarlos para tu provecho. Usar un archivo proyectado en memoria es muy fácil, como verás en este tutorial.

Baja el ejemplo aquí.

Teoría:

Si examinas detenidamente el ejemplo del tutorial previo, encontrarás que tiene un serio inconveniente: ¿qué pasa si el archivo que quieres leer es más 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 cuestión es que deberías leer repetidas veces en los datos desde el inicio del archivo hasta que encuentres el final del archivo. La respuesta para la segunda cuestión es que deberías prepararte para el caso especial al final del bloque de memoria. Esto es lo que se llama el problema del valor del límite. Presenta terribles dolores de cabeza a los programadores y causa innumerables errores [bugs].

Sería agradable localizar un bloque de memoria, lo suficientemente grande para almacenar todo el archivo pero nuestro programa debería ser abundante en recursos. Proyección de archivo al ataque. Al usar proyección 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 fácil como eso. No necesitas usar las funciones de memoria de la API y separar más las funciones de la API para E/S de archivo, todas ellas son una y la misma bajo proyección de archivo.

La proyección de archivos también es usada como un medio de compartir memoria entre los archivos. Al usar proyección de archivos de esta manera, no hay involucrados archivos reales. Es más 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 sincronización de procesos y de hilos, sino tus aplicaciones se quebrarán [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 cómo usar el archivo proyectado como medio para "proyectar" un archivo en memoria. En realidad, el cargador de archivos PE usa proyección de archivo para cargar archivos ejecutables en memoria. Es muy conveniente ya que sólo las partes pueden ser leídas selectivamente desde el archivo en disco. Bajo Win32, deberías usar proyección 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 tamaño no puede ser cambiado durante esa sección. Así que proyectar archivos es muy bueno para archivos de sólo lectura u operaciones

Page 80: curso lenguaje ensamblador

de archivos que no afecten el tamaño del archivo. Eso no significa que no puedes usar proyección de archivo si quieres incrementar el tamaño del archivo. Puedes estimar el nuevo tamaño y crear archivos proyectados en memoria basados en el nuevo tamaño y el archivo se incrementará a ese tamaño. Esto es muy conveniente, eso es todo.

Suficiente para la explicación. Vamos a zambullirnos dentro de la implemantación de la proyección de archivos. Con el fin de usar proyección 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

uno de sus parámetros. Esta función crea un objeto de archivo proyectado a partir del archivo abierto por CreateFile.

3. llamar a MapViewOfFile para proyectar una región del archivo seleccionado o el archivo completo a memoria. Esta función regresa un puntero al primer byte de la región 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 parámetro 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 través de una caja de diálogo "Open File". Abre el archivo utilizando proyección 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

Page 81: curso lenguaje ensamblador

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 también lo usamos como                                                                     ;una bandera en la sección WM_DESTROY

.data? hInstance HINSTANCE ? CommandLine LPSTR ? hFileRead HANDLE ?                               ; Manejador al archivo fuente hFileWrite HANDLE ?                                ; Manejador al archivo salida hMenu HANDLE ? pMemory DWORD ?                                 ; puntero a los datos en el archivo fuente SizeWritten DWORD ?                               ; número de bytes actualmente escritos por WriteFile

.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,\

Page 82: curso lenguaje ensamblador

                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

Page 83: curso lenguaje ensamblador

                    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  

Page 84: curso lenguaje ensamblador

Análisis:

                    invoke CreateFile,ADDR buffer,\                                                 GENERIC_READ ,\                                                 0,\                                                 NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\                                                 NULL

Cuando el usuario selecciona un archivo en el diálogo Open File, llamamos a CreateFile para abrirlo. Nota que especificamos GENERIC_READ para abrir este archivo con acceso de sólo lectura y dwShareMode es cero porque no queremos ningún otro proceso para modificar el archivo durante nuestra operación.

                    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

Deberías saber primero que CreateFileMapping no tiene que proyectar todo el archivo a memoria. Puedes usar esta función para proyectar sólo una parte del archivo actual a memoria. Especificas el tamaño del archivo proyectado a memoria en los parámetros dwMaximumSizeHigh y dwMaximumSizeLow. Si especificas un tamaño mayor al archivo actual, el tamaño de este archivo será expandido. Si quieres que el archivo proyectado sea del mismo tamaño que el archivo actual, pon ceros en ambos parámetros.

Puedes usar NULL en el parámetro lpFileMappingAttributes para que Windows cree un archivo proyectado en memoria con los atributos de seguridad por defecto.

flProtect define la protección deseada para el archivo proyectado en memria. En nuestro ejemplo, usamos PAGE_READONLY para permitir sólo 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 parámetro.

                    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 ubicación [path] completa es almacenado en un buffer, queremos desplegar sólo el nombre del archivo en el encabezado, así que debemos agregar el valor del miembro nFileOffset de la estructura OPENFILENAME a la dirección del buffer.

Page 85: curso lenguaje ensamblador

                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED                     invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

Como precausión, no queremos que el usuario abra más 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ú.

Después 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 código como el siguiente:

    .ELSEIF uMsg==WM_DESTROY         .if hMapFile!=0             call CloseMapFile         .endif         invoke PostQuitMessage,NULL

En el recorte de código 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 función CloseMapFile que contiene el siguiente código:

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 pérdida 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 diálogo común "save as". Después de que el usuario escribe el nombre del nuevo archivo, el archivo es creado por la función CreateFile.

                    invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0                     mov pMemory,eax

Inmediatamente después de que el archivo de salida es creado, llamamos a MapViewOfFile para proyectar la parte deseada del archivo proyectado en memoria. Esta función 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, sólo queremos leer los datos de manera que usamos FILE_MAP_READ. dwFileOffsetHigh y dwFileOffsetLow especifican el desplazamiento inicial de la proyección del archivo que queremos proyectar en memoria. En nuestro caso, queremos leerlo todo, de manera que comenzamos desde el desplazamiento cero en adelante.

Page 86: curso lenguaje ensamblador

dwNumberOfBytesToMap especifica el número de bytes a proyectar en memoria. Si quieres proyectar todo el archivo (especificado por CreateFileMapping), paamos 0 a MapViewOfFile. Después de llamar a MapViewOfFile, la porción deseada es cargada en memoria. Obtendrás el puntero al bloque de memoria que contiene los datos del archivo.

                    invoke GetFileSize,hFileRead,NULL

Conseguir el tamaño del archivo. El tamaño del archivo es regresado en eax. Si el archivo es mayor a 4 GB,  la palabra alta DWORD del tamaño del archivo es almacenada en FileSizeHighWord. Ya que no esperamos manejar un archivo de tal tamaño, 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 deseábamos 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 difuminación del elemento Save As del menú

Aprenderemos qué es un proceso, cómo crearlo y cómo terminarlo.

Bajar el ejemplo aquí.

Preliminares:

¿Qué es un proceso? He extrañido esta definición del referencia de la API de Win32:

"Un proceso es una aplicación en ejecución que consiste en un espacio de direcciones privado, código, datos, y otros recursos del sistema operativo, tales como archivos, tuberías, y objetos de sincronización que son visibles al proceso."

Cómo puedes ver, un proceso "se apropia" de algunos objetos: el espacio de direcciones, el módulo ejecutante o los módulos, y cualquier cosa que el módulo ejecutante pueda crear o abrir. Al menos, un proceso debe consistir en un módulo 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 ejecución. Cuando Windows crea un proceso, crea sólo un hilo para el proceso. Este hilo generalmente inicia la ejecución desde la

Page 87: curso lenguaje ensamblador

primera instrucción en el módulo. Si el proceso luego necesita más hilos, puede crearlos explícitamente.

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. Después de eso crea el hilo primario del proceso.

Bajo Win32, puedes crear procesos desde tus programas llamando a la función 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 número de parámetros. Podemos ignorar muchos de ellos.

lpApplicationName --> El nombre del archivo ejecutable, con o sin ubicación, que quieres ejecutar. Si este parámetro es nulo, debes proveer el nombre del archivo ejecutable en el parámetro lpCommandLine

lpCommandLine   --> Los argumentos en la línea de órdenes del programa que quieres ejecutar. Nota que si lpApplicationName es NULL, este parámetro debe contener también 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? También 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 parámetro 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 debería aparecer. la estructura STARTUPINFO contienen muchos miembros que especifican la apariencia de la ventana principal del proceso hijo. Si no quieres

Page 88: curso lenguaje ensamblador

nada especial, puedes llenar la estructura STARTUPINFO con los valores del proceso padre llamando a la función GetStartupInfo.

lpProcessInformation --> Apunta a la estructura PROCESS_INFORMATION que recibe información sobre la identificación 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.

Después de llamar a CreateProcess, se crea un nuevo proceso y la llamada a CreateProcess regresa de inmediato. Puedes chequear si el nuevo proceso todavía está activo llamando a la función GetExitCodeProcess que tiene la siguiente sintaxis:

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

Si esta llamada tiene éxito, lpExitCode contiene el status de terminación del proceso en cuestión. Si el valor en lpExitCode es igual a STILL_ACTIVE, entonces ese proceso todavía está corriendo.

Puedes forzar la terminación de un proceso llamando a la función TerminateProcess. Tiene la siguiente sintaxis:

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

Puedes especificar el código 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 función 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

Page 89: curso lenguaje ensamblador

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 código 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

Page 90: curso lenguaje ensamblador

    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

Page 91: curso lenguaje ensamblador

                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

Análisis:

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 aún no ha comenzado, queremos habilitar el elemento "start process" y difuminar [gray out] el elemento "terminate process". Hacemos la inversión si el nuevo proceso ya está activo.

Primero chequeamos si el nuevo proceso todavía está activo llamando a la función GetExitCodeProcess con el manejador de proceso llenado por la función CreateProcess. Si GetExitCodeProcess regresa FALSE, significa que el proceso no ha comenzado todavía así

Page 92: curso lenguaje ensamblador

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 todavía está corriendo. Así que comparamos el valor en ExitCode al valor STILL_ACTIVE, si son iguales, el proceso todavía 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 sección .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 todavía. Así que este es el momento de hacerlo.

Si llamamos a la función GetStartupInfo llenaremos la estructura startupinfo que pasaremos a la función CreateProcess. Después de que llamamos a la función CreateProcess para comenzar el nuevo proceso. Nota que no hemos chequeado el valor regersado por CreateProcess ya que haría más complejo el ejemplo. En la vida real, deberías chequear el valor regresado por CreateProcess. Inmediatamente después de CreateProcess, cerramos el manejador de hilo primario regresado en la estructura processInfo. Cerrar el manejador no significa que hemos terminado el hilo, sólo 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 función GetExitCodeProcess. Si todavía está activo, llamamos la función TerminateProcess para matar el proceso. También cerramos el manejador del proceso hijo ya que no lo necesitamos más.

Tutorial 15: Programación Multihilo

En este tutorial aprenderemos como crear un programa multihilos [multithreading program]. también estudiaremos los métodos de comunicación entre los hilos.

Bajar el ejemplo aquí.

Page 93: curso lenguaje ensamblador

Teoría:

En el tutorial previo, aprendiste que un proceso consta de al menos un hilo [thread]: el hilo primario. Un hilo es una cadena de ejecución. también puedes crear hilos adicionales en tu programa. Puedes concebir la programación multihilos [multithreading programming] como una programación multitareas [multitasking programming] dentro de un mismo programa. En términos de implementación, un hilo es una función que corre concurrentemente con el hilo principal. Puedes correr varias instancias de la misma función o puedes correr varias funciones simultáneamente dependiendo de tus requerimientos. La programación multihilos es específica 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 también 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 categorías:

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 través de su propia ventana. Este tipo de hilo está sujeto a la regla del Mutex de Win16 que permite sólo un hilo de interface de usuario en el núcleo de usuario y gdi de 16-bit. Mientras el hilo de interface de usuario esté ejecutando código de núcleo de usuario y gdi de 16-bit, otros hilos UI no podrán usar los servicios del núcleo de usuario y gdi. Nota que este Mutex de Win16 es específico a Windows 95 desde su interior, pues las funciones de la API de Windows 95 se remontan [thunk down] hasta el código de 16-bit. Windows NT no tiene Mutex de Win16 así que los hilos de interface de usuario bajo NT trabajan con más fluidez que bajo Windows 95.

2. Hilo obrero [Worker thread]: Este tipo de hilo no crea ninguna ventana así que no puede recibir ningún mensaje de ventana. Existe básicamente para hacer la tarea asignada en el trasfondo hence el nombre del hilo obrero.

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 público. 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 podría atender bien al público ni a la prensa. Esto sería 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 podría beneficiarse con la creación de un hilo adicional que sería el respondable de la extensa tarea, permitiendo al hilo primario responder a las órdenes del usuario.

Podemos crear un hilo llamando a la función CreateThread que tiene la siguiente sintaxis:

CreateThread proto lpThreadAttributes:DWORD,\                                 dwStackSize:DWORD,\                                 lpStartAddress:DWORD,\                                 lpParameter:DWORD,\                                 dwCreationFlags:DWORD,\                                 lpThreadId:DWORD

Page 94: curso lenguaje ensamblador

La función 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 tamaño de la pila del hilo. Si quieres que la pila del nuevo hilo tenga el mismo tamaño que la pila del hilo primario, usa NULL en este parámetro. lpStartAddress--> Dirección de la función del hilo. Es la función que hará el trabajo del hilo. Esta función DEBE recibir uno y sólo un parámetro de 32-bits y regresar un valor de 32-bits. lpParameter  --> El parámetro que quieres pasar a la función del hilo. dwCreationFlags --> 0 significa que el hilo corre inmediatamante después de que es creado. Lo opuesto es la bandera CREATE_SUSPENDED. lpThreadId --> La función CreateThread llenará el ID del hilo del nuevo hilo creado en esta dirección.

Si la llamada a CreateThread tiene éxito, regresa el manejador del hilo creado. Sino, regresa NULL.

La función 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 función ResumeThread.

Cuando la función del hilo regresa con la instrucción ret, Windows llama a la función ExitThread para la función de hilo implícitamente. Tú mismo puedes llamar a la función ExitThread con tu función de hilo pero hay un pequeño punto qué considerar al hacer esto. Puedes regresar el código de salida del hilo llamando a la función GetExitCodeThread. Si quieres terminar un hilo desde otro, puedes llamar a la función TerminateThread. Pero sólo deberías usar esta función bajo circunstancias extremas ya que la función termina el hilo de inmediato sin darle chance al hilo de limpiarse después.

Ahora vamos a ver los métodos de comunicación 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 método debe ser usado con cuidado. La sincronización 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 actualización de la estructura? ¡El otro hilo quedará con datos inconsistentes en la estructura! No cometas ningún error, los programas multihilos son difíciles de depurar y de mantener. Este tipo de errores parecen ocurrir al azar lo cual es muy difícil de rastrear.

También puedes usar mensajes Windows para comunicar los hilos entre sí. Si todos los hilos son interface de usuario, no hay problema: este método puede ser usado como una comunicación en dos sentidos. Todo lo que tienes que hacer es definir uno o más mensajes de ventana hechos a la medida que sean significativos sólo 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

Page 95: curso lenguaje ensamblador

Windows no usará ningún 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 método como dos vías de comunicación 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 método en nuestro ejemplo. El último método de comunicación es un objeto de evento. Puedes concebir un objeto de evento como un tipo de bandera. Si el objeto evento es un estado "no señalado" [unsignalled], el hilo está durmiendo o es un durmiente, en este estado, el hilo no recibe una porción de tiempo del CPU. Cuando el objeto de evento está en estado "señalado" [signalled], Windows "despierta" el hilo e inicia la ejecución de la tarea asignada.

 

Ejemplo:

Deberías 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 cálculo se ha completado, aparece una caja de mensaje. Después de eso, la ventana acepta tus órdenes normalmente.

Para evitar este tipo de inconvenientes al usuario, podemos mover la rutoina de "cálculo" a un hilo obrero separado y dejar que el hilo primario continúe con su tarea de interface de usuario. Incluso puedes ver que aunque la ventana principal responde más lento que de costumbre,  todavía 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

Page 96: curso lenguaje ensamblador

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

Page 97: curso lenguaje ensamblador

    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  

Análisis:

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 través del siguiente código:

Page 98: curso lenguaje ensamblador

            .if ax==IDM_CREATE_THREAD                 mov  eax,OFFSET ThreadProc                 invoke CreateThread,NULL,NULL,eax,\                                         NULL,0,\                                         ADDR ThreadID                 invoke CloseHandle,eax   La función de arriba crea un hilo que creará un procedimiento llamado ThreadProc que correrá concurrentemente con el hilo primario. Después de una llamada satisfactoria, CreateThread regresa de inmediato y ThreadProc comienza a correr. Puesto que no usamos manejadores de hilos, deberíamos 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 más 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 cáculo salvaje que tarda un poco para terminar y cuando finaliza envía 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 más seguro hacerlo así.

El mensaje WM_FINISH es significativo sólo dentro del programa. Cuando la ventana principal recibe el mensaje WM_FINISH, responde desplegando una caja de mensaje que dice que el cálculo ha terminado.

Puedes crear varios hilos en sucesión enviando varias veces el mensaje "Create Thread". En este ejemplo, la comuicación se realiza en un solo sentido ya que sólo un hilo puede notificar la ventana principal. Si quieres que el hilo principal envíe ó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.

Page 99: curso lenguaje ensamblador

Bajar el ejemplo aquí.

Teoría:

En el tutorial anterior, demostré como se comunican los hilos usando un mensaje de ventana custom. I left out otros dos métodos: 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 "señalado". Cuando es desactivado [turned off], está en estado "no-señalado". Creas un evento y pones en un recorte de código en los hilos releventes para ver el estado del objeto evento. Si el objeto evento está en el estado no señalado, los hilos que esperan serán puestos a dormir [asleep]. Cuando los hilos están en estado de espera, consumen algo de tiempo del CPU.

Creas un objeto evento llamando a la función 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 automáticamente restablezca el objeto evento a estado no señalado después de llamara WaitForSingleObject, debes especificar FALSE en este parámetro. Sino debes restablecer manualmente el objeto evento con la llamada a ResetEvent.

bInitialState--> Si quieres que el objeto evento sea creado en el estado señalado, especifica TRUE como este parámetro sino el objeto evento será creado en estado no señalado. 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 función SetEvent pone el objeto evento en estado señalado. 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 sincronización. El objeto evento es uno de los objetos de sincronización. dwTimeout --> especificar el tiempo en milisegundos que esta función esperará para ser el objeto señalado. Si el tiempo especificado ha pasado y el evento objeto todavía no está en estado señalado, WaitForSingleObject regresa a la instrucción que le llamó. Si quieres esperar por el objeto indefinidamente, debes especificar el valor INFINITE como valor de este parámetro.

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 cálculo salvaje.

Page 100: curso lenguaje ensamblador

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

Page 101: curso lenguaje ensamblador

    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

Page 102: curso lenguaje ensamblador

                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

Análisis:

En este ejemplo, demuestro otra técnica 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,\

Page 103: curso lenguaje ensamblador

                             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 señalado con restableci iento automático. Después 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 señalado según el código 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 señalado 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 señalado siguiendo este código:

            .if ax==IDM_START_THREAD                 invoke SetEvent,hEventStart

La llamada a SetEvent pone el evento en estado señalado 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 señalado porque especificamos el parámetro 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 cómo crearlas. Te puedes bajar el ejemplo aquí.  

Teoría:

Page 104: curso lenguaje ensamblador

Si tu programa se agranda demasiado, encontrarás que los programas que escribes usualmente tienen algunas rutinas en común. Es una pérdida de tiempo reescribirlas cada vez que empiezas un nuevo programa. Volviendo a los viejos tiempos del DOS, los programadores almacenaban estas rutinas que usaban comúnmente en una o varias librerías. Cuando querían usar las funciones, enlazaban la librería al archivo objeto y el enlazador extraía las funciones de la librería y las insertaba en el ejecutable final. Este proceso se llama enlace estático. Las librerías de rutinas del lenguaje C son un buen ejemplo. La parte negativa de este método está en que tienes funciones idénticas en muchos programas que las usan. Tu espacio en disco se llena almacenando copias idénticas de las funciones. Pero para programas de DOS este método 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 situación es mas crítica porque puedes tener varios programas funcionando al mismo tiempo. La memoria es consumida rápidamente si tu programa es bastante grande. Windows tiene la solución a este tipo de problemas: dynamic link libraries [librerias de enlace dinámico]. Las librerías de enlace dinamico son una especie de recopilación 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 tendrán 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 mágico a través de la paginación de manera que todos los procesos compartan el mismo código de la DLL. Así que en la memoria fisica sólo hay una copia del código de la DLL. Como siempre, cada proceso tendrá su sección única de datos de la DLL.

El programa enlaza la DLL en tiempo de ejecución [at run time], no como en las viejas librerías estáticas. ¿Por qué se la llama librería de enlace dinámico?. Sólo 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 todavía es usada por algún otro programa, la DLL continúa en memoria hasta que el último programa que la use la descargue.

Como siempre, el enlazador tiene el trabajo más difícil 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 información sobre la DLL y las funciones en el ejecutable final para poder localizar y cargar la DLL correcta en tiempo de ejecución [at run time].

Ahí es donde interviene la librería de importación [import library]. Una librería de importación contiene la información sobre la DLL que representa. El enlazador puede extraer la información que necesita de la librería de importación 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 librería sin dejárselo al cargador de Windows. Este método tiene sus pro y sus contras:

No se necesita una librería de importación, así que puedes cargar y usar cualquier librería siempre aunque no venga con librería de importación. Pero todavía tienes que saber algo sobre las funciones de su interior, cuantos parámetros 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 operación. Si cargas la DLL tú mismo, cuando la DLL no ha sido encontrada y no es esencial para la operación tu programa podrá advertir al usuario sobre el suceso y seguir.

Page 105: curso lenguaje ensamblador

Puedes llamar a funciones *no documentadas* que no están incluidas en la librería de importación. Suponiendo que conozcas suficiente información acerca de la función .

Si usas LoadLibrary tienes que llamar a GetProcAddress para todas las funciones que quieres llamar. GetProcAddress devuelve la dirección del punto de entrada de la función 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 función 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 función de punto de entrada. Windows llamará a la función del punto de entrada en caso que:

Page 106: curso lenguaje ensamblador

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 función del punto de entrada como quieras pero tendrás que terminarla END <Nombre de la función de entrada>. Esta función tiene tres parametros, sólo los dos primeros de estos son importantes. hInstDLL es el manejador del módulo (module handle) de la DLL. Este no es el mismo que el manejador de la instancia (instance handle) del proceso. Tendrás que guardar este valor si necesitas usarlo mas tarde. No podrás obtenerlo otra vez fácilmente. 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 inicialización .

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 inicialización debe reservar algo de memoria y no puede hacerlo satisfactoriamente, la función del punto de entrada devolverá FALSE para indicar que la DLL no puede funcionar.

Puedes poner tus funciones en la DLL detrás o delante de la función 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 módulo de definición (.def).

LA DLL necesita un archivo de módulo de definición en su entorno de desarrollo. Vamos a echarle un vistazo a esto.

LIBRARY   DLLSkeleton EXPORTS   TestFunction

Normalmente deberás tener la primera linea. La declaración LIBRARY define el nombre interno del módulo de la DLL. Tendrás que proporcionarlo con el nombre de archivo de la DLL. La definición 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 módulos sean capaces de llamar a TestFunction, así que ponemos el nombre en la definición EXPORTS.

Cualquier otro cambio es en las opciones del enlazador. Deberás poner  /DLL opciones y /DEF:<el nombre de archivo de tu def> en las opciones de tu enlazador algo así :

Page 107: curso lenguaje ensamblador

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 después de enlazar el archivo objeto, obtendrás un .dll y un .lib. El .lib es la librería 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 ?                                         ; el manejador (handle) de la librería (DLL) TestHelloAddr dd ?                        ; la dirección de la función 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 librería (DLL). Si no devolverá NULL ; Puedes pasar el manejador (handle) a GetProcAddress u otra función que requiera ; el manejador (handle) de la librería 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 librería, lo pasas a GetProcAddress con la 

Page 108: curso lenguaje ensamblador

; dirección del nombre de la función en la DLL que quieres llamar. Esto devuelve la dirección ; de la función si es correcto. De otra manera devuelve NULL ; Las direcciones de las funciones no cambian a menos que descarges y recarges la librería . ; 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 función con un simple call con la variable conteniendo ; la dirección de la función como operando. ;------------------------------------------------------------------------------------------------------------------------------------------------------                 .endif                 invoke FreeLibrary,hLib ;------------------------------------------------------------------------------------------------------------- ; Cuando no necesitas mas la librería descargarla con FreeLibrary. ;-------------------------------------------------------------------------------------------------------------         .endif         invoke ExitProcess,NULL end start

Puedes ver que usando LoadLibrary es un poco mas problemático pero mas flexible

Tutorial 18: Controles ComunesAprenderemos qué son los contrioles comunes y cómo usarlos. Este tutorial sólo será una introducción rápida a ellos.

Bajar el ejemplo de código fuente aquí.

Teoría:

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 tenían que escribir el código 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

Page 109: curso lenguaje ensamblador

Property page Tree view List view Animación 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 sería un despilfarro de recursos. Todos ellos, con excepción del control "rich edit", están 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 más grande que su brethren.

Puedes cargar comctl32.dll incluyendo una llamada a InitCommonControls en tu programa.

InitCommonControls es una función en comctl32.dll, así que refiriéndola en cualquier parte del código de tu programa hará que el cargador de archivos PE cargue comctl32.dll cuando corra tu programa.No tienes que ejecutarla , sólo inclúyela en algún lugar de tu código. ¡Esta función no hace NADA! Su unica instrucción es "ret". Su único propósito es incluir la referencia a comctl32.dll en la sección de importación 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 fucnión 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 explícitamente y luego llamar a FreeLibrary para descargarlo.

Ahora aprenderemos cómo crearlos. Puedes usar un editor de recursos para incorporarlos en las cajas de diálogo o puedes crearlos túmismo. 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 creación específicas, sin embargo, they are just wrappers around CreateWindowEx para facilitar la creación de esos controles. Las funciones de creación específicas 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 Control Común

Page 110: curso lenguaje ensamblador

ToolbarWindow32 Toolbar

tooltips_class32 Tooltip

msctls_statusbar32 Status bar

SysTreeView32 Tree view

SysListView32 List view

SysAnimate32 Animación

SysHeader32 Header

msctls_hotkey32 Hot-key

msctls_progress32 Progress bar

RICHEDIT Rich edit

msctls_updown32 Up-down

SysTabControl32 Tab

Los controles property sheets, property pages y image list tienen sus propias funciones de creación específicas. 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 guión 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 Programación 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. También tienen sus estilos específicos 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 cómo crear controles comunes, podemos ver el método de comunicación 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 través de WM_COMMAND. En vez de eso, ellos envían mensajes WM_NOTIFY a la ventana padre cuando algunos eventos interesantes

ocurren con ellos. El padre puede controlar al hijo enviándoles mensajes. También hay muchos mensajes para los nuevos controles. Deberías consultar tu referencia de la api de win32 para

más detalles.

Vamos ver los controles de barra de progreso [progress bar] y barra de estado [status bar] en el siguiente ejemplo.

Código 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

Page 111: curso lenguaje ensamblador

.const IDC_PROGRESS equ 1            ; IDs de los controles IDC_STATUS equ 2 IDC_TIMER  equ 3

.data ClassName  db "CommonControlWinClass",0 AppName    db "Common Control Demo",0 ProgressClass  db "msctls_progress32",0       ; el nombre de la clase de la barra de progreso Message  db "Finished!",0 TimerID  dd 0

.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_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\

Page 112: curso lenguaje ensamblador

           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

Page 113: curso lenguaje ensamblador

    .endif     xor eax,eax     ret WndProc endp end start

Análisis:

    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT     invoke ExitProcess,eax     invoke InitCommonControls

Deliberadamente pongo InitCommonControls después de ExitProcess para demostrar que InitCommonControls está justo ahí para poner una referencia a comctl32.dll en la sección de importación. 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 común. Nota que esta llamada a CreateWindowEx contiene hWnd como manejador de la ventana padre. También 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

Después de que es creada la barra de progreso, podemos establecer su rango. El rango por defecto es desde 0 a 100. Si no estás satisfecho con esto, puedes especificar tu propio rango con el mensaje PBM_SETRANGE. lParam de este parámetro contiene el rango, el máximo rango está en la palabra alta y el mínimo está en la palabra baja. Puedes especificar cuánto 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ú envías un mensaje PBM_STEPIT a la barra de progreso, el indicador de progreso se incrementará por 10. También puedes poner tu indicador de posición enviando mensajes PBM_SETPOS. Este mensaje te da un control más 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 fácil de entender, así que no la comentaré. Después de que es creada la ventana de estado, creamos un temporizador. En este ejemplo, actualizaremos la barra de progreso en un intervalo regular

Page 114: curso lenguaje ensamblador

de 100 ms así que debemos crear un control de teporización. Abajo está el prototipo de la función 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 envía un mensaje WM_TIMER lpTimerProc : la dirección de la función del temporizador que será llamada cuando el intervalo de tiempo expire. Si este parámetro es NULL, el temporizador enviará más 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 envía un mensaje WM_TIMER. Aquí pondrás el código que será ejecutado. En este ejemplo, actualizamos la barra de progreso y luego chequeamos si ha sido alcanzado el límite máximo. 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 más, también aprenderemos cómo arrastrar objetos y ponerlos en el control Tree View; además veremos cómo usar una lista de imágenes en este control. Puedes bajar el ejemplo aquí.

Tutorial 19: Control Tree View

Teoría:

Un control Tree View es un tipo especial de ventana que representa objetos en orden jerárquico. 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 diálogo. No hay que olvidar poner una llamada a InitCommonControls en el código fuente. Hay varios estilos específicos al control Tree View. Estos tres son los más usados.

Page 115: curso lenguaje ensamblador

TVS_HASBUTTONS == Despliega botones más (+) y menos (-) al lado de los elementos [items] padres. El usuario hace click con los botones del ratón 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 líneas para mostrar la jerarquía de elementos. TVS_LINESATROOT == Usa líneas 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 través de mensajes. La ventana padre le puede enviar varios mensajes y el control Tree View puede enviar mensajes de "notificación" a su ventana padre. En este proceso, el control Tree View no difiere de las otras ventanas: cuando algo interesante ocurre en él, envía un mensaje WM_NOTIFY con información 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                    más 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 envía este mensaje WM_NOTIFY.

idFrom es el ID del control que envía 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 envía TVN_xxxx en el miembro código de NMHDR. La ventana padre puede enviar TVM_xxxx para controlarlo.

 

Agregando elementos a un control list view

Después de crear un control Tree View, podemos agregarle elementos. Puedes hacer esto enviándole TVM_INSERTITEM.

Page 116: curso lenguaje ensamblador

TVM_INSERTITEM wParam = 0; lParam = pointer to a TV_INSERTSTRUCT;

A estas alturas, bebes conocer cierta terminología sobre la relación 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 algún(os) otro(s) subelemento(s) asociado(s) con él. Al mismo tiempo, el el elemento padre puede ser un hijo de algún otro elemento. Un elemento sin un padre se llama elemento raíz. Puede haber muchos elementos raíz 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 después 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 alfabético.

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 información 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 información sobre el elemento seleccionado.

Page 117: curso lenguaje ensamblador

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 sólo que el miembro pszText es válido. 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 sólo 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 tamaño del buffer proveído. Hay que suministrar el tamaño del buffer en este miembro. iImage y iSelectedImage refieren al índice dentro de una lista de imágenes que contienen las imágenes a ser mostradas cuando el elemento no es seleccionado (iImage) y cuando es seleccionado (iSelectedImage). Si corres el Explorador de Windows, verás que en el panel izquierdo unas imágenes que representan las carpetas; esas imágenes están 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 imágenes 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 imágenes y asociarla con el control tree view. Se puede crear una lista de imágenes llamando a ImageList_Create.

ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \                                             cInitial:DWORD,  cGrow:DWORD

Si esta función tiene éxito, regresa el manejador a una lista de imágenes vacía.

cx == ancho de cada imagen en esta lista, en pixeles. cy == altura de cada imagen en esta lista, en pixeles. Todas las imágenes en una lista deben ser iguales en tamaño. Si se especifica un gran bitmap, Windows usará cx y cy para *cortarla* en varios pedazos. Así que las imágenes propias deben prepararse como una secuencia de "pinturas" del mismo tamaño. flags == especifica el tipo de imágnes en esta lista: si son de color o monócromos y su profundidad de color. Consulta la refencia de la API de Win32 para mayor información. cInitial == El número de imágenes que esta lista contendrá inicialmente. Windows usará esta información para localizar memoria para las imágenes. cGrow == Cantidad de imágenes a las que la lista de imágenes puede expandirse cuando el sistema necesita cambiar el tamaño de la lista para hacer disponibles más imágnes. Este parámetro representa el número de imágenes nuevas que puede tener una lista de imágenes que ha cambiado de tamaño.

¡Una lista de imágenes no es una ventana! Es sólo un depósito de imágenes que ha de ser usado por otras ventanas. Después de crear una lista de imágens, se deben agregar imágenes llamando a ImageList_Add

ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD

Page 118: curso lenguaje ensamblador

Esta función regresa -1 si no tiene éxito. himl == manejador de la lista de imágenes a la cual quieres agregar imágenes. Es el valor regresado por una llamada satisfactoria a ImageList_Create hbmImage == manejador del bitmap a ser agregado a la lista de imágenes. Usualmente almacenas el bitmap en el recurso y lo cargas en la llamada a LoadBitmap. Nota que no tienes que especificar los parámetros pasados con el número de imágenes contenidas en este bitmap porque esta información es inferida a partir de los parámetros cx y cy pasados con la llamada a ImageList_Create. hbmMask == Manejador [handle] al bitmap que contiene la máscara. Si no se usa ninguna máscara con la lista de imágenes, se ignora este parámetro.

Normalmente, agregaremos sólo dos imágenes a la lista de imágenes 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 imágenes 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 TVSIL_NORMAL Establece la lista de iágenes normal, que contiene los elementos seleccionados y no seleccionados para el elemento del control tree-view.

o TVSIL_STATE Establece el estado de la lista de imágenes, que contiene las imágens para los elementos del control tree-view que están en un estado definido por el usuario.

lParam = Manejador de la lista de imágenes

 

Recuperar información sobre el elemento del treeview

Puedes recuperar información 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 información

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 más importante, debes llenar hItem con el manejador al elemento del cual deseas obtener información. Pero esto tiene un problema: ¿Cómo puedes conocer el manejador del elemento del cual deseas recuperar información? ¿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

Page 119: curso lenguaje ensamblador

lParam = manejador a un elemento tree view (sólo necesario para algunos valores de la bandera)

El valor en wParam es tan importante que prefiero presentar abajo todas las banderas:

o TVGN_CARET Regresa el elemento seleccionado. o TVGN_CHILD Regresa el primer elemento hijo del elemento especificado por

el parámetro hitem o TVGN_DROPHILITE Regresa el elemento que es el target de una operación

arrastrar-y-soltar [drag-and-drop]. o TVGN_FIRSTVISIBLE Regresa el primer elemento visible. o TVGN_NEXT Regresa el siguiente elemento hermano [sibling]. o 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.

o TVGN_PARENT Regresa el padre del elemento especificado. o TVGN_PREVIOUS Regresa el elemento hermano anterior. o 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.

o TVGN_ROOT Regresa el primer elemento o el que está en el tope del control

tree-view.

Como puede verse, si se quiere recuperar el manejador a un elemento del control tree view este mensaje resulta de gran interés. 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.

 

Operación Drag and Drop [arrastrar y soltar] en un control tree view

Esta parte es la razón 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 carecía de la información 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 están 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 envía la notificación 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 imágenes con sólo una imagen de arratre y te regresará el manejador de la lista de imágenes.

2. Después 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 , \

Page 120: curso lenguaje ensamblador

                                                    dxHotspot:DWORD, \                                                     dyHotspot:DWORD himlTrack es el manejador a la lista de imágenes que contiene la imagen de arratre. iTrack es el índice a la lista de imágenes 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 ratón, 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 debería 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.

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 debería 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, tendrás que soportar la operación 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 localización para la operación 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 ratón. La solución es capturar la entrada del ratón con SetCapture. Usando esta llamada, los mensajes del ratón serán enviados a la ventana especificada independientemente de donde esté el cursor del ratón.

5. Dentro del manejador de WM_MOUSEMOVE, actualizarás el paso del arrastre con una llamada a ImageList_DragMove. Esta función mueve la imagen que está siendo arrastrada durante una operación de arrastar-y-soltar [drag-and-drop]. Además, 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 algún 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, después que la operación de iluminación [hilite] haya finalizado, llamar de nuevo a ImageList_DragShowNolock para mostrar la imagen de arrastre.

6. Cuando el ususario suelta el botón izquierdo del ratón, debes hacer varias cosas. Si tú iluminas [hilite] un elemento, debes quitarle la iluminación [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 iluiminación el elemento, obtendrás un efecto extraño: cuando selecciones algún otro elemento, ese elemento será encerrado por un rectángulo pero la iluminación estará sobre el último elemento iluminado. Luego, debes llamar a ImageList_DragLeave seguido por ImageList_EndDrag. Debes liberar al ratón llamando a ReleaseCapture. Si creas una lista de imágenes, debes destruirla llamando a ImageList_Destroy. Después de eso, puedes ir a lo que que quieres que haga tu programa cuando la operación arrastrar y soltar [drag & drop] se haya completado.

Código de demostración:

Page 121: curso lenguaje ensamblador

.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 raíz del control tree view hImageList dd ?                  ; manejador de la lista de imágenes usadas en el control tree view hDragImageList  dd ?          ; manejador de la lista de imágenes 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

Page 122: curso lenguaje ensamblador

    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_MAXIMIZEBOX+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 imágenes 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 imágenes         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

Page 123: curso lenguaje ensamblador

        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

Page 124: curso lenguaje ensamblador

        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

Análisis:

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_LINESATROOT,0,\             0,200,400,hWnd,NULL,\             hInstance,NULL

Notar los estilos. TVS_xxxx son los estilos específicos 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

Después, se crea una lista de imágenes vacía para que acepte imágenes de 16x16 pixeles en tamaño, 16-bit de color e inicialmente, contendrá 2 imágenes pero puede ser expandido hasta 10 si se necesita. Luego cargamos el bitmap desde el recurso y lo agregamos a la lista de imágenes. Después de eso, borramos el manejador del bitmap ya que no será usado más. Cuando la lista de imágenes 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 raíz. Puesto que será el elemento raíz, 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 válido. Llenamos a esos tres miembros de valores apropiados. pszText contiene la etiqueta del elemento raíz, iImage es el índice a la imagen en la lista de la

Page 125: curso lenguaje ensamblador

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 estén llenos, enviamos el mensaje TVM_INSERTITEM al control tree view para agregar el elemento raíz 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

Después 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 imágenes idénticas en la lista de imágenes, 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 envía un mensaje WM_NOTIFY con el código TVN_BEGINDRAG. lParam es el puntero a una estructura NM_TREEVIEW que contiene algunas piezas de información 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 imágenes 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 operación de arrastre llamando a ImageList_DragEnter. Esta función 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 debería ser inicializada. Después de eso capturamos la entrada del ratón 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

Page 126: curso lenguaje ensamblador

            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 posición de la imagen de arrastre con ImageList_DragMove. Después de eso, chequeamos si la drag image está sobre algún 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 operación de iluminación, 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 botón izquierdo del ratón, llega a su final la operación 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, también chequeamos el último elemento iluminado [hilited], y lo seleccionamos. También debemos quitar la iluminación [un-hilite], sino los otros elementos no serán iluminaods cuando ellos sean seleccionados. Y finalmente, liberamos la captura del ratón.

Tutorial 20: Subclasificación de Ventanas

Page 127: curso lenguaje ensamblador

En este tutorial, aprenderemos acerca de la subclasificación de ventanas, qué es y como aprovecharla. Bajar el ejemplo aquí.

Teoría:

Si programas por un tiempo en Windows encontrarás casos donde tu ventana tiene CASI todos los atributos que deseas pero no todos. ¿Haz encontrado una situación donde deseas una clase especial de control de edición que filtre algunos caracteres indeseables? Lo más directo que se puede hacer es codificar tu propia ventana. Pero relamente es un trabajo duro y consume tiempo. La subclasificación de Windows al rescate.

En pocas palabras, la subclasificación de ventanas te permite hacerte cargo de la ventana subclasificada. Tienes el control absoluto sobre ella. Tomemos un ejemplo para clarificar más. Supongamos que necesitas una caja de texto que acepte sólo cadenas en hexadecimal. Si usas un control de edición simple, tienes que decir algo cuando el usuario transcribe algo diferente a números 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 cómo hacerlo. Cuando el usuario tipea algo dentro de la caja de texto, Windows envía el mensaje WM_CHAR al procedimiento de ventana del control de edición. 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 edición. 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 edición. Veamos el siguiente flujo:

Antes de la Subclasificación

Windows ==> procedimiento de ventana del control de edición

Después de la Subclasificación

Windows ==> nuestro procedimiento de ventana ----> procedimiento de ventana del control de edición

Ahora ponemos nuestra atención en cómo subclasificar una ventana. Nota que la subclasificación no está limitada a los controles, puede ser usada con cualquier ventana. Pensemos cómo Windows llega a tener conocimiento sobre dónde residen los procedimientos de ventana de los controles de edición. Una pista?......el miembro lpfnWndProc de la estructura WNDCLASSEX. Si podemos reemplazar este miembro con la dirección de nuestro procedimiento de ventana, Windows enviará más 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.

Page 128: curso lenguaje ensamblador

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 dirección del procedimiento de ventana. GWL_HINSTANCE Establece un nuevo manejador de instacia de la aplicación. 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 aplicación que creó la ventana.

dwNewLong = el valor de reemplazo.

Así que la tarea es fácil. Programamos un procedimiento de ventana que maneje los mensajes para el control de edición y luego llamamos a SetWindowLong con la bandera GWL_WNDPROC, pasando junto a ella la dirección de nuestro procedimento de ventana como tercer parámetro. Si la función tiene éxito, el valor de regreso es el valor previo del número entero especificado de 32-bit, en nuestro caso, la dirección del procedimiento de ventana original. Necesitamos almacenar este valor para usarlo dentro de nuestro procedimiento de ventana.

Recuerda que habrá algunos mensajes que no querrás manejar, los pasaremos al procedimiento de ventana original. Podemos hacerlo llamando a la función CallWindowProc.

CallWindowProc PROTO lpPrevWndFunc:DWORD, \                                             hWnd:DWORD,\                                             Msg:DWORD,\                                             wParam:DWORD,\                                             lParam:DWORD

lpPrevWndFunc = la dirección del procedimiento de ventana original.

Los restantes cuatro parámetros son pasados a nuestro procedimiento de ventana. Los pasamos justo con CallWindowProc.

Código 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

Page 129: curso lenguaje ensamblador

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_MAXIMIZEBOX+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

Page 130: curso lenguaje ensamblador

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

Page 131: curso lenguaje ensamblador

EditWndProc endp end start

Análisis:

        invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc         mov OldWndProc,eax

Después de que el control de ventana es creado, lo subclasificamos llamando a SetWindowLong, reemplazando la dirección principal del procedimiento de ventana con la dirección del nuestro. Observa que almacenamos la dirección del procedimiento principal para usarlo luego con CallWindowProc. Nota también que EditWndProc es un procedimiento de ventana común 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 pasándolo con el mensaje al procedimiento de ventana original. Si el carácter está en minúscula, lo convertimos en mayúscula agregándole 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 edición.

    .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 subclasificación 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 subclasificación de ventanas para tomar el control sobre otras ventanas. Es una de las técnicas más poderosas que deberías tener en tu arsenal.

Tutorial 21: Tubería

Page 132: curso lenguaje ensamblador

En este tutorial, exploraremos la tubería [pipe], qué es y qué podemos hacer por él. Para hacerlo más interesante, me meto en la técnica de cómo cambiar el color del fondo y del texto de una ventana de edición.

Bajar el ejempo aquí.

Teoría:

Una tubería [pipe] es un conducto o vía de comunicación entre dos terminales. Puedes usar una tubería para intercambiar datos entre dos procesos diferentes, o dentro del mismo proceso. Es como un walkie-talkie. Das a la otra parte una configuración y esta parte puede usarla para comunicarse contigo.

Hay dos tipos de tuberías: anónima y con nombre. Una tubería anónima es, como lo dice el nombre, anónima: es decir, puedes usarla sin saber su nombre. Una tubería nombrada es lo opuesto: tienes que conocer su nombre antes de usarla.

También puedes clasificar las tuberías de acuerdo a su propiedad: un-sentido (one-way) o dos-sentidos (two way). En una tubería de un sentido, los datos pueden fluir sólo en una dirección: de un terminal a otro. Mientras que en una tubería de dos sentidos, los datos pueden ser intercambiados entre dos terminales.

Una tubería anónima siempre es de un sentido mientras que una tubería nombrada puede ser de un sentido o de dos sentidos. Usualmente se usa una tubería nombrada en un entorno de red donde un servidor puede conectarse a varios clientes.

En este tutorial, examinaremos con cierto detalle las tuberías anónimas. El propósito principal de una tubería anónima es ser usada como un canal de comunicación entre un proceso padre y un proceso hijo o entre procesos hijos.

La tubería anónima es realmente útil cuando tratamos con una aplicación de cónsola. Una aplicación de cónsola es un tipo de programa win32 que usa una cónsola para su entrada y su salida. Una consola es como una caja DOS. Sin embargo, una aplicación de cónsola es un programa totalmente en 32-bit. Puede usar cualquier función GUI, como otros porgramas GUI. Lo que ocurre es que tiene una cónsola para su uso.

Una aplicación de cónsola tiene tres manejadores [handles] que pueden usarse para entrada y salida. Los llamamos manejadores estándard. Hay tres de ellos: entrada estándard, salida estándar y error estándard. El manejador de entrada estándard es usado para leer/recobrar la información de la cónsola y el manejador de salida eestándar es usado para información salida/impresión para la cónsola. El manejador de error estándar es usado para reportar condiciones de error ya que su salida no puede ser redireccionada.

Una aplicación de cónsola puede recuperar estos tres manejadores llamando a la función GetStdHandle, especificando el manejador que quiere obtener. Una aplicación GUI no tiene una cónsola. Si llamas a GetStdHandle, regresará un error. Si en realidad quieres usar una consola, puedes llamar a AllocConsole para localizar una nueva cónsola. Sin embargo, no olvides llamar a FreeConsole cuando hayas hecho lo que tienes que hacer con la cónsola.

Con más frecuencia se emplea una tubería anónima para redireccionar entrada y/o salida de una aplicación de cónsola hija. Para que esto trabaje el proceso padre puede ser una aplicación de cónsola o una GUI, pero el proceso hijo debe ser una aplicación de cónsola. Como debes saber, una aplicación de cónsola usa manejadores estándard para su entrada y salida. Si queremos redireccionar entrada y/o salida de una aplicación de cónsola, podemos reemplazar su manejador con el manejador de un terminal de una tubería. Una aplicación de cónsola no sabe que está usando el manejador de un terminal de una tubería. Lo usará como un manejador estándar. Esto es un tipo de polimorfismo, como se diría en la jerga POO [OOP:

Page 133: curso lenguaje ensamblador

Object Oriented Programming = Programación Orientada a Objetos]. Esta aproximación al problema es poderosa ya que no necesitamos modificarde ninguna manera el proceso hijo .

Otra cosa que deberías saber sobre las aplicaciones de cónsola es de donde obtiene los manejadores estándar. Cuando se crea una aplicación de cónsola, el proceso padre tiene dos posiblidades: puede crear una nueva cónsola para la hija o puede dejar que la hija herede su propia cónsola. Para que trabaje la segunda opción, el proceso padre debe ser una aplicación de cónsola, pero si es una aplicación GUI, debe llamar primero a AllocConsole para localizar una cónsola.

Comencemos a trabajar. Con el fin de crear una tubería anónima 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 tubería pWriteHandle es un puntero a una variable dword que recibirá el manejador al terminal de escritura de la tubería.

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 tamaño sugerido del buffer que la pipa reservará para su uso. Es sólo un tamaño sugerido. Puedes usar NULL para decirle a la función que use el tamaño por defecto.

Si la llamada tiene éxito, el valor regresado es distinto de cero. Si falla, el valor regresado es cero.

Después de que la llamada tiene éxito, obtendrás dos manejadores, uno para el terminal de lectura de la tubería y otro para el terminal de escritura.

Ahora resumiremos los pasos para redireccionar la salida estándard de un programa de cónsola hijo hacia nuestro propio proceso. Nota que mi método difiere del de la referencia de la api de win32 suministrada por Borland. El método en la referencia de la api win32 asume que el proceso padre es una aplicación de cónsola y por eso el proceso hijo puede heredar los manejadores estándar de él. Pero en muchas ocasiones, necesitaremos redireccionar la salida desde una aplicación de cónsola a una GUI.

1. Crear una tubería anónima con CreatePipe. No olvides establecer el miembro bInheritable de SECURITY_ATTRIBUTES a TRUE para que el manejador sea heredable.

2. Ahora debemos preparar los parámetros que pasaremos a CreateProcess ya que los usaremos para cargar la aplicación de cónsola 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 propósito. Puedes esconder la ventana principal y pasar el manejador de la tubería al proceso de cónsola hijo con ella. Abajo están los miembros que debes llenar:

o cb : tamaño de la estructura STARTUPINFO o dwFlags : las banderas binarias que determinan cuales de los miembros de la

estructura son validos también gobiernan el estado mostrado/oculto de la ventana principal. Para nuestro propósito, debería usar una combinación de STARTF_USESHOWWINDOW y STARTF_USESTDHANDLES

Page 134: curso lenguaje ensamblador

o hStdOutput and hStdError : los manejadores que quieres que el proceso hijo use como manejadores salida/error estándar. Para nuestro propósito, pasaremos el manejador de escritura de la tubería como manejador de salida y error estándar del proceso hijo. De manera que cuando las salidas hijas algo a la salida/error estándar, realmente pase la información a través de la tubería al proceso padre.

o wShowWindow gobierna el estado mostrado/oculto de la ventana principal. Para nuestro propósito, no queremos que la ventana de la cónsola del proceso hijo se muestre, así que ponemos SW_HIDE en este miembro.

3. Llamar a CreateProcess para cargar la aplicación hija. Después de que CreateProcess ha tenido éxito, la hija todavía está durmiendo. Es cargada en la memoria pero no corre de inmediato. Cerrar el manejador de escritura de la tubería. Esto es necesario. Porque el proceso padre no tiene uso para el manejador de escritura de la tubería, y la pipa no trabajará si hay más de un terminal de escritura, DEBEMOS cerrarlos antes de leer los datos de la tubería. Sin embargo, no cierres el manejador de escritura antes de llamar a CreateProcess, porque sino tu pipa se romperá. Deberías cerrarla justo después del regreso de CreateProcess y antes de leer los datos del terminal de lectura de la tubería.

4. 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 ejecución [running mode]. Iniciará la ejecución y cuando escriba algo al manejador de salida estándar (el cual es realmente el manejador al termianl de escritura de la tubería), los datos serán enviados a través de la tubería al terminal de lectura. Puedes pensar en ReadFile como una extracción de datos del terminal de lectura de la tubería. Debes llamar a ReadFile repetidas veces hasta que retorne 0 lo cual significa que ya no hay más datos que leer. Puedes hacer algo con los datos que lees de la tubería. En nuestro ejemplo, los pongo dentro del control de edición.

5. 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         ; el ID del menú principal IDM_ASSEMBLE equ 40001

.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

Page 135: curso lenguaje ensamblador

.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

Page 136: curso lenguaje ensamblador

    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

Page 137: curso lenguaje ensamblador

                                .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

Análisis:

El ejemplo llamará a ml.exe para ensamblar un archivo llamado test.asm y redireccionar la salida de ml.exe al control de edición 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 creación de la ventana principal es crear un control de edición 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 edición. Cuando un control de edición va a pintar su área cliente, envía el mensaje WM_CTLCOLOREDIT a su padre.

wParam contiene el manejador del dispositivo de contexto que usará el control de edición para escribir su propia área cliente. Podemos aprovechar esto para cambiar las características 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 edición. 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 tubería anónima.

            .if ax==IDM_ASSEMBLE                 mov sat.niLength,sizeof SECURITY_ATTRIBUTES

Page 138: curso lenguaje ensamblador

                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 tubería sean heredados al proceso hijo.

               invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL

 Después 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 código trabaja con win9x y NT. Después que regrese la llamada a GetStartupInfo, puedes modificar los miembros que son importantes. Copiamos el manejador del terminal de escritura de la tubería dentro de hStdOutput y hStdError ya que queremos que el proceso hijo lo use en vez de los manejadores estándar de salida/error. También queremos esconder la ventana de la cónsola 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 válidos 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 parámetro bInheritHandles debe ser establecido a TRUE para que trabaje el manejador de la tubería.

                       invoke CloseHandle,hWrite

Después de que creamos satisfactoriamente el proceso hijo, debemos cerrar el terminal de escritura de la tubería. Recuerda que pasamos el manejador de escritura al proceso hijo a través de la estructura STARTUPINFO. Si no cerramos el manejador de escritura de nuestro terminal, habrán dos terminales de escritura. Y la tubería no trabajará. Debemos cerrar el manejador de escritura después 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

Page 139: curso lenguaje ensamblador

                            .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 estándar del proceso hijo. Nos mantenemos en un bucle infinito hasta que no hayan más datos que leer desde el terminal de lectura de la tubería. Llamamos a RtlZeroMemory para llenar el buffer con ceros y luego llmamos a ReadFile, pasando el manejador de lectura de la tubería en lugar de un manejador de archivo. Nota que sólo leemos un máximo de 1023 bytes ya que necesitamos que los datos sean una cadena ASCIIZ que podemos pasar al control de edición

Cuando regresa ReadFile con los datos en el buffer, llenamos los datos dentro del control de edición. Sin embargo, aquí hay un pequeño problema. Si usamos SetWindowText para poner los datos dentro del control de edición, los nuevos datos sobreescribirán 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 edición 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: SuperclasificaciónEn este tutorial aprenderemos sobre la superclasificación, qué es y para qué sirve. También aprenderás cómo suministrar navegación con la tecla Tab a los controloes de tu propia ventana. Bajar el ejemplo aquí

Teoría:

En tu carrera de programación seguramente encontrarás alguna situación donde necesitarás controles con un comportamiento *levemente* distinto. Por ejemplo, puedes necesitar diez controles de edición que acepten sólo números hexadecimales. Hay varias maneras de alcanzar este objetivo:

Crear tu propia clase e instanciar los cotrolesCrear esos controles de edición y luego subclasificarlos

Superclasificar el control de edición

El primer método es demasiado tedioso. Tienes que implementar todas las funcionalidades del control tú mismo. Es una tarea difícil de hacer con agilidad. El segundo método es mejor que el primero, pero todavía requiere mucho trabajo. Es bueno sólo si subclasificas unos cuantos controles pero ya casi es una pesadilla subclasificar más de doce ventanas. La superclasificación es la técnica que deberías usar para esta ocasión.

La subclasificación es la técnica 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 propósitos y luego crear un montón de controles.

Aquí están esbozados los pasos de la subclasificación:

Page 140: curso lenguaje ensamblador

llamar a GetClassInfoEx para obtener la información de la ventana que quieres subclasificar. GetClassInfoEx requiere un puntero a la estructura WNDCLASSEX que será llenada con la información 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. Sólo recuerda salvar el miembro original lpfnWndProc si quieres llamarla con CallWindowProc.

Registtrar la estructura WNDCLASSEX modificada. Tendrás una nueva clase de ventana que tendrá algunas características de la antigua clase de ventana.

Crear la nueva ventana a partir de la nueva clase.

La superclasificación es mejor que subclasificar s quieres crear mechos controles con las mismas características.

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

Page 141: curso lenguaje ensamblador

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_MAXIMIZEBOX+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

Page 142: curso lenguaje ensamblador

        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

Page 143: curso lenguaje ensamblador

                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  

Análisis:

El programa creará una ventana con seis controles de edición "modificados" en su área cliente. Los controles de edición aceptarán sólo dígitos hexadecimales. Realmente, el ejemplo de subclasificación para hacer supreclasificación. 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. Después de que regresa GetClassInfoEx, wc es llenada con toda la información 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 técnica es idéntica a la de subclasificación, excepto porque modificas la estructura WNDCLASSEX directamente sin tener que llamar a SetWindowLong. Hay dos miembros que deberán ser cambiados, sino no padrás registrar la nueva clase de ventana:, hInstance and lpsClassName.

Page 144: curso lenguaje ensamblador

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. Obtendrás una nueva clase de ventana con algunas características 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 número 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 edición que sólo aceptan dígitos hexadecimales. Los procedimientos de ventanas reemplazados manejan el filtro. Realmente, es idéntico al procedimiento de ventana en la subclasificación. Como puedes ver, ya no tienes que hacer el trabajo extra de la subclasificación.

Me he metido en un recorte de código para manejar controles de navegación con tab y hacer más rico este ejemplo. Normalmente, si quieres poner controles en una caja de diálogo, el propietario de la caja de diálogo maneja las teclas de navegación para tí, de manera que puedas usar la tecla tab para ir al próximo 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 navegación" 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

Page 145: curso lenguaje ensamblador

                .if eax==NULL                     invoke GetWindow,hEdit,GW_HWNDLAST                 .endif             .endif             invoke SetFocus,eax             xor eax,eax             ret

El recorte de código 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 también está presionada. GetKeyState regresa un valor en eax que determina si la tecla específica 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 sólo la tecla Tab, llamamos a GetWindow para recuperar el manejador del próximo control. Usamos la bendera GW_HWNDNEXT para decir a GetWindow que obtenga el manejador a la ventana próxima a la línea del actual hEdit. Si esta función regeras NULL, lo interpretamos como si no más manejadorespara obtenert así que el actual hEdit es el último control en la línea. 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 cómo poner iconos en la banadeja de sistema y cómo crear/usar un menú emergente. Bajate el ejemplo aquí.

Teoría:

La bandeja del sistema es la región rectangular en la barra de tareas donde residen la mayoría de los iconos. Normalmente, verás como mínimo un reloj digital aquí. También puedes poner iconos en la barra de sistema. Debajo están 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: o cbSize   El tamaño de esta estructura. o hwnd      Manejador (Handle) de la ventana que recibirá la notificacion cuando

ocurran eventos de ratón 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, podrás chequear aquí desde cuál icono de bandeja proviene la notificación del ratón.

o uFlags    Especifica qué miembros son válidos NIF_ICON El miembro hIcon es válido. NIF_MESSAGE El miembro uCallbackMessage es válido. NIF_TIP El miembro szTip es válido.

o uCallbackMessage  El mensaje común que Windows mandará a la ventana especificado por el miembro hwnd cuando ocurren eventos sobre el icono de bandeja. Creas este mensaje tú mismo.

Page 146: curso lenguaje ensamblador

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 ratón esté sobre el icno de bandeja. 2. Llama a Shell_NotifyIcon que está definida en shell32.inc. Esta función tiene el

siguiente prototipo: 3.

 

            Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

    dwMessage  Es el tipo de mensaje a enviar al shell de Windows.            NIM_ADD Añade 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 añadir 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 mayoría de las veces no estás contento con sólo poner un icono aquí. Necesitas ser capaz de responder a eventos de ratón 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 ratón. Por ejemplo, si el usuario pulsa el botón derecho en el icono, lParam contendra WM_RBUTTONDOWN.

La mayoría de los iconos de bandeja, como siempre, muestran un menú emergente cuando el usuario pulsa el botón derecho sobre éste. Podemos implementar esta función creando un menú emergente y entonces llamando a TrackPopupMenu para mostrarlo. Los pasos están descritos abajo:

1. Crear un menu emergente llamando a CreatePopupMenu. Esta función crea un menu vacío. Esto devuelve un manejador (handle) al menú en eax si el resultado es satisfactorio.

2. Añade elementos de menú con AppendMenu, InsertMenu o InsertMenuItem. 3. Cuando quieres mostrar el menú emergente donde está el cursor del ratón, 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 selección de menú normal.

Nota: Precaución 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 notificación desde el menú emergente DEBERÁ ser la ventana de primer plano. Así que la llamada a SetForegroundWindow corregirá esto.

Page 147: curso lenguaje ensamblador

2. Despues de llamar a SetForegroundWindow, encontrarás 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 dueño 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!

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

Page 148: curso lenguaje ensamblador

    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_MAXIMIZEBOX+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

Page 149: curso lenguaje ensamblador

            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  

Análisis:

El programa mostrará una simple ventana. Cuando aprietes el botón minimizar, se ocultará y pondrá un icono en la bandeja del sistema. Cuando hagas una doble pulsación sobre el icono, el programa se restablecerá y borrara el icono de la bandeja de sistema. Cuando pulses el botón 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

Page 150: curso lenguaje ensamblador

Cuando se crea la ventana principal, crea un menu emergente y le añade 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 añadir el elemento uFlags le dice a Windows sobre el elemento de menú a añadir 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.

Después de que es creado el menú emergente, la ventana principal espera pacientemente a que el usuario pulse el botón 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 código 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 necesitarás 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 común (NIF_MESSAGE) y especificamos el texto tooltip (NIF_TIP). WM_SHELLNOTIFY es un mensaje común 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. Cárgalo 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 ratón está sobre el icono.

Ocultamos la ventana padre para dar la ilusión de parecer minimizar al icono de bandeja.

Page 151: curso lenguaje ensamblador

Después podemos llamar a Shell_NotifyIcon con el mensage NIM_ADD para añadir 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 ratón sobre él verás el tooltip que muestra el texto que ponemos dentro del miembro szTip. Después, si haces una pulsación 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 algún evento de ratón sobre el icono de bandeja, tu ventana recibe el mensaje WM_SHELLNOTIFY que es el mensage común 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 ratón actual. En el código de arriba, primero chequeamos si este mensaje viene del icono de bandeja que nos interesa. Si esto ocurre, chequeamos el mensaje del ratón. Como estamos interesados únicamente en la pulsación derecha o en la doble pulsación izquierda, procesamos sólo los mensages WM_RBUTTONDOWN y WM_LBUTTONDBLCLK.

Si el mensaje de ratón es WM_RBUTTONDOWN llamamos a GetCursorPos para obtener las coordenadas actuales en la pantalla del ratón. Cuando la función vuelve, la estructura POINT es rellenada con las coordenadas en pantalla del ratón. 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 resolución 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 función ScreenToClient.

Como siempre, para nuestro propósitos, queremos mostrar el menú emergente en la posición actual del ratón 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 después y qué botón del ratón 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 localización del menú en las coordenadas de pantalla.

Page 152: curso lenguaje ensamblador

nReserved deberá ser NULL hWnd es el manejador (handle) de la ventana que recibirá los mensajes desde el

menú. prcRect es el rectángulo 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 pulsación sobre el icono de la bandeja mandamos el mensaje WM_COMMAND a nuestra ventana especificando IDM_RESTORE para emular la selección 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 pulsación, 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, también borramos el icono de la barra y destruimos la ventana principal llamando a DestroyWindow.

Tutorial 24: Ganchos de WindowsEn 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í.

Teoría:

Los ganchos de Windows pueden considerarse uno de los rasgos más poderosos de Windows. Con ellos podemos atrapar eventos que ocurrirán en tus procesos o en otros remotos. A través del "enganche" [hooking], dices cosas a Windows sobre una función, una función filtro llamada también 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 atraparán eventos que ocurrirán en tus propio proceso. Ganchos Remotos atraparán eventos que ocurrirán en otro(s) proceso(s). Hay dos

tipos de ganchos remotos o específicos-a-un-hilo [thread-specific] atrapan eventos que ocurren en un hilo

específico de otro proceso. En pocas palabras, quieres observar un hilo específico en un proceso específico.

o ancho-de-sistema [system-wide]  atrapa todos los eventos de todos los hilos en

todos los procesos en el sistema.

Page 153: curso lenguaje ensamblador

Cuando instales ganchos, recuerda que ellos afectan el sistema de desempeño. Los ganchos de ancho de sistema [system-wide] son los más notorios en este aspecto. Como TODOS los eventos relacionados serán dirigidos a través de tu función filtro, tu sistema puede ralentizarse un poco. Así que si quieres usar un gancho de ancho de sistema, deberías usarlo juiciosamente y desactivarlo tan pronto ya no lo necesites. También tienes una probabilidad más alta de quebrar [crashing] los otros procesos, ya que puedes mediar [meddle] con otros procesos y si algo va mal en tu función 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 información 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 función filtro en tu proceso es llamada de una manera directa. Pero si es un gancho remoto, el sistema debe inyectar el código para el procedimiento del gancho dentro del espacio de direcciones del(os) otro(s) proceso(s). Y el sistema puede hacer eso sólo si la función 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 grabación diaria [journal record] y ganchos de ejecución diaria [journal playback]. El procedimiento de gancho para estos dos ganchos debe residir en el hilo que instala los ganchos. La razón por la que debe ser así, es porque ambos ganchos tienen que ver con la intercepción 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 código 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 solución 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

ratón desde la cola de mensajes WH_HARDWARE llamado cuando GetMessage o PeekMessage regresa algún

mensaje hardware que no está relacionado con el teclado ni con el ratón. WH_MSGFILTER  llamado cuando una caja de diálogo, el menú o la barra de

pergamino [scrollbar] está apunto de [is about] procesar un mensaje. Este gancho es local. Se usa específicamente 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 botón.

WH_CBT  usado específicamente 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

Page 154: curso lenguaje ensamblador

Ahora que sabemos algo de teoría, podemos ver ahora cómo 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 dirección del procedimiento de gancho que será llamada para procesar los mensajes para el gancho específico. 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 espíe. Este parámetro es el que determina si el gancho es local o remoto. Si este parámetro 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 estén en una DLL. Y WH_SYSMSGFILTER siempre es un gancho remoto de ancho de sistema [system-wide]. Realmente es idéntica 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 después.

Puedes desinstalar un gancho llamando a UnhookWindowsHookEx que sólo acepta un parámetro, 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 cómo 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 ratón, 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 nCode especifica el código del gancho. o wParam y lParam contienen información adicional sobre el evento

HookProc es realmente un comodín [placeholder] para el nombre de la función. Puedes llamarla de la forma que quieras siempre que conserves el prototipo de arriba. La interpretación 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 sólo 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

Page 155: curso lenguaje ensamblador

return value: no se usa, regresa cero

WH_MOUSE

nCode puede ser HC_ACTION o HC_NOREMOVE wParam contiene el mensaje del ratón lParam apunta a una estructura MOUSEHOOKSTRUCT return value: cero si el mensaje debería ser procesado. 1 si el mensaje deberúia ser

descartado.

La línea de abajo es: debes consultar tu referencia de la api de win32 para detalles sobre los significados de los parámetros 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 más recientemente colocado en la cabeza de la lista. Cuando ocurre un evento, Windows llamará sólo 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 deberías saber qué estás haciendo. Muchas veces, es una buena práctica llamar al siguiente procedimiento de manera que otros ganchos puedan tener una impresión [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 función usa este manejador para atravesar la lista enlazada y buscar el siguiente procedimiento de gancho que debería 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) sección(es) de datos(s) dentro de otros procesos. En pocas palabras, todos los procesos comparten una copia sencilla del código, ¡pero ellos tendrán su propia copia privada de la sección 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 sección 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 situación normal, esta conducta es deseable ya que provee la ilusión de que cada proceso tiene su propia copia de la DLL. Pero no cuando hay involucrados ganchos de Windows. Queremos que la DLL sea idéntica en todos los procesos, incluyendo los datos. La solución: debes marcar la sección de datos como compartida. Puedes hacer esto especificando el atributo de la(s) sección(es) en el conmutador [switch] del enlazador [linker]. Para MASM, necesitas usar este conmutador:

/SECTION:<section name>, S

El nombre de la sección 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 sección de datos no inicialoizados sea compartida entre procesos, debes usar la siguiente línea:

link /section:.bss,S  /DLL  /SUBSYSTEM:WINDOWS ..........

El atributo S marca la sección como compartida.

Page 156: curso lenguaje ensamblador

Ejemplo:

Hay dos módulos: uno es el programa principal que hará la parte de GUI y otra es la DLL que instalará/desinstalará el gancho.

;--------------------------------------------- Este es el código 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                   equ 101 IDC_CLASSNAME              equ 1000 IDC_HANDLE                     equ 1001 IDC_WNDPROC                 equ 1002 IDC_HOOK                         equ 1004 IDC_EXIT                           equ 1005 WM_MOUSEHOOK             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

Page 157: curso lenguaje ensamblador

    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

Page 158: curso lenguaje ensamblador

                        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 código 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

Page 159: curso lenguaje ensamblador

    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  

Análisis:

El ejemplo desplegará una caja de diálogo [dialog box] con tres controles de edición que serán llenados con el nombre de la clase, el manejador [handle] de ventana y la dirección del procedimiento de ventana asociada con la ventana bajo el cursor del ratón. Hay dos botones, Hook (gancho) y Exit (Salir). Cuando presionas el botón Hook, el programa engancha la entrada del ratón y el texto en el botón cambia a Unhook (desenganchar). Cuando mueves el cursor del ratón sobre la ventana, la info acerca de esa ventana será desplegada en la ventana principal del ejemplo. Cuando presionas el botón Unhook, el programa remueve el ganchjo del ratón.

El programa principal usa una caja de diálogo [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 ratón. Por supuesto, este es un plan arbitrario. Yo decido enviar un manejador en wParam por razones de simplicidad. Tú puedes escoger tu propio método de comunicación entre la ventana principal y la DLL de gancho.

Page 160: curso lenguaje ensamblador

                    .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 botón Hook, el programa chequea si el gancho ya está instalado. Si no lo está, se llama a la función InstallHook en la DLL de gancho para instalarlo. Nota que pasamos el manejador de la ventana principal como parámetro de la función 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 tabmbién es cargada. Realmente, las DLLs son cargadas inmediatamente después de que el programa está en memoria. El punto de entrada de la DLL es llamado incluso antes de que se ejecute la primera instrucción del programa principal. Así que cuando el programa principal ejecuta la(s) DLL(s) es/son inicializada(s). Ponemos el siguiente código en el punto de entrada de la DLL de gancho:

    .if reason==DLL_PROCESS_ATTACH         push hInst         pop hInstance     .endif

El código salva el manejador de instancia de la DLL de gancho misma a una variable global llamada hInstance para usar dentro de la función InstallHook. Ya que la función del punto de entrada de la DLL es llamada antes de que sean llamadas otras funciones de la DLL, hInstance siempre es válido. Ponemos hInstance en la sección .data, así que este valor es guardado en la base de la sección por proceso [is kept on per-process basis]. Debido a que cuando el cursor del ratón 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 debería ser re-proyectada a otra dirección. El valor de hInstance será actualizado para las de las nuevas direcciones cargadas. Cuando el usuario presiona el botón Unhook y luego el botón 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á erróneo porque en este proceso ejemplo la dirección de carga de la DLL de gancho no ha sido cambiada. El gancho será local donde puedes enganchar sólo los eventos de ratón que ocurren en tu propia ventana. Difícilmente 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 función InstallHook es muy simple. Salva el manejador de ventana pasado como su parámetro a una variable global llamada hWnd para ser usada luego. Luego llama a SetWindowsHookEx para instalar un gancho de ratón. El valor de retorno de SetWindowsHookEx es almacenado en una variable global llamada hHook para usar con UnhookWindowsHookEx.

Page 161: curso lenguaje ensamblador

Después de que es llamada SetWindowsHookEx, el gancho del ratón es funcional. Cada vez que ocurre un evento de ratón 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 ratón. Después de eso, llma a la función 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 ratón. Después de que enviamos el manejador de ventana a la ventana principal a través de PostMessage con el mensaje WM_MOUSEHOOK. Algo que deberías recordar es que: no deberías usar SendMessage dentro del procedimiento de gancho, ya que puede causar estancamiento de mensajes. Es más 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 dónde está el cursor del ratón hwnd es el manejador de la ventana que recibirá el mensaje del ratón. Usualmente es

la ventana debajo del cursor del ratón pero no siempre. Si una ventana llama a SetCapture, la entrada del ratón será desviada ahora más bien a la ventana. Debido a esta razón, no uso el miembro hwnd de esta estructura sino que más bien elijo llamara a WindowFromPoint.

wHitTestCode especifica la prueba del valor hit-test. Este valor da más información sobre la posición actual del cursor del ratón. Especifica en qué parte de la ventana está el cursor del ratón. Para una lista completa, chequea en tu referencia de la api de win32 el tópico sobre el mensaje WM_NCHITTEST.

dwExtraInfo contiene información 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 información 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

Page 162: curso lenguaje ensamblador

        .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á todavía en los controles de edición y el texto que se pondrá dentro de ellos para comprobar si on idénticos. Si lo son, los saltamos. Regresamos el nombre de la clase llamando a GetClassName, la dirección 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 edición 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 botón Unhook, el programa llama a la función UninstallHook en la DLL de gancho. UninstallHook llama a UnhookWindowsHookEx. Después de eso, cambia el texto del botón una vez más a "Hook", HookFlag a FALSE y se limpia el contenido de los controles de edición. Nota que el conmutador [switch] del enlazador en el makefile.

        Link /SECTION:.bss,S  /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

Especifica a la sección .bss como una sección compartida para hacer que todos los procesos compartan la sección de datos no inicializados de la DLL de gancho. Sin este conmutador [switch], tu DLL de gancho no funcionará correctamente.

Tutorial 25: Simple BitmapEn 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.

Teoría

Page 163: curso lenguaje ensamblador

Los bitmaps pueden ser vistos como pinturas almacenadas en la computadora. Hay muchos formatos de pinturas usados con los computadores pero Windows sólo soporta como nativos los archivos Gráficos de Bitmap de Windows (.bmp). Los bitmaps a que me referiré en este tutorial son archivos gráficos de Windows. La manera más fácil de usar un bitmap es emplearlo como recurso. Hay dos maneras de hacer eso. Puedes incluir el bitmap en el archivo de definición del recurso (.rc) de la siguiente manera:  

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

Este método usa una constante para representar el bitmap. La primera línea crea una constante llamada IDB_MYBITMAP que tiene el valor de 100. Usaremos esta etiqueta para referirnos al bitmap en el programa. La siguiente línea declara un recurso bitmap. Dice al compilador de recursos dónde encontrar el actual archivo bmp.

El otro método usa un nombre para representar el bitmap de la siguiente manera:

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

Este método requiere que te refieras al bitmap en tu programa usando la cadena "MyBitMap" en vez de un valor.

Cualquiera de estos métodos trabaja bien siempre que sepas cuál estás 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 definición:

LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR

Esta función 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 método 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 sería 100). Un pequeño ejemplo, en orden:

  Primer Método:

.386

.model flat, stdcall

................

.const IDB_MYBITMAP    equ 100 ............... .data? hInstance  dd ? ..............

Page 164: curso lenguaje ensamblador

.code

.............     invoke GetModuleHandle,NULL     mov hInstance,eax ............     invoke LoadBitmap,hInstance,IDB_MYBITMAP ...........

Segundo Método:

.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 llamando a BeginPaint en respuesta al mensaje WM_PAINT o llamando a GetDC en algún lado.

2. 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 operación, copiamos el contenido de la hidden drawing surface al contexto de dispositivo actual en una llamada a función. Es un ejemplo de técnica de doble-buffer usada para desplegar con rapidez imágenes sobre la pantalla. Puedes crear esta superficie oculta para dibujo [hidden drawing surface] llamando a CreateCompatibleDC.

CreateCompatibleDC  proto  hdc:HDC

Si esta función 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 parámetro y el manejador al bitmap como segundo parámetro. SelectObject tiene la siguiente definición:

SelectObject   proto  hdc:HDC, hGdiObject:DWORD

Page 165: curso lenguaje ensamblador

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 operación tales como BitBlt y StretchBlt. BitBlt copia el contenido de un DC a otro de manera que es más rápido mientras que StretchBlt pueda estrechra o comprimir el bitmap para fijar el área de salida. Usaremos aquí BitBlt por simplicidad. BitBlt tiene la siguiente definición:

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 operación 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 operación de transferencia del bitmapnxSrc, nySrc son las coordenadas de la esquina superior izquierda del rectángulo de origen. dwROP es el código de la operación-raster (de ahí las siglas ROP) que gobierna cómo 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, sólo quieres sobre-escribir los datos de colores existentes con los nuevos.

1. Cuando ya hayas hecho lo que ibas a hacer con el bitmap, suprímelo con una llamada a la API DeleteObject.

¡Eso es todo! Para recapitular, necesitas poner el bitmap dentro del guión de recursos. Luego cárgalo desde el recurso con LoadBitmap. Obtendrás 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.

Código 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

Page 166: curso lenguaje ensamblador

.data ClassName db "SimpleWin32ASMBitmapClass",0 AppName  db "Win32ASM Simple Bitmap Example",0

.data? hInstance HINSTANCE ? CommandLine LPSTR ? hBitmap dd ?

.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  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

Page 167: curso lenguaje ensamblador

  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 guión de recursos ;--------------------------------------------------------------------- #define IDB_MAIN 1 IDB_MAIN BITMAP "tweety78.bmp"

Análisis:

No hay mucho que analizar en este tutorial ;)  

#define IDB_MAIN 1 IDB_MAIN BITMAP "tweety78.bmp"

Page 168: curso lenguaje ensamblador

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 guión 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 parámetro a la API. Obtenemos el manejador al bitmap cuando regresa la función.

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 dimensión 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 sección 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 ScreenAhora que sabemos cómo usar un bitmap, podemos progresar hacia un uso más creativo de él. Splash screen. Baja el ejemplo.

Teoría

Una splash screen [pantalla de salpicadura] es una ventana que no tiene barra de título, ni caja de menú de sistema, ni borde, que despliega un bitmap por un lapso de tiempo y luego desaparece automáticamente. Usualmente es usada durante el inicio de un programa, para

Page 169: curso lenguaje ensamblador

desplegar el logo del programa o distraer la atención del usuario mientras el programa hace alguna inicialización 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,  verás que hay un consumo precioso de memoria cuando se carga un bitmap que será usado sólo una vez y se mantiene en la memoria hasta que el programa es cerrado. Una mejor solución es crear una DLL de *recursos* que contenga el bitmap y que tenga el único propósito 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 módulos: 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 función del punto de entrada de la DLL. Se creará un temporizador y se

establecerá cuánto 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.

4. 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

5. 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á.

Examinaremos los mecanismos en detalle.

Cargar/Descargar una DLL

Puedes cargar dinámicamente una DLL con la función LoadLibrary que tiene la siguiente sintaxis:

LoadLibrary  proto lpDLLName:DWORD

Toma sólo un parámetro: la dirección del nombre de la DLL que quieres cargar en memoria. Ssi la llamada es satisfactoria, regresa el manejador del módulo de la DLL sino regresa NULL.

Para descargar una DLL, llama a FreeLibrary:

FreeLibrary  proto  hLib:DWORD

Toma un parámetro: el manejador del módulo de la DLL que quieras descargar. Normalmente, obtienes el manejador a partir de LoadLibrary

Cómo 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 notificación del temporizador. Este parámetro 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.

Page 170: curso lenguaje ensamblador

uElapse es el valor del lapso de tiempo en milisegundos. lpTimerFunc Es la dirección de una función que procesará los mensajes de notificación del temporizador. Si pasas NULL, los mensajes del temporizador serán enviados a la ventana especificada por el parámetro 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 notificación del temporizador para ir a esa ventana, debes pasar todos los cuatro parámetros 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 función en lugar de un manejador de ventana. Debes especificar el valor de la dirección del temporizador que procesará los mensajes del temporizador.

Usaremos la primera aproximación en este ejemplo.

Cuando se cumple el período de tiempo, se envía 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 ?

Page 171: curso lenguaje ensamblador

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

Page 172: curso lenguaje ensamblador

 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

Page 173: curso lenguaje ensamblador

        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

Page 174: curso lenguaje ensamblador

                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

Page 175: curso lenguaje ensamblador

        .endif         xor eax,eax         ret WndProc endp

End DllEntry

Análisis:

Primero examinaremos el código 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 después de eso, descargarla de la memoria con FreeLibrary. LoadLibrary no regresará hasta que la DLL haya terminado con su inicialización.

Eso es todo lo que hace le programa principal. La parte interesante está en la DLL.

   .if reason==DLL_PROCESS_ATTACH  ; Cuando la dll es cargada       push hInst       pop hInstance       call ShowBitMap

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 función 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 sólo WS_POPUP lo cual hará que la ventana no tenga bordes ni tampoco encabezamiento [caption]. También 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 código.

                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

Page 176: curso lenguaje ensamblador

                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 tamaño 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 dimensión deseada. En este caso, queremos que el bitmap se fije dentro de la ventana así que usamos StretchBlt en vez de BitBlt. Después de eso, borramos el DC de memoria.

Page 177: curso lenguaje ensamblador

        .elseif uMsg==WM_LBUTTONDOWN                 invoke DestroyWindow,hWnd

Sería frustrante para el usuario tener que esperar hasta que la splash screen desaparezca. Podemos suministrarle al usuario un elección. Cuando haga click sobre la splash screen, desaparecerá. Por eso es que necesitamos procesar el mensaje WM_LBUTTONDOWN en la DLL. Durante la recepción 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 duplicación de código. 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 cómo crearlo y usarlo. Baja el ejemplo.

Teoría:

Un 'tooltip' [una sugerencia] es una pequeña ventana rectangular que se despliega cuando el ratón pasa sobre algún área específica. Una ventana 'tooltip' contiene algún 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 ratón o hace click lejos del área diseñada. Probablemente estarás 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 cómo podemos crearlo y usarlo. Los pasos están esbozados aquí:

1. Crear un control 'tooltip' con CreateWindowEx 2. Definir una región que el control 'tooltip' monitoreará para el

movimiento del puntero del ratón. 3. Subsumir la región del control 'tooltip' 4. Transmitir los mensajes del ratón del área subsumida al control

'tooltip' (este paso puede ocurrir antes, dependiendo del método usado para to transmitir los mensajes)

Page 178: curso lenguaje ensamblador

Ahora examinaremos cada paso en detalle.

Creación del Tooltip

El control 'tooltip' es un control común. Por eso, necesitas llamar InitCommonControls en algún lugar de tu código fuente de manera que MASM implícitamente enlace tu programa a comctl32.dll. Creas un control 'tooltip' con CreateWindowEx. El escenario típico sería más 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 ratón 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 ratón pase sobre el área que registraste para el control 'tooltip', la ventana 'tooltip' aparecerá incluso si la ventana debajo del puntero del ratón 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 automáticamente. Tampoco necesitas especificar, la altura y el ancho de la ventana del control 'tooltip': el control 'tooltip' los ajustará automáticamente para fijar el texto del 'tooltip' que será desplegado, así que suministramos CW_USEDEFAULT en todos los cuatro parámetros. Los restantes parámetros 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 ratón pase sobre algún á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 ratón. Si el puntero del ratón pasa sobre la herramienta, aparecerá la ventana 'tooltip'. El área rectangular puede cubrir todo el área cliente o sólo parte de ella. Así que podemos dividir la herramienta en dos tipos: una implementada como una ventana y otra implementada como un área

Page 179: curso lenguaje ensamblador

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 edición, 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 definición:

TOOLINFO STRUCT   cbSize             DWORD      ?   uFlags             DWORD      ?   hWnd               DWORD      ?   uId                DWORD      ?   rect               RECT      <>   hInst              DWORD      ?   lpszText           DWORD      ?   lParam             LPARAM     ? TOOLINFO ENDS

Nombre del Campo

Explicación

cbSize

El tamaño de la estructura TOOLINFO. DEBES llenar este miembro. Windows no monitoreará el error si este campo no es llenado debidamente y recibirás extraños e impredecibles resultados.

uFlags Los bits de bandera que especifican . Este valor puede ser una combinación de las siguientes banderas:

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 dimensión del rectángulo.

TTF_CENTERTIP  Normalmente la ventana 'tooltip' a la derecha y abajo del puntero del ratón. Si especificas esta bandera, la ventana 'tooltip' aparecerá

Page 180: curso lenguaje ensamblador

directamente debajo de la herramienta y será centrada independientemente de la posición del puntero del ratón.

TTF_RTLREADING  Pudes olvidar esta bendera si tu programa no está diseñado específicamente para sistemas arábicos 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 cuál está la herramienta de manera que el control 'tooltip' pueda interceptar mensajes de ratón que son enviados a la ventana. Esta manera es muy conveniente. Si no usas esta bandera, tienes que trabajar más para transmitir los mensajes del ratón 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] dinámicamente, deberías especificar el valor LPSTR_TEXTCALLBACK en el miembro lpszText. El control 'tooltip' enviará el mensaje de notificación TTN_NEEDTEXT a la ventana identificada por el manejador en el campo hWnd.

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

ID de la herramienta definido para la aplicación si la bandera TTF_IDISHWND no está especificada. Puesto que esto significa que usas una herramienta que cubre sólo una parte del área cliente, es lógico 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 aplicación

Page 181: curso lenguaje ensamblador

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 diseñar 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 dimensión de la herramienta. Esta estructura define un rectángulo 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 sólo 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)

hInst

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 explicación del miembro lpszText y entenderás para qué es usado este campo. El control 'tooltip' ignorará este campo si el campo lpszText no contiene un identificador del recursos de cadena.

lpszText Este campo puede tener diversos valores: Si especificas el valor LPSTR_TEXTCALLBACK en este

campo, el control tooltip enviará el mensaje de notificación 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 método más dinámico de actualización 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

Page 182: curso lenguaje ensamblador

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 método es útil si planificas portear [to port] tu programa a otros lenguajes. Puesto que el recurso de cadena es definido en el guión de recursos, no necesitas modificar el código fuente. Sólo tienes que modificar las tablas de cadenas y los textos de sugerencia cambiarán 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 método es el más fácil de usar pero el menos flexible.

Para recapitular, necesitas llenar la estructura TOOLINFO antes de subsumirla al control 'tooltip'. Esta estructura describe las características de la herramienta que deseas.

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

Después 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 más de un control 'tooltip' para una ventana. Para registrar una herramienta con un control 'tooltip', envías el mensaje TTM_ADDTOOL al control 'tooltip'. El valor de wParam no se usa y lParam debe contener la dirección 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'.

Page 183: curso lenguaje ensamblador

Transmitiendo los Mensajes del Ratón al Control 'Tooltip'

Cuando el paso de arriba es completado, el control 'tooltip' sabe qué área debería monitorearse para los mensajes del ratón y qué texto debería desplegar el conrtrol tooltip. Lo único que falta es el *gatillo* [trigger] para la acción. Piensa en ello: el área especificada por la herramienta está sobre el área cliente de la otra ventana. ¿Cómo puede interceptar el control 'tooltip' los mensajes del ratón para esa ventana? Necesita hacerlo así con el fin de que pueda medir la cantidad de tiempo que el puntero del ratón 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 métodos para alcanzar esta meta, una requiere la cooperación de la ventana que contiene la herramienta y la otra sin la cooperación sobre la parte de esa ventana.

La ventana que contiene la herramientea debe transmitir los mensajes del ratón al control 'tooltip' enviando mensajes TTM_RELAYEVENT al control. El valor lParam debe contener la dirección de una estructura MSG que especifica el mensaje a ser transmitido al control 'tooltip'. Un control 'tooltip' procesa sólo los siguientes mensajes del ratón:

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         ..........

Page 184: curso lenguaje ensamblador

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 ratón sin la cooperación de la ventana. Este método es más fácil de usar ya que no requiere más código que la especificación 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 cuáles deberías tener conocimiento.

TTM_ACTIVATE.  Si quieres deshabilitar/habilitar el control 'tooltip' dinámicamente, 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 después de que fue subsumida al control 'tooltip', usa estos mensajes. Necesitas especificar esta herramienta con los valores correctos de uId y hWnd. Si sólo quieres cambiar el miembro rect, usa el mensaje TTM_NEWTOOLRECT. Si sólo 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 más.

Ejemplo:

El siguiente ejemplo es una simple caja de diálogo con dos botones. El área cliente de la caja de diálogo 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 también 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

Page 185: curso lenguaje ensamblador

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

Page 186: curso lenguaje ensamblador

        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

Page 187: curso lenguaje ensamblador

        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

Análisis:

Page 188: curso lenguaje ensamblador

Después de que es creada la ventana del diálogo 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

Después de eso procedemos a definir cuatro herramientas para cada esquina de la caja de diálogo.

    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 la

ventana del diálogo.     push hDlg     pop ti.hWnd    ; manejador para la ventana que contiene la

herramienta     invoke GetWindowRect,hDlg,addr rect; obtener la dimensión 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 dimensión del área cliente. Esa es la razón por la cual llamamos a GetWindowRect. No queremos transmitir mensajes de ratón al control 'control' nosotros mismos, así que especificamos la bandera TIF_SUBCLASS.

SetDlgToolArea es una función que calcula el rectángulo asociado de cada herramienta y registra la herramienta para el control 'tooltip'. No entraré en detalles engorrosos sobre el cálculo, es suficiente decir que divide el área cliente en 4 áreas con los mismos tamaños. Luego envía el mensaje TTM_ADDTOOL al control 'tooltip', pasando la dirección de la estructura TOOLINFO en el parámetro lParam.

    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

Después de que son registradas las 4 herramientas, podemos ocuparnos en los botones de la caja de diálogo. Podemos manejar cada botón a través 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 diálogo y luego registrarlos para el control 'tooltip'. EnumChildWindows tiene la siguiente sintaxis:

EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD

Page 189: curso lenguaje ensamblador

hWnd es el manejador de la ventana padre. lpEnumFunc es la dirección de la función EnumChildProc que será llamada para cada control enumerado. lParam es el valor definido de la aplicación [application-defined value] que será pasado a la función EnumChildProc. La a función EnumChildProc tiene la siguiente definición:

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 más o menos así:

invoke EnumChildWindows,hDlg,addr EnumChild,addr ti

Pasamos la dirección de la estructura TOOLINFO en el parámetro lParam porque registraremos cada control de ventana hija para el control 'tooltip' en la función EnumChild. Si no queremos usar este método, 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 diálogo y llamamos a la función EnumChild una vez para cada control enumerado. De esta manera, si nuestra caja de diálogo tiene dos controles, EnumChild será llamada dos veces.

La función 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

Page 190: curso lenguaje ensamblador

herramienta. También debemos especificar la benadera TTF_IDISHWND en el miembrio uFlags.

Tutorial 28: API de Depuración de Win32 Parte 1

En este tutorial, aprenderás que ofrece Win32 a los desarrolladores interesados en las primitivas de depuración. Sabrás como depurar un proceso cuando hayas finalizado con este tutorial. Bajar el ejemplo.

Teoría:

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 depuración de Win32 o primitivas. Con ellas puedes:

Cargar un programa o enganchar un programa que esté corriendo para su depuración

Obtener información de bajo nivel sobre el programa que estás depurando, tal como el ID, la dirección del punto de entrada, la base de la imagen ,etc..

Ser notificado sobre los eventos de depuración relacionados tales como cuando un proceso/hilo inicia/termina, cuándo las DLLs son cargadas/descargadas, etc.

Modificar el proceso/hilo que está siendo depurado

En pocas palabras, puedes escribir el código 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 básicos y daré un marco general para usar las APIs de depuración de Win32 en este tutorial.

Los pasos al usar las APIs de depuración de Win32 son:

1. Crear un proceso o enganchar un proceso en curso. Este es el primer paso al usar las APIs de depuración 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 depuración, 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 depuración [important debugging-related (debug events)] que ocurren en el "depurando". El proceso en depuración será suspendido inmediatamente hasta que tu programa esté listo. Si el proceso en depuración crea también un proceso hijo, Windows enviará también eventos de depuración 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 combinación con la bandera DEBUG_PROCESS.

o Puedes anexar tu programa a un proceso que esté corriendo con DebugActiveProcess.

Page 191: curso lenguaje ensamblador

2. Esperar por eventos de depuración. Después de que tu programa ha adquirido un proceso para su depuración, el hilo primario del proceso en depuración es suspendido hasta que tu programa llame a WaitForDebugEvent. Esta función trabaja como otras funciones WaitForXXX,, es decir, bloquea el hilo que llama hasta que ocurre el evento por el cual se espera. En este caso, espera por eventos de depuración a ser enviados por Windows. Veamos su definición:

WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

lpDebugEvent es la dirección de una estructura DEBUG_EVENT que será llenada con información sobre el evento de depuración que ocurre dentro del depurando.

dwMilliseconds es el lapso de tiempo en milisegundos que esta función esperará hasta que ocurra el evento de depuración. Si este período caduca y no ocurre ningún evento de depuración, WaitForDebugEvent regresa al programa que ha hecho la llamada. Pero si especificas la constante INFINITE en este argumento, la función no regresará hasta que ocurra un evento de depuración.

Ahora examinemos con más 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 depuración 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

CREATE_PROCESS_DEBUG_EVENT

Un proceso ha sido creado. Este evento será enviado cuando el proceso en depuración es creado (y todavía no está correindo) o cuando tu programa se anexe a un proceso con DebugActiveProcess. Este es el primer evento que recibirá tu programa.

EXIT_PROCESS_DEBUG_EVENT Un proceso termina.

CREATE_THREAD_DEBUG_EVENT

Se ha creado un nuevo hilo en el proceso en depuración o tu programa se anexa a un proceso que ya está corriendo. Nota que no recibirás esta notificación cuando el hilo primario del proceso en depuración sea creado.

EXIT_THREAD_DEBUG_EVENTTermina un hilo en el proceso en depuración. Tu programa no recibirá este evento para el hilo primario. En pocas

Page 192: curso lenguaje ensamblador

palabras, puedes pensar en el hilo primario del proceso en depuración como un equivalente del mismo proceso en depuración. 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 depuración carga una DLL. Recibirás 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 depuración llama a LoadLibrary.

UNLOAD_DLL_DEBUG_EVENT Una DLL es descargada del proceso en depuración.

EXCEPTION_DEBUG_EVENT

Ocurre una excepción en el proceso en depuración. Importante: Este evento ocurrirá una vez justo antes de que el proceso en depuración comience a ejecutar su primera instrucción. La excepción realmente es una ruptura de depuración [a debug break] (int 3h). Cuando quieres resumir el proceso en depuración, llamas a ContinueDebugEvent con la bandera DBG_CONTINUE. No uses la bandera DBG_EXCEPTION_NOT_HANDLED sino el proceso en depuración rehusará correr bao NT (en Win98, trabaja bien).

OUTPUT_DEBUG_STRING_EVENT

Este evento es generado cuando el proceso en depuración llama a la función DebugOutputString para eviar una 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 depuración. Puedes usar estos valores como identificadores del proceso/hilo en el cual estás interesado. Recuerda que si usas CreateProcess para cargar el proceso en depuración, también obtienes los IDs del proceso y del hilo del proceso en depuración en la estructura PROCESS_INFO. Puedes usar estos valores para diferenciar entre los eventos de depuración que ocurren en el proceso en depuración y su proceso hijo (en caso de que no hayas especificado la bandera DEBUG_ONLY_THIS_PROCESS).

u es una union que contiene más información sobre el proceso en depuración. Puede ser una de las siguientes estructuras dependiendo del valor de dwDebugEventCode arriba.

valor en dwDebugEventCode Interpretación de u

CREATE_PROCESS_DEBUG_EVENT

Una estructura CREATE_PROCESS_DEBUG_INFO

Page 193: curso lenguaje ensamblador

llamada CreateProcessInfo

EXIT_PROCESS_DEBUG_EVENTUna estructura EXIT_PROCESS_DEBUG_INFO llamada ExitProcess

CREATE_THREAD_DEBUG_EVENT

Una estructura CREATE_THREAD_DEBUG_INFO llamada CreateThread

EXIT_THREAD_DEBUG_EVENTUna estructura EXIT_THREAD_DEBUG_EVENT llamada ExitThread

LOAD_DLL_DEBUG_EVENT Una estructura LOAD_DLL_DEBUG_INFO llamada LoadDll

UNLOAD_DLL_DEBUG_EVENTUna estructura UNLOAD_DLL_DEBUG_INFO llamada UnloadDll

EXCEPTION_DEBUG_EVENTUna estructura EXCEPTION_DEBUG_INFO llamada Exception

OUTPUT_DEBUG_STRING_EVENT

Una estructura OUTPUT_DEBUG_STRING_INFO llamada DebugString

RIP_EVENT A RIP_INFO llamada RipInfo

En este tutorial no entraré en detalles sobre todas las estructuras, aquí sólo será cubierta la estructura CREATE_PROCESS_DEBUG_INFO.

Asumiendo que nuestro programa llama a WaitForDebugEvent y regresa . Lo primero que deberíamos hacer es examinar dwDebugEventCode para ver qué tipo de evento de depuración ocurrió en el proceso en depuración. 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 depuración. Cuando regresa WaitForDebugEvent, signifca que acaba de ocurrir un evento de depuración 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.

4. Dejar que el proceso en depuración continúe la ejecución. Cuando ocurre un evento de depuración, Windows suspende el proceso en depuración. Cuando hayas terminado la manipulación del evento, necesitas poner en movimiento el proceso en depuración de nuevo. Haces esto llamando a la función ContinueDebugEvent.

ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

Esta función resume el hilo que fue suspendido previamante porque ocurrió un evento de depuración.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.

Page 194: curso lenguaje ensamblador

dwContinueStatus especifica cómo continuar el hilo que reportó el evento de depuración. Hay dos valores posibles: DBG_CONTINUE y DBG_EXCEPTION_NOT_HANDLED. Para los otros eventos de depuración, esos dos valores hacen lo mismo: resumen el hilo. La excepción es el EXCEPTION_DEBUG_EVENT. Si el hilo reporta un evento de depuración excepción, significa que ocurrió una excepción en el hilo del proceso en depuración. Si especificas DBG_CONTINUE, el hilo ignorará su manipulación de la excepción y continuará con la ejecución. En este escenario, tu programa debe examinar y resolver la excepción misma antes de resumir el hilo con DBG_CONTINUE sino la excepción ocurrirá una vez más, una vez más.... Si especificas DBG_EXCEPTION_NOT_HANDLED, tu programa está diciendo a Windows que no manejará la excepción: Windows usaría el manejador de excepción por defecto del proceso en depuración para manejar la excepción. En conclusión, si el evento de depuración refiere a una excepción en el proceso en depuración, deberías llamar a ContinueDebugEvent con la bandera DBG_CONTINUE si tu programa ya removió la causa de la excepción. 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 depuración vaya a ejecutar su primera instrucción, tu programa recibirá el evento de depración exepción. Realmente es un quiebre de depuración [debug break] (int 3h). Si respondes llamando a ContinueDebugEvent con la bandera DBG_EXCEPTION_NOT_HANDLED, Windows NT reusará correr el proceso en depuración (porque nada cuida de él). Siempre debes usar la bandera DBG_CONTINUE en este caso para decir a Windows que quieres que el hilo continúe.

5. Continuar este ciclo en un bucle infinito hasta que termine el proceso en depuración. Tu programa debe estar en un bucle infinito muy parecido al bucle de mensajes hasta que el proceso en depuración termine. El bucle tiene más 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 depuración hasta que termine.

Resumamos los pasos de nuevo:

1. Crear un procesos o anexar tu programa a un proceso que esté corriendo.

2. Esperar por los eventos de depuración 3. Hacer lo que tu programa quiere hacer en respuesta al evento en

depuración. 4. Dejar que el proceso en depuración continúe su ejecución. 5. Continuar este ciclo en un bucle infinito hasta que el proceso en

depuración termina

Page 195: curso lenguaje ensamblador

Ejemplo:

Este ejemplo depura un programa win32 y muestra información 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,

Page 196: curso lenguaje ensamblador

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

Análisis:

El programa llena la estructura OPENFILENAME y luego llama a GetOpenFileName para pernitir que el usuario elija un programa para su depuración.

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 depuración, introducimos el bucle infinito de depuración, llamando a WaitForDebugEvent. WaitForDebugEvent no regresará hasta que ocurra un evento de depuración en el 'proceso en depuración' porque especificamos INFINITE como su segundo parámetro. Cuando ocurre un evento de depuracion, WaitForDebugEvent regresa y DBEvent es llenada con información sobre el evento de depuración.

   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT        invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION        .break

Page 197: curso lenguaje ensamblador

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 depuración ha culminado] y luego salimos del bucle de depuración.

   .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 información interesante sobre el proceso en depuración en una caja de mensaje. Obtenemos esa información a partir de u.CreateProcessInfo. CreateProcessInfo es una estructura del tipo CREATE_PROCESS_DEBUG_INFO. Puedes obtener más 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 excepción. Es una línea larga de referencia de estructura anidada pero puedes obtener el tipo de excepción 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 depuración no tiene incrustado int 3h), podemos asumir con seguridad que esta excepción ocurrió cuando el proceso en depuración iba a ejecutar la primera instrucción. 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 depuración. Luego volvemos a esperar el siguiente evento de depuración.

   .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

Page 198: curso lenguaje ensamblador

Excepto para el caso EXCEPTION_DEBUG_EVENT de arriba, llamamos a ContinueDebugEvent con la bandera DBG_EXCEPTION_NOT_HANDLED para resumir el proceso en depuración.

invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread

Cuando termina el proceso en depuración, estamos fuera del bucle de depuración y debemos cerrar los manejadores del proceso y del hilo del proceso en depuración. Cerrar los manejadores no significa que estamos matando el proceso/hilo. Sólo significa que no queremos usar más esos manejadores para referir al proceso/hilo.

Tutorial 29: API de Depuración de Win32 Parte 2

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

Baja el ejemplo

Teoría:

En el tutorial previo, aprendimos como cargar el debuggee y a manejar eventos de depuración 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 propósito.

ReadProcessMemory Esta función permite leer la memoria en el proceso especificado. El prototipo de la función 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 dirección del proceso objeto [target process] que quieres comenzar a leer. Por ejemplo, si quieres leer 4 bytes del proceso en depuración comenzando en 401000h, el valor en este parámetro debe ser 401000h.lpBuffer es la dirección del buffer que recibirá los bytes leídos del proceso. nSize es el número de bytes que quieres leerlpNumberOfBytesRead es la dirección de la variable de tamaño dword que recibe el número de bytes realmente leídos. Si no esrelevante para tí, puedes usar NULL.

WriteProcessMemory es la contraparte de ReadProcessMemory. Te permite escribir la memoria del proceso objeto. Sus parámetros son exactamente los mismos que los de ReadProcessMemory

Las siguientes dos funciones de la API necesitan un poco de explicación. 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 más alta. Justo antes de conmutar

Page 199: curso lenguaje ensamblador

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 depuración, Windows suspende el proceso en depuración. El contexto del proceso en depuración es salvado. Como el proceso en depuración es suspendido, podemos estar seguros de que los valores en el contexto se mantendrá sin alteración. 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 depuración: puedes alterar los valores del registro salvado y justo antes de que el proceso en depuración resuma la ejecución, los valores del contexto serán re-escritos dentro de los registros. Cualquier cambio que hagas al contexto es reflejado en el proceso en depuración. Piensa en ello: ¡incluso puedes alterar el valor del registro eip y desviar el flujo de la ejecución a donde quieras! No serás capaz de hacer eso bajo circunstancias normales.

GetThreadContext proto hThread:DWORD, lpContext:DWORD

hThread es el manejador del hilo del que quieres obtener el contextolpContext es la dirección de la estructura CONTEXT que será llenada cuando la función regresa satisfactoriamente.

SetThreadContext tiene exactamente los mismos parámetros. Vemos que la estructura CONTEXT se ve más o menos como:

CONTEXT STRUCT ContextFlags dd ?

;----------------------------------------------------------------------------------------------------------; Esta sección es regresada si ContextFlags contiene el valor ; CONTEXT_DEBUG_REGISTERS

;-----------------------------------------------------------------------------------------------------------iDr0 dd ? iDr1 dd ? iDr2 dd ? iDr3 dd ? iDr6 dd ? iDr7 dd ?

;----------------------------------------------------------------------------------------------------------; Esta sección es regresada si ContextFlags contiene el valor ; CONTEXT_FLOATING_POINT

;-----------------------------------------------------------------------------------------------------------

FloatSave FLOATING_SAVE_AREA <> ;--------------------------------------------------------------------------------------------

--------------; Esta sección es regresada si ContextFlags contiene el valor ; CONTEXT_SEGMENTS

;-----------------------------------------------------------------------------------------------------------

regGs dd ? regFs dd ?

Page 200: curso lenguaje ensamblador

regEs dd ? regDs dd ?

;----------------------------------------------------------------------------------------------------------; Esta sección es regresada si ContextFlags contiene el valor CONTEXT_INTEGER

;-----------------------------------------------------------------------------------------------------------

regEdi dd ? regEsi dd ? regEbx dd ? regEdx dd ? regEcx dd ? regEax dd ?

;----------------------------------------------------------------------------------------------------------; Esta sección es regresada si ContextFlags contiene el valor ; CONTEXT_CONTROL

;-----------------------------------------------------------------------------------------------------------

regEbp dd ? regEip dd ? regCs dd ? regFlag dd ? regEsp dd ? regSs dd ?

;----------------------------------------------------------------------------------------------------------; Esta sección 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 cuáles 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 sólo 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 límite de una dword sino obtendrás extraños resultados bajo NT. Debes especificar "align dword" justo arriba de la línea que lo declara, como este:

align dwordMyContext 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

Page 201: curso lenguaje ensamblador

win.exe y modificará el código 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

Page 202: curso lenguaje ensamblador

.else     invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif invoke ExitProcess, 0 end start

;--------------------------------------------------------------------; El código 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 FEinvoke 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

Análisis:

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 parámetro. 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

Page 203: curso lenguaje ensamblador

    mov ThreadId, eax     invoke DebugActiveProcess, ProcessId

Después de que obtengamos el Id del proceso, podemos llamar a DebugActiveProcess. Luego introducimos el bucle de depuración que espere por los eventos de depuración.

       .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 cirugía sobre él. En este ejemplo, podemos sobreescribir la instrucción del bucle infinito en el depurando (0EBh 0FEh) con NOPs ( 90h 90h). Primero, necesitamos obtener la dirección de la instrucción. Puesto que el depurando está ya en el bucle por el tiempo que nuestro programa está anexo a él, eip siempre apuntará a la instrucción. 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 instrucción "jmp $" con NOPs, ayudar efectivamente de esta manera a que el depurando salga del bucle infinito. Después de que desplegamos el mensaje al usuario y luego llmamos a ContinueDebugEvent para resumir el debuggee. Puesto que la instrucción "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 aproximación 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 ..............

Page 204: curso lenguaje ensamblador

Todavía se llama a GetThreadContext para obtener el valor actual de eip pero en vez de sobreescribir la instrucción "jmp $", se incrementa en 2 el valor de regEip para "saltar por encima" ["skip over"] de la instrucción. El resultado es que cuando el archivo en depuración [debuggee] vuelve a ganar el control, resume la ejecución en la siguiente instrucción después de "jmp $".

Ahora puedes ver el poder de Get/SetThreadContext. También puedes modificar las otras imágenes de registros y sus valores serán reflejados de regerso al depurando. Incluso puedes insertar la instrucción int 3h para poner puntos de quiebre [breakpoints] en el proceso debuggee.

Tutorial 30: API de Depuración de Win32 Parte 3

En este tutorial, continuaremos la exploración de la api de depuración de win32. Específicamente, aprenderemos como trazar el depurando [debugee].Baja el ejemplo.

Historia de revisiones:

12/2/2000: Se olvidó el alineamiento dword de la estructura CONTEXT

Teoría:

Si haz usado antes un depurador, estarás ya familiarizado con el trazado. Cuando "trazas" un programa, se detiene después de ejecutar cada función, dándote la oportunidad de examinar los valores de registros/memoria. Paso simple [single-stepping] es el nombre oficial del trazado.

El rasgo de paso simple [single-step] es proveído 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 excepción de depuración después de cada instrucción. Después de que se genera la excepción de depuración, la bandera de trampa es limpiada automáticamente.

También 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 depuración, como es usual. El depurando

[debuggee] se ejecutará en modo de paso simple [single-step]. Después de que se ejecuta cada instrucción, obtendremos EXCEPTION_DEBUG_EVENT con el valor EXCEPTION_SINGLE_STEP en u.Exception.pExceptionRecord.ExceptionCode

5. Si necesitas trazar la próxima instrucción , necesitarás poner de nuevo el bit de trampa.

Ejemplo:

Page 205: curso lenguaje ensamblador

.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 dwordcontext 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

Page 206: curso lenguaje ensamblador

          .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

Análisis:

El programa muestra la caja de diálogo openfile. Cuando el usuario elige un archivo ejecutable, ejecuta el programa en modo de paso simple, contando el número 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

Page 207: curso lenguaje ensamblador

Aprovechamos esta oportunidad para poner el depurando [debuggee] en modo de paso simple [single-step mode]. Recuerda que Windows envía un EXCEPTION_BREAKPOINT justo antes de que se ejecute la primera instrucción 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. Más específicamente, 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 depuración [debuggee].

          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP              inc TotalInstruction

Cuando se ejecuta una función en el archivo de depuración, 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 depuración 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 instrucción en el archivo bajo depuración.

             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 después que se genera la excepción de depuración, debemos poner la bandera de trampa de nuevo si queremos continuar en modo de paso simple [single-step mode].

Page 208: curso lenguaje ensamblador

Adevertencia: No uses el ejemplo en este tutorial con un programa muy grande: el trazado sería muy LENTO. Podrías tener que esperar hasta diez minutos antes de que puedas cerrar el archivo en depuración.