платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 13 - Memory Mapped файлы

Предыдущая тема Следующая тема Перейти вниз

платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 13 - Memory Mapped файлы

Сообщение  electrik в Пн Июн 29, 2009 6:22 pm

Win32 API. Урок 13. Memory Mapped файлы

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

Теория:

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

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

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

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

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


Пример:

Программа, листинг которой приведен ниже, позволит вам открыть файл с помощью окна открытия файла. Она откроет файл, используя FM, если это удастся, заголовок окна изменится на имя открытого файла. Вы можете сохранить файл под другим именем, выбрав пункт меню File/Save. Программа скопирует все содержимое открытого файла в новый файл. Учтите, что вы не должны вызывать GlobalAlloc для резервирования блока памяти в этой программе.

файл mmf.bas

Код:

' Memory Mapped файлы
#define WIN_INCLUDEALL
#include "windows.bi"
#include "l13_mfm.bi"
declare function WinMain ( byval hInst as HINSTANCE, _
byval hPrevInst as HINSTANCE, _
  byval szCmdLine as LPSTR, _
  byval iCmdShow as integer ) as integer
declare sub CloseMapFile()


' начало программы
dim shared hModule as HINSTANCE ' хэндл модуля
dim shared hMapFile as HANDLE = 0 ' Указатель на MMF
dim shared pMemory  as LPVOID ' Указатель на данные в исходном файле
dim shared hMenu as HANDLE ' хэндл меню
dim shared hFileRead as HANDLE ' Хэндл источника
dim shared hFileWrite as HANDLE ' Хэндл выходного файла
dim shared SizeFile as uInteger ' размер файла
dim shared SizeWritten as uInteger ' количество действительно записанных байт
dim shared ofn as OPENFILENAME ' структура OPENFILENAME
dim shared as zstring * MAXSIZE buffer ' буфер для имени файла
hModule =  GetModuleHandle( NULL )
end WinMain(hModule,NULL,GetCommandLine() , SW_SHOWNORMAL ) ' вызвать основную функцию
' здесь заканчивается программа

' процедура окна
function  WndProc _
(byval hwnd as HWND, _ ' хэндл окна
byval uMsg as UINT, _ ' сообщение
byval wParam as WPARAM, _ ' дополнительный  параметр сообщений
byval lParam as LPARAM) as LRESULT ' дополнительный параметр сообщений

function = 0
select case uMsg  'начинаем обработку сообщений
case WM_DESTROY ' если пользователь закрывает окно
if hMapFile <> 0 then ' если map файл открыт
    CloseMapFile() ' закроем map файл
end if
PostQuitMessage(0) ' выходим из программы
exit function
case WM_CREATE
hMenu = GetMenu(hWnd) ' Получаем хэндл меню

' заполняем структуру OPENFILENAME
ofn.lStructSize = SIZEOF(OPENFILENAME) ' размер структуры OPENFILENAME
ofn.hWndOwner = hWnd ' хэндл родительского окна
ofn.hInstance = hModule ' хэндл модуля

' настроим фильтр файлов, чтоб в типах файлов отображал только нужные нам расширения
ofn.lpstrFilter = _
@!"Text files, *.txt\0*.txt\0Bas files, *.bas\0*.bas\0Bi files, *.bi\0*.bi\0All files, *.*\0*.*\0\0"
ofn.lpstrFile = @buffer ' указатель набуфер содержащий имя файла
ofn.nMaxFile = MAXSIZE ' размер буфера
case WM_COMMAND ' если получили сообщение WM_COMMAND
if lParam = 0 then ' если lParam = 0, значит идет сообщение от меню
select case loword(wParam)  ' начинаем обрабатывать элементы меню
case IDM_OPEN ' если в меню выбрали open

' заполним некоторые флаги в структуре OPENFILENAME
ofn.Flags = OFN_FILEMUSTEXIST or _
OFN_PATHMUSTEXIST or OFN_LONGNAMES or _
OFN_EXPLORER or OFN_HIDEREADONLY
if GetOpenFileName(@ofn) = true then ' если в диалоге открытия файла выбрали файл
hFileRead = CreateFile _ ' создадим файл
(@buffer, _ ' указатель на буфер содержащий имя файла
GENERIC_READ, _ ' открыть для чтения
    0, _ ' доступ другим процессам
NULL, _ ' атрибуты безопасности
OPEN_EXISTING, _ ' открыть существующий
FILE_ATTRIBUTE_ARCHIVE, _ ' атрибуты файла. установить атрибут архивный
NULL)
hMapFile = CreateFileMapping _ ' создадим map файл
(hFileRead, _ хэндл читаемого файла
NULL, _
PAGE_READONLY, _ ' только чтение
0,0,NULL)
SetWindowText _ ' установим в окно текст
(hWnd, _ ' хэндл окна
(@buffer + ofn.nFileOffset)) ' смещение только имени файла
EnableMenuItem _ ' изменим пункт меню
(hMenu, _ ' хэндл меню
IDM_OPEN, _ ' идентификатор меню
MF_GRAYED) ' сделаем недоступным
EnableMenuItem _ ' изменим пункт меню
(hMenu, _ ' хэндл меню
IDM_SAVE, _ ' идентификатор меню
MF_ENABLED) ' сделаем доступным
end if
case IDM_SAVE ' если в меню выбрали save
' заполним некоторые элементы структуры OPENFILENAME
ofn.Flags = OFN_LONGNAMES or _
OFN_EXPLORER or OFN_HIDEREADONLY
if GetSaveFileName(@ofn) = TRUE then ' если в диалоге сохранения файла выбрали файл
hFileWrite = CreateFile _ ' создадим файл
(@buffer, _ ' указатель на буфер содержащий имя файла
GENERIC_READ or GENERIC_WRITE, _ ' открыть для чтения и записи
FILE_SHARE_READ or FILE_SHARE_WRITE, _ ' разрешить другим процессам читать/писать файл
NULL, _ ' атрибуты безопасности
CREATE_NEW, _ ' создать новый
FILE_ATTRIBUTE_ARCHIVE, _ ' атрибуты файла. установить атрибут архивный
NULL)
pMemory = MapViewOfFile _ ' промэппируем mmf
(hMapFile, _ ' хэндл map файла
FILE_MAP_READ, _
0,0,0)

SizeFile  = GetFileSize _ получим размер файла
(hFileRead, _ ' хэндл исходного файла
NULL)
WriteFile _ ' запишем файл
(hFileWrite, _ ' хэндл записываемого файла
pMemory, _ ' указатель на участок памяти
SizeFile, _ ' размер записываемых данных в байтах
@SizeWritten, _ ' указатель на переменную число действительно записанных байт
NULL)
UnmapViewOfFile (pMemory)
CloseMapFile() ' закроем map файл
CloseHandle(hFileWrite) ' закроем записываемый файл
SetWindowText _ ' установим в окно текст
(hWnd, _ ' хэндл окна
@AppName) ' указатель на имя программы
EnableMenuItem _ ' изменим пункт меню
(hMenu, _ ' хэндл меню
IDM_OPEN, _ ' идентификатор меню
MF_ENABLED) ' сделаем доступным
EnableMenuItem _ ' изменим пункт меню
(hMenu, _ ' хэндл меню
IDM_SAVE, _ ' идентификатор меню
MF_GRAYED) ' сделаем недоступным
end if

    case IDM_EXIT
        DestroyWindow(hWnd) ' разрушим окно и выдем из программы
        end select
    end if
end select
function = DefWindowProc(hWnd,uMsg,wParam,lParam) ' Дефаултная функция обработки окна
end function


'функция WinMain
function WinMain _
(byval hInst as HINSTANCE, _ ' хэндл программы
byval hPrevInst as HINSTANCE, _ 'в win32 всегда 0
byval szCmdLine as LPSTR, _  'указатель на командную строку
byval iCmdShow as integer ) as integer  ' состояние окна при первом появлении

dim wc as WNDCLASSEX ' структура параметров окна
dim wMsg as MSG  ' структура сообщений
dim hWnd as HWND ' хэндл окна

 
 ' заполняем структуру wc
wc.cbSize = SIZEOF( WNDCLASSEX )  ' размер структуры WNDCLASSEX
wc.style = CS_HREDRAW or CS_VREDRAW  ' Стиль окна
wc.lpfnWndProc = @WndProc ' Адрес процедуры окна WndProc
wc.cbClsExtra = NULL  ' резервирование  дополнительных байт за концом структуры
wc.cbWndExtra = NULL
wc.hInstance = hInst  ' хэндл модуля
wc.hbrBackground = cast(HGDIOBJ, COLOR_WINDOW+1) ' Цвет фона
wc.lpszMenuName = @"FirstMenu" ' хэндл меню
wc.lpszClassName = @ClassName ' имя класса окна
wc.hIcon = LoadIcon( NULL,IDI_APPLICATION ) ' Хэндл иконки
wc.hIconSm = wc.hIcon 'Хэндл маленькой иконки
wc.hCursor = LoadCursor( NULL,IDC_ARROW) ' Хэндл курсора

' регистрация нашего класса окна
if(RegisterClassEx(@wc) = FALSE) then
MessageBox(0,"Не могу зарегистрировать класс окна","Ошибка",0)
end 1
end if

' Создадим окно
hwnd = CreateWindowEx _
(WS_EX_CLIENTEDGE, _ ' дополнительные стили
ClassName, _ ' строка с именем класса окна
AppName, _ ' строка с именем окна
WS_OVERLAPPEDWINDOW, _ ' стиль окна
CW_USEDEFAULT, _ ' X
CW_USEDEFAULT, _ ' Y
300, _ ' ширина окна
200, _ ' высота окна
NULL, _ ' хэндл родительского окна
NULL, _ ' хэндл меню
hInst, _ ' хэндл модуля
NULL) ' указатель на структуру данных

ShowWindow( hwnd,iCmdShow) ' отобразить наше окно на десктопе
UpdateWindow( hwnd) ' обновить клиентскую область

while( GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE )  'цикл сообщений
TranslateMessage( @wMsg )
DispatchMessage( @wMsg )
wend
function = wMsg.wParam
end function

sub CloseMapFile()
CloseHandle _ ' закроем хэндл
(hMapFile) ' map файла
hMapFile = 0 ' пишем в хэндл 0

CloseHandle _ ' закроем хэндл
(hFileRead) ' исходного файлла
end sub

файл mmf.bi

Код:

#define ClassName !"Win32FreeBasicFileMappingClass\0" ' класс окна
#define AppName !"Win32 FreeBasic File Mapping Example\0" ' имя программы
#define IDM_OPEN 32000
#define IDM_SAVE 32001
#define IDM_EXIT 32002
#define MAXSIZE 260

файл mmf.rc

Код:

#include "mmf.bi"
FirstMenu MENU
{
POPUP "&File"
{
MENUITEM "&Open",IDM_OPEN
MENUITEM "&Save",IDM_SAVE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
}


Последний раз редактировалось: electrik (Вт Май 23, 2017 5:45 pm), всего редактировалось 1 раз(а)

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Re: платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 13 - Memory Mapped файлы

Сообщение  electrik в Пн Июн 29, 2009 6:24 pm

Win32 API. Урок 13. Memory Mapped файлы - продолжение

Анализ:

hFileRead = CreateFile _
(@buffer, _
GENERIC_READ, _
0, _ ' доступ другим процессам
NULL, _ ' атрибуты безопасности
OPEN_EXISTING, _
FILE_ATTRIBUTE_ARCHIVE, _
NULL)

Когда пользователь выбирает файл в окне открытия файла, мы вызываем CreateFile, чтобы открыть его. Заметьте, что мы указываем GENERIC_READ, чтобы открыть этот файл в режиме read-only, потому что мы не хотим, чтобы какие-либо другие процессы изменяли файл во время нашей работы с ним.

hMapFile = CreateFileMapping _
(hFileRead, _
NULL, _
PAGE_READONLY, _
0,0,NULL)

Затем мы вызываем CreateFileMapping, чтобы создать MMF из открытого файла. CreateFileMapping имеет следующий синтаксис:

function CreateFileMapping _
(byval hFile as HANDLE, _
byval lpFileMappingAttributes as LPSECURITY_ATTRIBUTES, _
byval flProtect as DWORD, _
byval dwMaximumSizeHigh as DWORD, _
byval dwMaximumSizeLow as DWORD, _
byval lpName as LPCSTR) as HANDLE

Вам следует знать, что CreateFileMapping не обязана мэппировать весь файл в память. Вы можете промэппировать только часть файла. размер мэппируемого файла вы задаете параметрами dwMaximumSizeHigh и dwMaximumSizeLow. Если вы зададите размер больше, чем его действительный размер, файл будет увеличен до нового размера. Если вы хотите, чтобы MMF был такого же размера, как и исходный файл, сделайте оба параметра равными нулю.

Вы можете использовать NULL в lpFileMappingAttributes, чтобы Windows создали MMF со значениями безопасности по умолчанию.
flProtect определяет желаемую защиту для MMF. В нашем примере, мы используем PAGE_READONLY, чтобы разрешить только операции чтения над MMF. Заметьте, что
этот атрибут не должен входить в противоречие с атрибутами, указанными в CreateFile, иначе CreateFileMapping возвратит ошибку.

lpName указывает на имя MMF. Если вы хотите разделять этот файл с другими процессами, вы должны присвоить ему имя. Но в нашем примере другие процессы не будут его использовать, поэтому мы игнорируем этот параметр.

SetWindowText _
(hWnd, _
(@buffer + ofn.nFileOffset))

Если CreateFileMapping выполнилась успешно, мы изменяем название окна на имя открытого файла. Имя файла с полным путем сохраняется в буфере, мы же хотим отобразить только собственно имя файла, поэтому мы должны добавить значение параметра nFileOffset структуры OPENFILENAME к адресу буфера.

EnableMenuItem _
(hMenu, _
IDM_OPEN, _
MF_GRAYED)
EnableMenuItem _
(hMenu, _
IDM_SAVE, _
MF_ENABLED)

В качестве предосторожности, мы не хотим чтобы пользователь мог открыть несколько файлов за раз, поэтому делаем пункт меню Open недоступным для выбора и делаем доступным пункт Save.
EnableMenuItem используется для изменения атрибутов пункта меню.
После этого, мы ждем, пока пользователь выберет File/Save или закроет программу.

case WM_DESTROY ' если пользователь закрывает окно
if hMapFile <> 0 then ' если map файл открыт
CloseMapFile() ' закроем map файл
end if
PostQuitMessage(0) ' выходим из программы

В выше приведенном коде, когда процедура окна получает сообщение WM_DESTROY, она сначала проверяет значение hMapFile - равно ли нулю или нет. Если оно не равно нулю, она вызывает функцию CloseMapFile, которая содержит следующий код:

sub CloseMapFile()
CloseHandle _ ' закроем хэндл
(hMapFile) ' map файла
hMapFile = 0 ' пишем в хэндл 0

CloseHandle _ ' закроем хэндл
(hFileRead) ' исходного файлла
end sub

CloseMapFile закрывает MMF и сам файл, так что наша программа не оставляет за собой следов при выходе из Windows. Если пользователь выберет сохранение информации в другой файл, программа покажет ему окно сохранения файла. После он сможет напечатать имя нового файла, который и будет создан функцией CreateFile.

pMemory = MapViewOfFile _
(hMapFile, _
FILE_MAP_READ, _
0,0,0)

Сразу же после создания выходного файла, мы вызываем MapViewOfFile, чтобы промэппировать желаемую порцию MMF в память. Эта функция имеет следующий синтаксис:

function MapViewOfFile _
(byval hFileMappingObject as HANDLE, _
byval dwDesiredAccess as DWORD, _
byval dwFileOffsetHigh as DWORD, _
byval dwFileOffsetLow as DWORD, _
byval dwNumberOfBytesToMap as DWORD) as PVOID

dwDesiredAccess определяет, какую операцию мы хотим совершить над файлом. В нашем примере мы хотим только прочитать данные, поэтому мы используем FILE_MAP_READ.

dwFileOffsetHigh и dwFileOffsetLow задают стартовое файловое смещение файловой порции, которую вы хотите загрузить в память. В нашем случае мы хотим читать весь файл, поэтому начинаем мэппинг со смещение ноль.

dwNumberOfBytesToMap задает количество байтов, которое нужно промэппировать в память. Чтобы сделать это со всем файлом, передайте ноль MapViewOfFile.

После вызова MapViewOfFile, желаемое количество загружается в память. Вы получите указатель на блок памяти, который содержит данные из файла.

SizeFile = GetFileSize _
(hFileRead, _
NULL)

Теперь узнаем, какого размера наш файл. размер файла возвращается в SizeFile.\ Если файл больше, чем 4 GB, то верхнее двойное слово размера файла сохраняется в FileSizeHighWord. Так как мы не ожидаем встретить таких больших файлов, мы можем проигнорировать это.

WriteFile _
(hFileWrite, _
pMemory, _
SizeFile, _
@SizeWritten, _
NULL)

Запишем данные в выходной файл.

UnmapViewOfFile (pMemory)

Когда мы заканчиваем со входным файлом, вызываем UnmapViewOfFile.

CloseMapFile()
CloseHandle(hFileWrite)

И закрываем все файлы.

SetWindowText _ ' установим в окно текст
(hWnd, _
@AppName)

Восстанавливаем оригинальное название окна.

EnableMenuItem _
(hMenu, _
IDM_OPEN, _
MF_ENABLED)
EnableMenuItem _
(hMenu, _
IDM_SAVE, _
MF_GRAYED)

разрешаем доступ к пункту меню Oрen и запрещаем к Save As.

[C] Iczelion, пер. Aquila.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Предыдущая тема Следующая тема Вернуться к началу


 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения