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

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 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
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,\
                ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
               CW_USEDEFAULT,300,200,NULL,NULL,\
    hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .WHILE TRUE
        invoke GetMessage, ADDR msg,NULL,0,0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_CREATE
        invoke GetMenu,hWnd                       ;Obtener el manejador del menú
        mov  hMenu,eax
        mov ofn.lStructSize,SIZEOF ofn
        push hWnd
        pop  ofn.hWndOwner
        push hInstance
        pop  ofn.hInstance
        mov  ofn.lpstrFilter, OFFSET FilterString
        mov  ofn.lpstrFile, OFFSET buffer
        mov  ofn.nMaxFile,MAXSIZE
    .ELSEIF uMsg==WM_DESTROY
        .if hMapFile!=0
            call CloseMapFile
        .endif
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_OPEN
                mov  ofn.Flags, OFN_FILEMUSTEXIST or \
                                OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                                invoke GetOpenFileName, ADDR ofn
                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ ,\
                                                0,\
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL
                    mov hFileRead,eax
                    invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
                    mov     hMapFile,eax
                    mov     eax,OFFSET buffer
                    movzx  edx,ofn.nFileOffset
                    add      eax,edx
                    invoke SetWindowText,hWnd,eax
                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
                .endif
            .elseif ax==IDM_SAVE
                mov ofn.Flags,OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                invoke GetSaveFileName, ADDR ofn
                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ or GENERIC_WRITE ,\
                                                FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                                NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL
                    mov hFileWrite,eax
                    invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
                    mov pMemory,eax
                    invoke GetFileSize,hFileRead,NULL
                    invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
                    invoke UnmapViewOfFile,pMemory
                    call   CloseMapFile
                    invoke CloseHandle,hFileWrite
                    invoke SetWindowText,hWnd,ADDR AppName
                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
                .endif
            .else
                invoke DestroyWindow, hWnd
            .endif
        .endif
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor    eax,eax
    ret
WndProc endp

CloseMapFile PROC
        invoke CloseHandle,hMapFile
        mov    hMapFile,0
        invoke CloseHandle,hFileRead
        ret
CloseMapFile endp

end start
 

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.

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


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:   n u M I T_o r