Отображение файла в память на WinAPI, а также вопрос на проверку знаний со звездочкой

19 октября 2015

Отображением файла в память мы с вами пользовались в заметках об OpenGL, посвященных работе с моделями и текстурами, но не рассматривали подробно, как именно это работает. И хотя я почти уверен, что многие читатели этого блога уже знакомы с отображением файлов в память, я все же посчитал необходимом написать соответствующие заметки для тех читателей, кому данный механизм не знаком. Сегодня мы рассмотрим, как все это работает под Windows.

Заведем такую структуру, хранящую хэндл файла, хэндл отображения, размер файла, а также указатель на участок памяти с отображением:

#include <windows.h>

struct FileMapping {
  HANDLE hFile;
  HANDLE hMapping;
  size_t fsize;
  unsigned char* dataPtr;
};

Рассмотрим чтение из файла с использованием отображения.

Открываем файл:

HANDLE hFile = CreateFile(fname, GENERIC_READ, 0, nullptr,
                          OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL, nullptr);
if(hFile == INVALID_HANDLE_VALUE) {
  std::cerr << "fileMappingCreate - CreateFile failed, fname = "
            << fname << std::endl;
  return nullptr;
}

С процедурой CreateFile мы уже знакомы по заметке Учимся работать с файлами через Windows API, поэтому двигаемся дальше.

Получаем размер файла:

DWORD dwFileSize = GetFileSize(hFile, nullptr);
if(dwFileSize == INVALID_FILE_SIZE) {
  std::cerr << "fileMappingCreate - GetFileSize failed, fname = "
            << fname << std::endl;
  CloseHandle(hFile);
  return nullptr;
}

Вторым аргументом процедура GetFileSize получает указатель на DWORD для записи старшей части размера файла. Передав в качестве этого аргумента nullptr, мы ограничили размер файла, который может вернутся. Если размер файла будет 4 Гб или больше, процедура вернет ошибку.

Создаем отображение:

HANDLE hMapping = CreateFileMapping(hFile, nullptr, PAGE_READONLY,
                                    0, 0, nullptr);
if(hMapping == nullptr) {
  std::cerr << "fileMappingCreate - CreateFileMapping failed, fname = "
            << fname << std::endl;
  CloseHandle(hFile);
  return nullptr;
}

Заметьте, что hMapping проверяется на равенство nullptr. Это не баг — согласно MSDN, в случае ошибки CreateFileMapping действительно возвращает пустой указатель, а не INVALID_HANDLE_VALUE, как можно было бы ожидать. Такая неконсистентность, к сожалению, встречается время от времени в WinAPI. Мы уже встречались с этой проблемой, изучая работу с реестром.

Наконец, получаем указатель на участок памяти с отображением, используя процедуру MapViewOfFile:

unsigned char* dataPtr = (unsigned char*)MapViewOfFile(hMapping,
                                                       FILE_MAP_READ,
                                                       0,
                                                       0,
                                                       dwFileSize);
if(dataPtr == nullptr) {
  std::cerr << "fileMappingCreate - MapViewOfFile failed, fname = "
            << fname << std::endl;
  CloseHandle(hMapping);
  CloseHandle(hFile);
  return nullptr;
}

Затем заполняем структуру FileMapping и возвращаем указатель на нее в качестве результата:

FileMapping* mapping = (FileMapping*)malloc(sizeof(FileMapping));
if(mapping == nullptr) {
  std::cerr << "fileMappingCreate - malloc failed, fname = "
            << fname << std::endl;
  UnmapViewOfFile(dataPtr);
  CloseHandle(hMapping);
  CloseHandle(hFile);
  return nullptr;
}

mapping->hFile = hFile;
mapping->hMapping = hMapping;
mapping->dataPtr = dataPtr;
mapping->fsize = (size_t)dwFileSize;

return mapping;

Теперь читая mapping->fsize байт памяти по адресу mapping->dataPtr можно получить содержимое файла.

Когда отображение становится ненужным, не забываем его закрыть:

UnmapViewOfFile(mapping->dataPtr);
CloseHandle(mapping->hMapping);
CloseHandle(mapping->hFile);
free(mapping);

Как видите, все предельно просто. Исходники к этой заметке вы найдете здесь.

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

Задание со звездочкой для желающих — ответьте на следующий вопрос. Можно ли, работая с отображением файла в память, менять его размер, например, делать append или обрезать файл посередине, и если можно, то как? Если честно, я сам так с лету не готов ответить на этот вопрос, потому что такой задачи передо мной не вставало. Так что, с нетерпением жду ваших вариантов ответа в комментариях!

Дополнение: Пример отображения файла в память под Linux и MacOS

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.