Асинхронный файловый ввод/вывод в операционной системе WINDOWS, страница 15

Если работа с файлом, отображенным на память, завершена, необходимо обратиться к вызову UnmapViewOfFile. После этого следует дважды обратиться к функции СloseHandle для того, чтобы уничтожить объект отображения файла, а затем закрыть дескриптор файла.

Пример

Теперь посмотрим, как все это происходит на практике. В качестве примера создадим консольную программу, которая принимает в качестве аргумента командной строки список имен файлов, и копирует содержимое этих файлов один за другим в стандартный поток вывода. Таким образом, при помощи этой утилиты можно объединить несколько файлов в один или отобразить содержимое файлов на экране консоли.

Программа в нашем примере — это классический вариант программы, которую можно написать с использованием стандартных функций STDIO или потоков ввода/вывода C++. В листинге 1 приводится исходный код, использующий для этой цели системные вызовы ввода/вывода Windows. Теперь попробуем реализовать эту программу с использованием механизма отображения файлов на оперативную память.

Полный текст программы содержится в листинге 4.

Листинг 4. Версия NTCAT, использующая отображение файла в память

// обработка ошибок отсутствует

#include <windows.h>

#include <iostream.h>

// для удобства использования MessageBox:

void MB(char *s)

{

  MessageBox(NULL, s, NULL, MB_OK|MB_ICONSTOP);

}

// эта программа выполняет основную работу:

void docat(char *fname)

{

  HANDLE file;     // файл

  HANDLE map;  // объект отображения файла

  char *base;          // указатель на начальную позицию

  int n;                    // размер файла

// Открыть файл с атрибутами: exclusive access/read only/no create

            file=CreateFile(fname, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);

            if (file==INVALID_HANDLE_VALUE)

            {

                        MB("Can't open file");

                        exit(1);

            }

// Размер файла не может быть больше <4GB

            n=GetFileSize(file,NULL); // определение размера файла

// создать отображение файла (без имени, поскольку только для одного потока)

            map=CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);

            if (!map)

            {

                        MB("Не могу открыть файл");

                        exit(2);

            }

// преобразовать в указатель

            base=(char *)MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);

            if (!base)

            {

                        MB("Не могу получить указатель на отображение");

                        exit(3);

            }

// Вывод символа в поток:

// можно использовать cout<<base[i] в цикле либо putchar

// но вызов  cout.write  более эффективен

            cout.write(base,n); 

// завершение работы с отображением

            UnmapViewOfFile(base);

            CloseHandle(map);

            CloseHandle(file);

}

void main(int argc,char *argv[])

{

            if (argc==1)

            {

              MB("Надо запускать строкой: ntcat FILENAME [FILENAME ....]");

              exit(9);

            }

// обработать все файлы

            while (--argc) docat(*++argv);

            exit(0);

}

Не правда ли, ничего сложного? Получив указатель на место в памяти, программа использует единственный вызов cout.write для того, чтобы передать все содержимое файла в поток вывода. Обратите внимание, что программа приказывает отразить в оперативную память количество байт, равное точному размеру файла (соответствующий аргумент вызова CreateFileMapping равен 0). Вместе с тем программа использует вызов GetFileSize для того, чтобы определить точный размер файла и в дальнейшем передать этот размер вызову write. Также обратите внимание на то, что программа не рассчитана на работу с файлами, размер которых превышает 4 Гбайт.