a rtesanías
in italiano in english
artículos descargas cursos
Un taller en desarrollo para la construcción de futuras aplicaciones para MenuetOS y la divulgación de la programación en lenguaje ensamblador.

APLICACIONES:

Bienvenidos al tercer curso de la serie. Ahora tenemos listos los conceptos básicos y no nos queda más remedio que comenzar a divertirnos...


Paquete de entrenamiento tut03.zip (243 kB)

Como de costumbre, sugerimos descargar el material de trabajo en el mismo directorio de los anteriores.
En éste paquete encontrarás una utilidad llamada "spy" que será imprescindible de aquí en adelante.

Mayo 10 de 2004

Unas palabras de aliento ántes de entrar en materia. La ventaja principal de programar en ensamblador es "tener" que entender como funcionan realmente las cosas. Al principio puede parecer innecesariamente tedioso y desventajoso respecto a los métodos más conocidos y difusos, es decir, los demás lenguajes y herramientas que "simplifican" la programación.
Pero dentro de muy poco tiempo te darás cuenta de dos cosas:
1. El ensamblador no es tan tedioso como parecía al principio.
2. El conocer los mecanismos internos permite mejorar el desempeño en cualquier lenguaje o método de programación.
Ahora haremos lo que nos interesa: programar la interfaz gráfica e interactuar con ella, lo que seguramente es el motivo principal que tienes para seguir leyendo éstas líneas ¿o no?


IMPORTANTE:
El material tratado en el siguiente curso tiene como objetivo fundamental, el explicar cómo funcionan los mecanismos internos necesarios para hacer programas en Windows.


Al terminar el curso, veremos algunas simplificaciones para la programación del día a día. Es importante no dejarnos intimidar por su complejidad para lograr entender los conceptos.

Bueno, a continuación, el primer ejemplo del curso.

Se trata de un editor de texto demasiado simple.

Listado de tut03es1.asm
format PE GUI

include 'macros.inc'

NULL   = 00h

WM_DESTROY      = 0002h
GWL_WNDPROC     = -4

WS_VISIBLE      = 010000000h
WS_SYSMENU      = 000080000h
WS_THICKFRAME   = 000040000h
WS_EX_TOPMOST   = 00008h

ES_MULTILINE    = 0004h
ES_AUTOVSCROLL  = 0040h

struc POINT
 {
   .x dd ?
   .y dd ?
 }

struc MSG
 {
   .hwnd    dd ?
   .message dd ?
   .wParam  dd ?
   .lParam  dd ?
   .time    dd ?
   .pt      POINT
 }

section '.code' code readable executable

call CreateWindowEx,WS_EX_TOPMOST,_class,_title,\
       WS_VISIBLE or WS_SYSMENU or WS_THICKFRAME\
       or ES_AUTOVSCROLL or ES_MULTILINE,\
       200,220,220,180,\
       NULL,NULL,NULL,NULL
mov  [hwnd],eax

call SetWindowLong,eax,GWL_WNDPROC,WndProc
mov  [oldproc],eax

msg_loop:
    call GetMessage,msg,NULL,0,0
    or   eax,eax
    jz   terminate
    call TranslateMessage,msg
    call DispatchMessage,msg
    jmp  msg_loop

terminate:
    call ExitProcess,[msg.wParam]

WndProc:
    push ebp
    mov  ebp,esp
    mov  eax,[ebp+12]
    cmp  eax,WM_DESTROY
    jne  .default
    call PostQuitMessage,0
  .default:
    call CallWindowProc,[oldproc],[ebp+8],\
           [ebp+12],[ebp+16],[ebp+20]
    leave
    retn 16

section '.data' data readable writeable

hwnd      dd ?
oldproc   dd ?
msg       MSG

_class   db 'EDIT',0
_title   db 'Ensamblando Win32',0

section '.idata' import data readable

library kernel,'KERNEL32.DLL',\
        user,'USER32.DLL'

import kernel,\
        ExitProcess,'ExitProcess'

import user,\
        CallWindowProc,'CallWindowProcA',\
        CreateWindowEx,'CreateWindowExA',\
        DispatchMessage,'DispatchMessageA',\
        GetMessage,'GetMessageA',\
        PostQuitMessage,'PostQuitMessage',\
        SetWindowLong,'SetWindowLongA',\
        TranslateMessage,'TranslateMessage'
Pantallazo
Resultado
El principio no cambia. Las dos primeras líneas ya son muy familiares y el archivo 'macros.inc' es el mismo del curso anterior.
A las igualdades que se encuentran al principio las llamaremos constantes de Windows. Son simplemente números a los que daremos nombres especiales.
Podríamos usar solo los números respectivos y el programa funciona igualmente. Pero es más fácil recordar que quiere decir "WS_VISIBLE" que simplemente "010000000h".
struc
La primera novedad del ejemplo es una directiva del fasm que nos permite asociar diferentes etiquetas bajo un mismo nombre de base, comportandose como una unidad llamada estructura.
POINT es muy usado en las librerías de Windows. Se usa para describir un punto en la pantalla por medio de las coordenadas x e y.
MSG es otra estructura básica para el Windows. Se utiliza para comunicaciones entre el sistema operativo y cada aplicación.
Entre la información comunicada encontramos un punto, es decir, podemos definir estructuras usando otras estructuras.
Comienza la sección '.code' y con ella, la ejecución de nuestro programa.

El sistema operativo cargará la sección en memoria y pondrá el procesador a ejecutar en la primera línea que encuentra, en éste caso, llamará la función CreateWindowEx por medio de nuestra macro call.
CreateWindowEx
Es una función de Windows, específicamente de la librería user32.dll. Nos permite crear una ventana con unas características determinadas. Las características se determinan con los valores que dejamos en la pila.
En el caso particular, la función espera encontrar doce (12) valores en estricto orden.
Otra novedad es el uso de "\" para continuar en la línea siguiente. Fasm interpretará cada línea siguiente como si hiciera parte de la misma línea.

Estamos creando una ventana muy conocida y utilizada en windows: EDIT. En windows, cada objeto que vemos en la pantalla es o hace parte de una ventana. Cada tipo de ventana pertenece a una clase determinada. Cada ventana EDIT que sea creada, tiene las mismas características y comportamiento aunque nosotros podemos adaptarlo a nuestras necesidades.
mov
Es una de las instrucciones más conocidas del lenguaje ensamblador y es quizá la más utilizada. Es una orden directa al procesador para que asigne un valor en una posición determinada.
Aquí se trata de asignar en una posición de memoria con etiqueta hwnd, el valor que hay en eax.
eax
Es un registro del procesador. Se llama acumulador y tiene una capacidad de 32 bits.
Un registro es un componente electrónico que hace parte del procesador y es en realidad un área de trabajo. El procesador puede trabajar solo con la información que almacena uno de los registros.
Como se deduce de lo anterior, la importancia de los registros es crucial para la programación de computadores y por lo tanto tendremos la oportunidad de profundizar el tema.
La situación es análoga con las demás funciones y describiremos dentro de poco su funcionamiento. Por ahora veamos un elemento crucial para el funcionamiento de una aplicación: el bucle de mensajes (message loop).

Las aplicaciones en Windows dependen de la información que el sistema les entrega bajo forma de mensajes. Los movimientos del ratón, una tecla pulsada, un temporizador o un menu, por ejemplo, llegan a nuestra aplicación en forma de mensajes. La aplicación decide cómo y a cuales mensajes responder.
El loop girara eternamente, esperando a que nosotros le indiquemos cuando parar y terminar el programa.
WndProc
Es el procedimiento de la ventana de nuestra aplicación. Se encarga de recibir y procesar los mensajes a los cuales queremos responder.
Para responder un mensaje, necesitamos identificarlo y escribir el código necesario para cumplir con la función deseada. Ampliaremos éste procedimiento en breve.
El resto del programa es bien conocido en éste momento. La sección '.data' contiene unas etiquetas nuevas, definidas con directivas de datos pero con valor indeterminado denotado por ?. El objetivo de dichas etiquetas es el de manipular variables, es decir, escribiremos en unos espacios reservados en la memoria.
por ejemplo, con la etiqueta hwnd, denotaremos la información contenida en los 32 bits que componen un dword, definido con la directiva dd.
La estructura MSG (en mayúsculas), ocupa un total de 5 dwords (dd) más un punto (POINT) que ocupa 2 dwords más o sea 28 bytes. Es decir, a continuación de la etiqueta msg (en minúsculas), Fasm reserva 28 bytes para la información que escribiremos durante la ejecución del programa.
Usaremos bytes cómo unidad de base para medir la cantidad de memoria porque casi todas las funciones de Windows lo requieren.

Notas acerca de las funciones de Windows:
La pregunta que surge espontánea es: ¿cómo hacemos para saber que hace cada función y que valores debemos dejar en la pila?
La respuesta se encuentra en la documentación para programadores de Windows, principalmente preparados por Microsoft cómo las bibliotecas MSDN. Una alternativa muy popular entre los programadores es el archivo de ayuda Win32.hlp que aunque no ha sido actualizado desde hace tiempo, es una base rápida y efectiva para la gran mayoría de usos.
Casi toda la información útil disponible para Windows está escrita con el lenguaje C en mente, pero para un programador de ensamblador su uso no representa un problema. Basta saber:
1. Qué hace la función,
2. Cuántos y cuáles parámetros necesita,
3. Qué resultado nos entrega.
Otra pregunta frecuente es el ¿porqué se usan tantas constantes?
La respuesta se encuentra en el procesador. El procesador maneja fácilmente los números enteros. Por lo tanto, si queremos aplicaciones veloces y sencillas, la mejor estrategia es tratar de usar en lo posible números enteros. Es lo que las librerías de Windows hacen con sus constantes, para describir estilos, manejos, propiedades o acciones.

Los nombres de las constantes y de las funciones corresponden a la nomenclatura usada por los escritores de las librerías y siguen unas pautas homogéneas que constituyen una práctica común. Los desarrollos de nuevas librerías o aplicaciones para Windows siguen en lo posible los mismos criterios, así como conviene seguir los mismos criterios en el diseño de los elementos gráficos, para que tanto desarrolladores como usuarios sientan el sistema como una unidad y no como una colcha de retazos. Lo anterior constituye la clave para la intuitividad de la interfaz gráfica.

Al principio, la metodología a seguir es muy simple. Copiar textualmente de los ejemplos e ir modificando poco a poco según los requerimientos. Es muy útil tener una colección nutrida de recortes para las operaciones comunes.

Al momento de comenzar un nuevo desarrollo, conviene analizar muy bien todas las operaciones que pretendemos hacer, clasificarlas en grupos que contengan las alternativas posibles asignando números enteros a cada una y escribiendo una función para cada tarea, bien delimitada y precisa. Haciendo ésto, lograremos aplicaciones veloces, pequeñas, fácilmente depurables y expandibles.

Análisis de código



La función de ventana es el corazón de una aplicación para Windows.
Es una función llamada directamente por el sistema operativo. Nosotros le decimos la dirección donde se encuentra la etiqueta (el nombre de la función) y el sistema operativo se encarga de poner cuatro valores en la pila y después llamar la función. Los nombres tradicionales para estos cuatro valores son: hwnd, msg, wparam y lparam. Para evitar bloquear el sistema operativo o nuestra aplicación, debemos "jugar limpio" con éste tipo de interacción con Windows.
La forma descrita anteriormente se conoce como callback y es uno de los pilares fundamentales de la arquitectura del Windows.
Veamos como luce la función de ventana:
WndProc:
    push ebp
    mov  ebp,esp
    mov  eax,[ebp+12]
    cmp  eax,WM_DESTROY
    jne  .default
    call PostQuitMessage,0
  .default:
    call CallWindowProc,[oldproc],[ebp+8],\
           [ebp+12],[ebp+16],[ebp+20]
    leave
    retn 16

Nuestra función reacciona ante un solo mensaje: WM_DESTROY. El mensaje es enviado por el sistema operativo cuando el usuario cierra la ventana. Una vez llega el mensaje, nosotros terminamos la ejecución.
Cuando la ejecución del código llega a la función, existen cuatro valores en la pila en el siguiente órden: hwnd, msg, wparam, lparam. Los nombres y el órden son idénticos para todas las funciones de ventana en Windows.
Nosotros nos encargaremos de leer los valores de la pila y de retirarlos al terminar, como vimos en los tipos de llamadas durante el curso pasado.
Para leerlos, necesitamos saber dónde están y lo conseguimos leyendo el registro esp. Dicho registro se llama precisamente "puntero de la pila" o stack pointer en inglés.


El valor lo pondremos en el registro del "puntero de base" o base pointer ebp. Pero primero empujamos su valor en la pila para no perderlo. Veamos cómo luce la pila después de empujar ebp:
DIRECCIÓN  VALOR       SIGNIFICADO
05504240:  0005504264  Valor en ebp
05504244:  1074317760  RETORNO a KERNEL32
05504248:  0000000168  hwnd
05504252:  0000000015  msg
05504256:  0000000000  wparam
05504260:  0000000000  lparam
05504264:  1074317798
05504268:  0000000226
05504272:  0005504264

No te preocupes si parece demasiado articulado al principio, pero es necesario comprenderlo bien porque la secuencia es la misma para cualquier procedimiento. La secuencia anterior es la que nos permite tener cualquier cantidad de información en la pila ántes de llamar una función.

Los números de las direcciones se deben a que el sistema operativo decide en cual posición de memoria comienza la pila. A nosotros nos interesa solo saber la posición respecto al elemento que se encuentra en la cima, indicado por el valor en esp.
Como esp varía con cada movimiento de la pila, recurriremos a ebp, el cual no variará mientras lo necesitamos. Éste es el significado de las primeras dos líneas de la función.
Siendo así, el valor de hwnd lo encontraremos en ebp+8, el de msg en ebp+12 y así sucesivamente.
La pila contiene valores de 32 bits que nosotros podemos leer o modificar a nuestro antojo. En el ejemplo nos limitamos a leer uno de ellos, msg, que contiene el número correspondiente al mensaje que Windows le está comunicando a nuestra función de ventana.
Necesitamos terminar nuestra aplicación cuando nos llegue el mensaje WM_DESTROY. Para identificar dicho mensaje, comparamos su valor con el que se encuentra en la pila.

Listado de WndProc
WndProc:
    push ebp
    mov  ebp,esp
    mov  eax,[ebp+12]
    cmp  eax,WM_DESTROY
    jne  .default
    call PostQuitMessage,0
  .default:
    call CallWindowProc,[oldproc],[ebp+8],\
           [ebp+12],[ebp+16],[ebp+20]
    leave
    retn 16
cmp
Es una instrucción del ensamblador que hace una comparación entre los argumentos dados restándolos pero sin afectarlos.
Si los valores son iguales, el resultado es cero, que es lo que nos interesa determinar.
jne
Es una instrucción del ensamblador que permite hacer un salto condicional a otra dirección de ejecución. Su nombre es una sigla de "jump if not equal" es decir "salto si no es igual".
Si los valores comparados anteriormente son iguales, la ejecución continúa en la instrucción siguiente, de lo contrario, salta a la etiqueta .default definida más abajo.
CallWindowProc
Es una función del sistema operativo que nos permitirá usar la función de ventana habitual de un cuadro de edición, para que actúe en la forma definida por Windows.
A la función le entregamos los mismos valores que el sistema operativo nos dió pero además le damos la dirección de la función, que tenemos guardada en la variable oldproc desde que creamos la ventana.

Las comparaciones y saltos del lenguaje ensamblador constituyen la espina dorsal del lenguaje. Permiten la programación estructurada y la toma de decisiones. Veremos que constituyen un método de programación eficaz, limpio y potente.

Cuando la instrucción cmp es ejecutada, el procesador actualiza un registro especial llamado flags register de 16 bits. Es especial porque cada bit es independiente y algunos tienen un nombre y una función particular.

FLAGS REGISTER - REGISTRO DE BANDERAS (16 bit)

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 0 0 O D I T S Z 0 A 0 P 0 C

Overflow - Desbordamiento
Direction - Dirección
Interruption - Interrupción
Trap - Atrapamiento
Sign - Signo
Zero - Cero
Auxiliary Carry - Acumulador auxiliar
Parity - Paridad
Carry - Acumulador


Enumeraremos los bits siempre en la misma forma, de 15 hasta cero, porque se trata de números binarios. El bit más significativo se encuentra a la izquierda.
Cada una de éstas banderas puede tener un valor 1 (activo) ó 0 (no activo). Muchas instrucciones del ensamblador afectan una o más banderas de acuerdo al tipo de operación. Las iremos viendo a medida que las vayamos necesitando.

Por ahora veremos las que son afectadas directamente por la instrucción cmp.

Conclusión



Éste curso está aún en desarrollo. Los temas tratados deben ser completados.


En construcción.

Pronto será disponible un nuevo curso: ARCHIVOS.


Retroalimentación:

Para nosotros es sumamente importante conocer sus preguntas y comentarios acerca de ésta página para poder mejorar los contenidos del curso. Responderemos y clasificaremos los comentarios con la mayor brevedad posible.

Gracias



Los programas distribuidos en éste sitio son gratuitos para cualquier uso.
Todos los derechos están reservados por sus respectivos autores. El material educativo y los artículos están portegidos por la
Licencia Artística Artesanal.
® 2004 Artisan Shop