Уроки Iczelion'а

       

Memory Mapped файлы


Я покажу вам, что такое MMF и как использовать их для вашей выгоды. Использование MMF достаточно просто, как вы увидите из этого туторила.

Скачайте пример здесь.

Теория:

Если вы хорошо изучили пример из прошлого туториала, вы увидите, что у него есть серьезный недостаток: что, если файл, который вы хотите прочитать больше, чем зарезервированный блок памяти? или если строка, которую вы хотите найти будет обрезана посередине, потому что кончился блок памяти? Традиционный ответ на первый вопрос - это то, что вам нужно последовательно читать данные из файла, пока он не кончится. Ответом на второй вопрос является то, что вы должны обрабатывать подобную возможность. Это называется проблемой пограничного значения. Она представляет собой головную большую для программистов и вызывает неисчислимое количество багов.

Было бы неплохо, если бы мы могли зарезервировать очень большой блок памяти, достаточный для того, чтобы сохранить весь файл, но наша программа стала бы очень прожорливой в плане ресурсов. File maрing - это спасение. Используя его, вы можете считать весь файл уже загруженным в память и использовать указатель на память, чтобы читать или писать данные в файл. Очень просто. Hет нужды использовать API памяти и файловые API одновременно, в FM это одно и то же. FM также используется для обмена данными между процессами. При использовании FM таким образом, реально не используется никакой файл. Это больше похоже на блок памяти, который могут видеть все процессы. Hо обмен данными между процессами - весьма деликатный предмет. Вы должны будете обеспечить синхронизацию между процессами и ветвями, иначе ваше приложение очень скоро повиснет.

Мы не будем касаться того, как использовать FM для создания общего pегиона памяти в этом туториале. Мы сконцентрируемся на том, как использовать FM для "загрузки" файла в память. Фактически, PE-загрузчик использует FM для загрузки исполняемых файлов в память. Это очень удобно, так как только необходимые порции файла будут считываться с диска. Под Win32 вам следует использовать FM так часто, как это возможно.


Правда, существует несколько ограничений при использовании FM. Как только вы создали такой файл, его размер не может изменяться до закрытия сессии. Поэтому FM прекрасно подходит для файлов из которых нужно только читать или файловых операций, которые не изменяют размер файла. Это не значит, что вы не можете использовать FM, если хотите увеличить pазмеp файла. Вы можете установить новый размер и создать MMF нового размера и файл увеличится до этого размер. Это просто неудобно, вот и все.
Достаточно объяснений. Давайте перейдем к реализации FM. Для того, чтобы его использовать, должны быть выполнены следующие шаги.

  • Вызов CreateFile для открытия файла.

  • Вызов CreateFileMaрing, которой передается хэндл файла, возвращенный CreateFile. Эта функция создает FM-объект из файла, созданного CreateFile'ом.

  • Вызов MaрViewOfFile, чтобы загрузить выбранный файловый регион или весь файл в память. Эта функция возращает указатель на первый байт промэппированного файлового региона.

  • Используйте указатель, чтобы писать или читать из файла.



  • Вызовите UnmaрViewOfFile, чтобы выгрузить файл.

  • Вызов CloseHandle, передав ему хэндл промэппированного файла в качестве одного из параметра, чтобы закрыть его.

  • Вызов CloseHandle снова, передав ему в этот раз хэндл файла, возвращенный CreateFile, чтобы закрыть сам файл.

Пpимеp:
Программа, листинг которой приведен ниже, позволит вам открыть файл с помощью окна открытия файла. Она откроет файл, используя FM, если это удастся, заголовок окна изменится на имя открытого файла. Вы можете сохранить файл под другим именем, выбрав пункт меню File/Save. Программа скопирует все содержимое открытого файла в новый файл. Учтите, что вы не должны вызывать GlobalAlloc для резервирования блока памяти в этой программе.
.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)
hMaрFile HANDLE 0 ; Указатель на MMF, должен быть инициализирован ; нулем, так как мы также используем его ; в качестве флага в секции WM_DESTROY
.data? hInstance HINSTANCE ? CommandLine LPSTR ? hFileRead HANDLE ? ; Хэндл источника hFileWrite HANDLE ? ; Хэндл выходного файла hMenu HANDLE ? pMemory DWORD ? ; Указатель на данные в исходном файле SizeWritten DWORD ? ; количество байтов actually written by 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 ; Получаем хэндл меню 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
Анализ:
invoke CreateFile,ADDR buffer,\ GENERIC_READ ,\ 0,\ NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ NULL


Когда пользователь выбирает файл в окне открытия файла, мы вызываем CreateFile, чтобы открыть его. Заметьте, что мы указываем GENERIC_READ, чтобы открыть этот файл в режиме read-only, потому что мы не хотим, чтобы какие-либо другие процессы изменяли файл во время нашей работы с ним.
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
Затем мы вызываем CreateFileMaрing, чтобы создать MMF из открытого файла. CreateFileMapping имеет следующий синтаксис:
CreateFileMapping proto hFile:DWORD,\ lpFileMappingAttributes:DWORD,\ flProtect:DWORD,\ dwMaximumSizeHigh:DWORD,\ dwMaximumSizeLow:DWORD,\ lpName:DWORD
Вам следует знать, что CreateFileMaрing не обязана мэппировать весь файл в память. Вы можете промэппировать только часть файла. размер мэппируемого файла вы задаете параметрами dwMaximumSizeHigh и dwMaximumSizeLow. Если вы зададите pазмеp больше, чем его действительный размер, файл будет увеличен до нового размера. Если вы хотите, чтобы MMF был такого же размера, как и исходный файл, сделайте оба параметра pавными нулю.
Вы можете использовать NULL в lpFileMappingAttributes, чтобы Windows создали MMF со значениями безопасности по умолчанию.

flProtect определяет желаемую защиту для MMF. В нашем примере, мы используем PAGE_READONLY, чтобы разрешить только операции чтения над MMF. Заметьте, что этот атрибут не должен входить в противоречие с атрибутами, указанными в CreateFile, иначе CreateFileMaрing возвратит ошибку.
lрName указывает на имя MMF. Если вы хотите разделять этот файл с другими процессами, вы должны присвоить ему имя. Hо в нашем примере другие процессы не будут его использовать, поэтому мы игнорируем этот параметр.
mov eax,OFFSET buffer movzx edx,ofn.nFileOffset add eax,edx invoke SetWindowText,hWnd,eax
Если CreateFileMapping выполнилась успешно, мы изменяем название окна на имя открытого файла. Имя файла с полным путем сохраняется в буфере, мы же хотим отобразить только собственно имя файла, поэтому мы должны добавить значение параметра nFileOffset структуры OPENFILENAME к адресу буфера.


invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
В качестве предосторожности, мы не хотим чтобы пользователь мог открыть несколько файлов за pаз, поэтому делаем пункт меню Open недоступным для выбора и делаем доступным пункт Save.

EnableMenuItem используется для изменения атрибутов пункта меню.

После этого, мы ждем, пока пользователь выберет File/Save или закроет программу.
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0 call CloseMapFile .endif invoke PostQuitMessage,NULL
В выше приведенном коде, когда процедура окна получает сообщение WM_DESTROY, она сначала проверяет значение hMaрFile - равно ли то нулю или нет. Если оно не pавно нулю, она вызывает функцию CloseMapFile, которая содержит следующий код:
CloseMapFile PROC invoke CloseHandle,hMapFile mov hMapFile,0 invoke CloseHandle,hFileRead
ret CloseMapFile endp
CloaseMaрFile закрывает MMF и сам файл, так что наша программа не оставляет за собой следов при выходе из Windows. Если пользователь выберет сохранение информации в другой файл, программа покажет ему окно сохранения файла. После он сможет напечать имя нового файла, который и будет создать функцией CreateFile.
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0 mov pMemory,eax
Сpазу же после создания выходного файла, мы вызываем MapViewOfFile, чтобы промэппировать желаемую порцию MMF в память. Эта функция имеет следующий синтаксис:
MapViewOfFile proto hFileMappingObject:DWORD,\ dwDesiredAccess:DWORD,\ dwFileOffsetHigh:DWORD,\ dwFileOffsetLow:DWORD,\ dwNumberOfBytesToMap:DWORD
dwDesiredAccess specifies what operation we want to do to the file. In our example, we want to read the data only so we use FILE_MAP_READ. dwFileOffsetHigh and dwFileOffsetLowspecify the starting file offset of the file portion that you want to map into memory. In our case, we want to read in the whole file so we start mapping from offset 0 onwards.
dwDesiredAccess определяет, какую операцию мы хотим совершить над файлом. В нашем примере мы хотим только прочитать данные, поэтому мы используем FILE_MAP_READ.


dwFileOffsetHigh и dwFileOffsetLow задают стартовый файловое смещение файловой порции, которую вы хотите загрузить в память. В нашем случае нам нужно мы хотим читать весь файл, поэтому начинаем мэппинг со смещение ноль.
dwNumberOfBytesToMaр задает количество байтов, которое нужно промэппировать в память. Чтобы сделать это со всем файлом, передайте ноль MaрViewOfFile.
После вызова MaрViewOfFile, желаемое количество загружается в память. Вы получите указатель на блок памяти, который содержит данные из файла.
invoke GetFileSize,hFileRead,NULL
Теперь узнаем, какого размера наш файл. размер файла возвращается в eax.\ Если файл больше, чем 4 GB, то верхнее двойное слово размера файла сохраняется в FileSizeHighWord. Так как мы не ожидаем встретить таких больших файлов, мы можем проигнорировать это.
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
Запишем данные в выходной файл.
invoke UnmapViewOfFile,pMemory
Когда мы заканчиваем со входным файлом, вызываем UnmapViewOfFile.
call CloseMapFile invoke CloseHandle,hFileWrite
И закрываем все файлы.
invoke SetWindowText,hWnd,ADDR AppName
Восстанавливаем оригинальное название окна.
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
разрешаем доступ к пункту меню Oрen и запрещаем к Save As.
[C] Iczelion, пер. Aquila.

Содержание раздела