Site hosted by Angelfire.com: Build your free website today!

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:

  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
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
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,OFFSET MenuName
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,300,200,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .WHILE TRUE
        invoke GetMessage, ADDR msg,NULL,0,0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_CREATE
        invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
                   WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
                   ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
                   0,0,0,hWnd,EditID,\
                   hInstance,NULL
        mov hwndEdit,eax
        invoke SetFocus,hwndEdit
;========================================================
;        Inicializacion de los miembros de la estructura OPENFILENAME
;========================================================
        mov ofn.lStructSize,SIZEOF ofn
        push hWnd
        pop  ofn.hWndOwner
        push hInstance
        pop  ofn.hInstance
        mov  ofn.lpstrFilter, OFFSET FilterString
        mov  ofn.lpstrFile, OFFSET buffer
        mov  ofn.nMaxFile,MAXSIZE
    .ELSEIF uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        shr edx,16
        and eax,0ffffh
        invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
    .ELSEIF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_OPEN
                mov  ofn.Flags, OFN_FILEMUSTEXIST or \
                                OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                invoke GetOpenFileName, ADDR ofn
                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                GENERIC_READ or GENERIC_WRITE ,\
                                FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                NULL
                    mov hFile,eax
                    invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
                    mov  hMemory,eax
                    invoke GlobalLock,hMemory
                    mov  pMemory,eax
                    invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
                    invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
                    invoke CloseHandle,hFile
                    invoke GlobalUnlock,pMemory
                    invoke GlobalFree,hMemory
                .endif
                invoke SetFocus,hwndEdit
            .elseif ax==IDM_SAVE
                mov ofn.Flags,OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                invoke GetSaveFileName, ADDR ofn
                    .if eax==TRUE
                        invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ or GENERIC_WRITE ,\
                                                FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                                NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL
                        mov hFile,eax
                        invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
                        mov  hMemory,eax
                        invoke GlobalLock,hMemory
                        mov  pMemory,eax
                        invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
                        invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
                        invoke CloseHandle,hFile
                        invoke GlobalUnlock,pMemory
                        invoke GlobalFree,hMemory
                    .endif
                    invoke SetFocus,hwndEdit
                .else
                    invoke DestroyWindow, hWnd
                .endif
            .endif
        .ELSE
            invoke DefWindowProc,hWnd,uMsg,wParam,lParam
            ret
.ENDIF
xor    eax,eax
ret
WndProc endp
end start


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.

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

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

dwShareMode especifica qué operaciones quieres reservar para que otros procesos puedan llevarlas a cabo en el archivo que va a ser 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. dwFlagsAndAttributes especifica los atributos de archivo                     invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
                    mov  hMemory,eax
                    invoke GlobalLock,hMemory
                    mov  pMemory,eax

Cuando se abre el archivo, reservamos un bloque de memoria para usar con las funciones ReadFile y WriteFile. Especificamos el flag GMEM_MOVEABLE para dejar a Windows mover el bloque de memoria para consolidar la memoria. La bandera [flag] GMEM_ZEROINIT le dice a GlobalAlloc que rellene el nuevo bloque de memoria reservado con ceros.

Cuando GlobalAlloc vuelve satisfactoriamente, eax contiene el manejador (handle) al bloque de memoria reservado. Le pasamos este manejador (handle) a la 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.

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.


Indice

Siguiente

[Iczelion's Win32 Assembly HomePage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por  JoTaKe, y revisado por:   n u M I T_o r