Baja el ejemplo aquí.
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:
.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
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ú.
[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