]>Отображаемые в память файлы

Отображаемые в память файлы

Аватар пользователя mabu

Операции с файлами — это то, что рано или поздно приходится делать практически во всех программах, и всегда это вызывает массу вопросов. Должно ли приложение просто открыть файл, прочитать данные и закрыть его, или открыть, прочитать фрагмент в буфер и перезаписать его в другую часть файла? В Windows многие из этих проблем решаются очень изящно: с помощью отображённых в память файлов.

Отображение файла в память — это соединение содержания файла с частью виртуального адресного пространства процесса. К отображённому файлу обращаются так, будто это данные в оперативной памяти, а не через операции файлового ввода‐вывода.

Где примененяются проецируемые файлы?

На уровне ядра Windows это работает так. Когда процесс пытается считать или записать данные из отображённого файла, процессор уведомляет, что памяти не существует, ставит процесс на паузу и выкидывает ошибку страницы (page fault). Ядро перехватывает ошибку и помещает в память актуальные данные, чтобы приложение могло их считать. Затем процесс снимается с паузы и находит в соответствующем месте волшебным образом появившиеся данные.

  1. Функции
    1. CreateFile
    2. CreateFileMapping
    3. OpenFileMapping
    4. MapViewOfFile
    5. UnmapViewOfFile
    6. CloseHandle
  2. Примеры
    1. Демонстрационная программа «Адресная книга»
    2. Межпроцессное взаимодействие

Функции

Чтобы отобразить файл в память и использовать его, нам понядобятся следующие функции:

CreateFile

В самом начале необходимо открыть файл функцией CreateFile. Вот её объявление из заголовочных файлов:

Код FreeBASIC
Declare Function CreateFile Alias "CreateFileW"( _
&t;&t;&t;ByVal lpFileName As LPCWSTR, _
&t;&t;&t;ByVal dwDesiredAccess As DWORD, _
&t;&t;&t;ByVal dwShareMode As DWORD, _
&t;&t;&t;ByVal lpSecurityAttributes As LPSECURITY_ATTRIBUTES, _
&t;&t;&t;ByVal dwCreationDisposition As DWORD, _
&t;&t;&t;ByVal dwFlagsAndAttributes As DWORD, _
&t;&t;&t;ByVal hTemplateFile As HANDLE) _
As HANDLE

Параметры.

lpFileName
Указатель на строку с именем файла, который необходимо открыть.
dwDesiredAccess
Тип доступа к объекту, например, GENERIC_READ, GENERIC_WRITE или и то, и другое.
dwShareMode
Режим совместного доступа к файлу (из других процессов). Может быть 0, FILE_SHARE_READ, FILE_SHARE_WRITE или их комбинацией.
lpSecurityAttributes
Указатель на структуру SECURITY_ATTRIBUTES, устанавливающую, может ли возвращённый дескриптор быть унаследован дочерними процессами. В нашем случае это не важно, поэтому устанавливаем в NULL.
dwCreationDisposition
Выполняемые действия с файлами. CREATE_NEW — создать файл, если таковой уже существует, выйдет ошибка. CREATE_ALWAYS — всегда создавать или перезаписывать файл с нуля. OPEN_EXISTING — открыть существующий файл, если таковой не существует, вылезет ошибка. OPEN_ALWAYS — открыть существующий файл или создать. TRUNCATE_EXISTING — перезаписать с нуля существующий файл, или вывести ошибку, если файл не существует.
dwFlagsAndAttributes
Атрибуты файла, здесь необходим только флаг FILE_ATTRIBUTE_NORMAL.
hTemplateFile
Указатель на файл‐шаблон. Не используется и должен быть установлен в NULL.

Если функция завершается успешно, возвращаемое значение — открытый дескриптор заданного файла.

Если функция завершается с ошибкой, возвращаемое значение — INVALID_HANDLE_VALUE.

CreateFileMapping

После открытия файла необходимо создать отображённый в памяти файл функцией CreateFileMapping.

Код FreeBASIC
Declare Function CreateFileMapping Alias "CreateFileMappingW"( _
&t;&t;&t;ByVal hFile As HANDLE, _
&t;&t;&t;ByVal lpFileMappingAttributes As LPSECURITY_ATTRIBUTES, _
&t;&t;&t;ByVal flProtect As DWORD, _
&t;&t;&t;ByVal dwMaximumSizeHigh As DWORD, _
&t;&t;&t;ByVal dwMaximumSizeLow As DWORD, _
&t;&t;&t;ByVal lpName As LPCWSTR) _
As HANDLE

Параметры.

hFile
Дескриптор файла, из которого создаётся отображённый файл. Можно также передать INVALID_HANDLE_VALUE, тогда отображённый файл создаётся из файла подкачки и пригоден для межпроцессного взаимодействия; в этом случае необходимо установить параметры dwMaximumSizeHigh и dwMaximumSizeLow.
lpAttributes
Указатель на структуру SECURITY_ATTRIBUTES, если требуется, чтобы дескриптор отображённого файла мог быть унаследован, иначе оставляем в NULL.
flProtect
Вид защиты, необходимый для файла, когда он отображается. Нас интересует только пара значений: PAGE_READONLY для чтения и PAGE_READWRITE для чтения и записи. Этот параметр должен быть совместим с параметром dwDesiredAccess функции CreateFile.
dwMaximumSizeHigh
Старшее двойное слово максимального размера объекта «отображённый файл».
dwMaximumSizeLow
Младшее двойное слово (DWORD) максимального размера объекта «отображённый файл». Если этот параметр и dwMaximumSizeHigh равняются нулю, максимальный размер объекта «отображённый файл» равен текущему размеру файла, идентифицированного hFile.

Если dwMaximumSizeHigh и dwMaximumSizeHigh установлены в 0, то попытка отобразить в память файл с нулевой длиной завершается ошибкой с кодом ERROR_FILE_INVALID, в этом случае приложения должны обнаруживать файлы с нулевой длиной и отклонять их.

lpName
Указатель на строку с именем объекта «отображённый файл». Необходим для межпроцессного взаимодействия, в остальных случаях устанавливается в NULL.

Если функция завершается успешно, то возвращается дескриптор, если возникла ошибка, то возвращается NULL.

OpenFileMapping

Функция OpenFileMapping открывает уже существующий именованный объект «отображённый файл».

Код FreeBASIC
Declare Function OpenFileMapping Alias "OpenFileMappingW"( _
&t;&t;&t;ByVal dwDesiredAccess As DWORD, _
&t;&t;&t;ByVal bInheritHandle As WINBOOL, _
&t;&t;&t;ByVal lpName As LPCWSTR) _
As HANDLE

Параметры

dwDesiredAccess
Тип доступа к объекту, например, GENERIC_READ, GENERIC_WRITE или и то, и другое.
bInheritHandle
Если этот параметр истина (WinTrue), то процесс наследует дескриптор.
lpName
Указатель на строку с именем открываемого файла.

Если функция завершается успешно, возвращаемое значение — дескриптор заданного объекта «отображённый файл». Если функция завершается ошибкой, возвращаемое значение — NULL.

MapViewOfFile

Функция MapViewOfFile отображает данные файла в адресное пространство процесса.

Код FreeBASIC
Declare Function MapViewOfFile( _
&t;&t;&t;ByVal hFileMappingObject As HANDLE, _
&t;&t;&t;ByVal dwDesiredAccess As DWORD, _
&t;&t;&t;ByVal dwFileOffsetHigh As DWORD, _
&t;&t;&t;ByVal dwFileOffsetLow As DWORD, _
&t;&t;&t;ByVal dwNumberOfBytesToMap As SIZE_T_) _
As LPVOID

hFileMappingObject
Дескриптор открытого объекта «отображённый файл».
dwDesiredAccess
Режим доступа. Нас интересуют только FILE_MAP_READ для чтения данных, FILE_MAP_WRITE для записи данных и FILE_MAP_ALL_ACCESS для чтения и записи. Не должен быть в противоречии с другими режимами.
dwFileOffsetHigh
Старшее двойное слово (DWORD) смещения файла, где начинается отображение.
dwFileOffsetLow
Младшее двойное слово (DWORD) смещения файла, где начинается отображение. Значение этого начального адреса должно быть кратным 64 Кбайт. Чтобы начало отображаемого участка совпадало с началом файла, dwFileOffsetHigh и dwFileOffsetLow следует задать равными 0.
dwNumberOfBytesToMap
Число отображаемых байтов файла. Если этот параметр равняется нулю, отображается весь файл.

При успехе функция возвращает указатель, по которому можно считывать и записывать данные. В случае ошибки функция возвращает NULL.

Если размер файла больше, чем адресное пространство процесса, то одновременно можно отобразить только небольшую часть файла.

UnmapViewOfFile

Функция UnmapViewOfFile отменяет отображение файла из адресного пространства процесса.

Код FreeBASIC
Declare Function UnmapViewOfFile(ByVal lpBaseAddress As LPCVOID)As WINBOOL

Параметры.

lpBaseAddress
Указатель на адрес отображаемого представления файла, отображение которого должно быть прекращено.

Если функция завершается успешно, то возвращается не ноль. В случае ошибки возвращается ноль.

Эту функцию мы будем использовать для выгрузки данных файла из памяти процесса (на самом деле данные выгрузятся тогда, когда будут отменены все отображения файла и закрыты отображаемые файлы).

CloseHandle

Функция CloseHandle закрывает дескриптор открытого объекта.

Код FreeBASIC
Declare Function CloseHandle(ByVal hObject As HANDLE)As WINBOOL

Параметры.

hObject
Дескриптор открытого объекта.

Если функция завершается успешно, величина возвращаемого значения — не ноль. Если функция завершается с ошибкой, величина возвращаемого значения — ноль.

Эту функцию мы будем использовать для закрытия объекта «отображённый файл» и самого файла.

Примеры

Для примера рассмотрим использование отображённых в память файлов для создания демонстрационной программы «Адресная книга» и межпроцессного взаимодействия.

Демонстрационная программа «Адресная книга»

Задача. Необходимо написать программу «адресная книга». Программа должна хранить список из 50 адресов, уметь добавлять контакты, редактировать, удалять и выводить весь список. Для контактов можно использовать имя, фамилию, электронный адрес и телефон.

Заголовочный файл «AddressBook.bi».

Код FreeBASIC
#ifndef unicode
#define unicode
#endif

#include once "windows.bi"

' Максимальное количество символов для строки
Const MaxBytesCount As Integer = 265

' Имя файла для отображения
Const FileName = "Адресная книга.dat"

' Длина массива структур
Const ArrayLength As Integer = 10

Const Menu = !"1.\tПоказать список контактов.\r\n2.\tДобавить контакт.\r\n3.\tРедактировать контакт.\r\n4.\tУдалить контакт.\r\n0.\tВыход.\r\n"
Const Hello = !"Программа «Адресная книга»\r\nВыбери действие.\r\n\r\n"

Const ContactsCountString = "Количество контактов"

Type Contact
&t;Dim Name As WString * (MaxBytesCount + 1)
&t;Dim Surname As WString * (MaxBytesCount + 1)
&t;Dim Email As WString * (MaxBytesCount + 1)
&t;Dim Phone As WString * (MaxBytesCount + 1)
End Type

' Чтение данных с консоли
Declare Function ReadLine(ByRef s As WString)As Integer

Файл «AddressBook.bas».

Код FreeBASIC
#include once "AddressBook.bi"

Function ReadLine(ByRef s As WString)As Integer
&t;Dim InHandle As HANDLE = GetStdHandle(STD_INPUT_HANDLE)
&t;Dim SymbolsCount As DWORD = Any
&t;If ReadConsole(InHandle, @s, MaxBytesCount, @SymbolsCount, 0) = 0 Then
&t;&t;ReadFile(InHandle, @s, MaxBytesCount, @SymbolsCount, 0)
&t;&t;SymbolsCount \= SizeOf(WString)
&t;End If
&t;s[SymbolsCount - 2] = 0
&t;Return SymbolsCount
End Function


Print Hello

Dim hFile As HANDLE = CreateFile(@FileName, GENERIC_READ + GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)

If hFile = INVALID_HANDLE_VALUE Then
&t;' Ошибка
&t;Print "Ошибка открытия файла"
&t;End(1)
End If

' Отобразить файл
Dim hFileMap As Handle = CreateFileMapping(hFile, 0, PAGE_READWRITE, 0, SizeOf(Integer) + ArrayLength * SizeOf(Contact), 0)
If hFileMap = 0 Then
&t;' Ошибка
&t;Print "Ошибка отображения файла"
Else
&t;' Массив структур для записи в файл
&t;Dim b As Byte Ptr = CPtr(Byte Ptr, MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0))
&t;If b = 0 Then
&t;&t;' Ошибка
&t;&t;Print "Ошибка проецирования файла"
&t;Else
&t;&t;' Количество контактов
&t;&t;Dim ContactsCount As Integer Ptr = CPtr(Integer Ptr, b)
&t;&t;' Список контактов
&t;&t;Dim Contacts As Contact Ptr = CPtr(Contact Ptr, b + SizeOf(Integer))

&t;&t;Do
&t;&t;&t;Print Menu
&t;&t;&t;' Дождаться ввода действия
&t;&t;&t;Dim Value As WString * (MaxBytesCount + 1) = Any
&t;&t;&t;Dim ValueLength As Integer = ReadLine(Value)
&t;&t;&t;Print Value

&t;&t;&t;Select Case CInt(Value)
&t;&t;&t;&t;Case 0
&t;&t;&t;&t;&t;Exit Do
&t;&t;&t;&t;Case 1
&t;&t;&t;&t;&t;' Список контактов
&t;&t;&t;&t;&t;Print ContactsCountString, *ContactsCount
&t;&t;&t;&t;&t;For i As Integer = 0 To ArrayLength - 1
&t;&t;&t;&t;&t;&t;If Len(Contacts[i].Email) > 0 Then
&t;&t;&t;&t;&t;&t;&t;Print "Номер", i
&t;&t;&t;&t;&t;&t;&t;Print "Почта", Contacts[i].Email
&t;&t;&t;&t;&t;&t;&t;Print "Телефон", Contacts[i].Phone
&t;&t;&t;&t;&t;&t;&t;Print "Имя", Contacts[i].Name
&t;&t;&t;&t;&t;&t;&t;Print "Фамилия", Contacts[i].Surname
&t;&t;&t;&t;&t;&t;&t;Print
&t;&t;&t;&t;&t;&t;End If
&t;&t;&t;&t;&t;Next
&t;&t;&t;&t;Case 2
&t;&t;&t;&t;&t;' Добавить
&t;&t;&t;&t;&t;If *ContactsCount > ArrayLength Then
&t;&t;&t;&t;&t;&t;Print "Адресная книга переполнена, необходимо удалить один из контактов"
&t;&t;&t;&t;&t;Else
&t;&t;&t;&t;&t;&t;*ContactsCount += 1
&t;&t;&t;&t;&t;&t;' Найти пустой контакт
&t;&t;&t;&t;&t;&t;Dim i As Integer = 0
&t;&t;&t;&t;&t;&t;Do Until Len(Contacts[i].Email) = 0
&t;&t;&t;&t;&t;&t;&t;i += 1
&t;&t;&t;&t;&t;&t;Loop
&t;&t;&t;&t;&t;&t;' Нашли
&t;&t;&t;&t;&t;&t;Print "Введи адрес электропочты"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[i].Email)
&t;&t;&t;&t;&t;&t;Print "Введи телефон"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[i].Phone)
&t;&t;&t;&t;&t;&t;Print "Введи имя"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[i].Name)
&t;&t;&t;&t;&t;&t;Print "Введи фамилию"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[i].Surname)
&t;&t;&t;&t;&t;&t;Print "Контакт создан под номером", i
&t;&t;&t;&t;&t;End If
&t;&t;&t;&t;Case 3
&t;&t;&t;&t;&t;' Редактировать
&t;&t;&t;&t;&t;Print "Введи номер контакта"
&t;&t;&t;&t;&t;ValueLength = ReadLine(Value)
&t;&t;&t;&t;&t;Dim Number As UInteger = CUInt(Value)
&t;&t;&t;&t;&t;If Number >= ArrayLength Then
&t;&t;&t;&t;&t;&t;Print "Номер контакта слишком большой"
&t;&t;&t;&t;&t;Else
&t;&t;&t;&t;&t;&t;Print "Введи адрес электропочты"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[Number].Email)
&t;&t;&t;&t;&t;&t;Print "Введи телефон"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[Number].Phone)
&t;&t;&t;&t;&t;&t;Print "Введи имя"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[Number].Name)
&t;&t;&t;&t;&t;&t;Print "Введи фамилию"
&t;&t;&t;&t;&t;&t;ValueLength = ReadLine(Contacts[Number].Surname)
&t;&t;&t;&t;&t;&t;Print "Контакт отредактирован"
&t;&t;&t;&t;&t;End If
&t;&t;&t;&t;Case 4
&t;&t;&t;&t;&t;' Удалить
&t;&t;&t;&t;&t;Print "Введи номер контакта"
&t;&t;&t;&t;&t;ValueLength = ReadLine(Value)
&t;&t;&t;&t;&t;Dim Number As UInteger = CUInt(Value)
&t;&t;&t;&t;&t;If Number >= ArrayLength Then
&t;&t;&t;&t;&t;&t;Print "Номер контакта слишком большой"
&t;&t;&t;&t;&t;Else
&t;&t;&t;&t;&t;&t;If *ContactsCount <> 0 Then
&t;&t;&t;&t;&t;&t;&t;*ContactsCount -= 1
&t;&t;&t;&t;&t;&t;End If
&t;&t;&t;&t;&t;&t;Contacts[Number].Email[0] = 0
&t;&t;&t;&t;&t;&t;Print "Контакт удалён"
&t;&t;&t;&t;&t;End If
&t;&t;&t;End Select
&t;&t;&t;Print
&t;&t;Loop

&t;&t;' Закрыть
&t;&t;UnmapViewOfFile(b)
&t;End If
&t;CloseHandle(hFileMap)
End If
' Закрыть
CloseHandle(hFile)

Межпроцессное взаимодействие

Первый процесс создаёт отображённый файл в памяти и записывает в него строку.

Файл «proc1.bas».

Код FreeBASIC
#define unicode
#include once "windows.bi"

Const MaxBytesCount As Integer = 256
Const FileName = "MyFileMappingObject"
Const MessageText = "Сообщение из первого процесса"

' Открыть отображённый файл для чтения‐записи в файле подкачки
Dim hMapFile As Handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, SizeOf(WString) * (MaxBytesCount + 1), @FileName)

If hMapFile = 0 OrElse hMapFile = INVALID_HANDLE_VALUE Then
&t;Print "Не могу создать отображённый файл в памяти"
&t;End(1)
End If

' Указатель для записи
Dim b As WString Ptr = CPtr(WString Ptr, MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(WString) * (MaxBytesCount + 1)))
If b = 0 Then
&t;Print "Не могу отобразить файл"
&t;End(2)
End If

' Запись текста, доступного другому процессу, по полученному указателю
*b = MessageText

' Пауза, чтобы не закрыть объекты раньше времени
Sleep()

UnmapViewOfFile(b)
CloseHandle(hMapFile)

Второй процесс открывает уже созданный отображённый файл и читает из него строку.

Файл «proc2.bas».

Код FreeBASIC
#define unicode
#include once "windows.bi"

Const MaxBytesCount As Integer = 256
Const FileName = "MyFileMappingObject"

Dim hMapFile As Handle = OpenFileMapping(FILE_MAP_ALL_ACCESS, False, @FileName)

If hMapFile = 0 OrElse hMapFile = INVALID_HANDLE_VALUE Then
&t;Print "Не могу открыть отображённый файл"
&t;End(1)
End If

Dim b As WString Ptr = CPtr(WString Ptr, MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(WString) * (MaxBytesCount + 1)))
If b = 0 Then
&t;Print "Не могу отобразить файл"
&t;End(2)
End If

MessageBox(NULL, b, "Process2", MB_OK)

UnmapViewOfFile(b)
CloseHandle(hMapFile)

Поделись ссылочкой в социальных сетях