Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6272
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

564 Часть III. Управление памятью

Программа корректно работает только с текстовыми файлами. В какой кодировке создан текстовый файл (ANSI или Unicode), FileRev определяет вызовом IsTextUnicode (см. главу 2).

После щелчка кнопки Reverse File Contents программа создает копию файла с именем FileRev.dat. Делается это для того, чтобы не испортить исходный файл, изменив порядок следования байтов на обратный. Далее программа вызывает функцию FileReverse — она меняет порядок байтов на обратный и после этого вызывает CreateFile, открывая FileRev.dat для чтения и записи.

Как я уже говорил, простейший способ «перевернуть» содержимое файла — вызвать функцию _strrev из библиотеки С. Но для этого последний символ в строке должен быть нулевой. И поскольку текстовые файлы не заканчиваются нулевым символом, программа FileRev подписывает его в конец файла. Для этого сначала вызывается функция GetFileSize.

dwFileSize = GetFileSize(hFile, NULL);

Теперь, вооружившись знанием длины файла, можно создать объект «проекция файла», вызвав CreateFileMapping. При этом размер объекта равен dwFileSize плюс размер «широкого» символа, чтобы учесть дополнительный нулевой символ в конце файла. Создав объект «проекция файла», программа проецирует на свое адресное пространство представление этого объекта. Переменная pvFile содержит значение, возвращенное функцией MapViewOfFile, и указывает на первый байт текстового файла.

Следующий шаг — запись нулевого символа в конец файла и реверсия строки:

PSTR pchANSI = (PSTR) pvFile; pchANSI[dwFileSize / sizeof(CHAR)] = 0;

В текстовом файле каждая строка завершается символами возврата каретки ('\r') и перевода строки ('\n'). К сожалению, после вызова функции _strrev эти символы тоже меняются местами. Поэтому для загрузки преобразованного файла в текстовый редактор придется заменить все пары «\n\r» на исходные «\r\n». В программе этим занимается следующий цикл:

while (pchANSI != NULL) {

 

// "переворачиваем" содержимое файла

 

*pchANSI++ = '\r';

// заменяем ‘\n’ на ‘\r’

*pchANSI++ = '\n';

// заменяем ‘\r’ на ‘\n’

pchANSI = strstr(pchANSI, "\n\r");

// ищем следующее вхождение

}

 

Закончив обработку файла, программа прекращает отображение на адресное пространство представления объекта «проекция файла» и закрывает описатели всех объектов ядра. Кроме того, программа должна удалить нулевой символ, добавленный в конец файла (функция _strrev не меняет позицию этого символа). Если бы программа не убрала нулевой символ, то полу-

Глава 17. Проецируемые в память файлы.docx 565

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

Прежде всего установите указатель файла в требуемую позицию (в данном случае — в конец файла) и вызовите функцию SetEndOfFile:

SetFilePointer(hFile, dwFileSize, NULL, FILE_BEGIN);

SetEndOfFile(hFile);

Примечание. Функцию SetEndOfFile нужно вызывать после отмены проецирования представления и закрытия объекта «проекция файла», иначе она вернет FALSE, а функция GetLastError — ERROR_USER_MAPPED_FILE.

Данная ошибка означает, что операция перемещения указателя в конец файла невозможна, пока этот файл связан с объектом «проекция файла».

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

566 Часть III. Управление памятью

FileRev.cpp

/****************************************************************************** Module: FileRev.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* см. приложение A */ #include <windowsx.h>

#include <tchar.h> #include <commdlg.h>

#include <string.h> // для доступа к _strrev #include "Resource.h"

///////////////////////////////////////////////////////////////////////////////

#define FILENAME TEXT("FileRev.dat")

///////////////////////////////////////////////////////////////////////////////

BOOL FileReverse(PCTSTR pszPathname, PBOOL pbIsTextUnicode) {

*pbIsTextUnicode = FALSE; // предполагаем, что текст в Unicode

// открываем файл для чтения и записи

HANDLE hFile = CreateFile(pszPathname, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if (hFile == INVALID_HANDLE_VALUE) { chMB("File could not be opened."); return(FALSE);

}

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

DWORD dwFileSize = GetFileSize(hFile, NULL);

//создаем объект «проекция файла». Он на 1 символ больше, чем сам

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

//завершения строки. Поскольку пока еще неизвестно, содержит файл

//ANSIили Unicode-символы, я предлагаю худшее и добавляю

//размер WCHAR вместо CHAR.

HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, dwFileSize + sizeof(WCHAR), NULL);

if (hFileMap == NULL) {

chMB("File map could not be opened.");

Глава 17. Проецируемые в память файлы.docx 567

CloseHandle(hFile);

return(FALSE);

}

// получаем адрес, по которому проецируется в память первый байт файла

PVOID pvFile = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);

if (pvFile == NULL) {

chMB("Could not map view of file."); CloseHandle(hFileMap); CloseHandle(hFile);

return(FALSE);

}

// что содержит буфер: ANSIили Unicode-символы? int iUnicodeTestFlags = -1; // Try all tests

*pbIsTextUnicode = IsTextUnicode(pvFile, dwFileSize, &iUnicodeTestFlags);

if (!*pbIsTextUnicode) {

//при дальнейших операциях с файлом явно используется ANSI-функция,

//так как мы имеет дело с ANSI-файлом

//записываем в самый конец файла нулевой символ.

PSTR pchANSI = (PSTR) pvFile; pchANSI[dwFileSize / sizeof(CHAR)] = 0;

//«переворачиваем» содержимое файла. _strrev(pchANSI);

//преобразуем все комбинации "\n\r" обратно в to "\r\n", чтобы

//сохранить нормальную последовательность кодов завершения строки

//в текстовый файл

pchANSI = strstr(pchANSI, "\n\r"); // Find first "\r\n".

while (pchANSI != NULL) { // вхождение найдено…

*pchANSI++

=

'\r';

//

заменяем

'\n'

на

'\r'.

*pchANSI++

=

'\n';

//

заменяем

'\r'

на

'\n'.

pchANSI = strstr(pchANSI, "\n\r"); // ищем следующее вхождение

}

}else {

//при дальнейших операциях с файлом явно используем

//Unicode-функции, так как мы имеем дело с Unicode-файлом

568 Часть III. Управление памятью

// записываем в самый конец файла нулевой символ

PWSTR pchUnicode = (PWSTR) pvFile; pchUnicode[dwFileSize / sizeof(WCHAR)] = 0;

if ((iUnicodeTestFlags & IS_TEXT_UNICODE_SIGNATURE) != 0) {

//если первый символ – Unicode-маркер порядка байтов (0xFEFF),

//то оставим этот символ в начале файла.

pchUnicode++;

}

//«переворачиваем» содержимое файла

_wcsrev(pchUnicode);

//преобразуем все комбинации "\n\r" обратно в "\r\n", чтобы

//сохранить нормальную последовательность кодов завершения

//строки в текстовом файле

pchUnicode = wcsstr(pchUnicode, L"\n\r"); // ищем первое вхождение '\n\r'

while (pchUnicode != NULL) {

// вхождение найдено…

 

*pchUnicode++ = L'\r';

// заменяем '\n' на '\r'.

*pchUnicode++ = L'\n';

// заменяем '\r' на '\n'.

pchUnicode = wcsstr(pchUnicode, L"\n\r"); // ищем следующее вхождение

}

}

//очищаем все перед завершением

UnmapViewOfFile(pvFile);

CloseHandle(hFileMap);

//удаляем добавленный ранее концевой нулевой байт

SetFilePointer(hFile, dwFileSize, NULL, FILE_BEGIN); SetEndOfFile(hFile);

CloseHandle(hFile);

return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {

chSETDLGICONS(hWnd, IDI_FILEREV);

Глава 17. Проецируемые в память файлы.docx 569

// инициализируем диалоговое окно

EnableWindow(GetDlgItem(hWnd, IDC_REVERSE), FALSE); return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

TCHAR szPathname[MAX_PATH];

switch (id) { case IDCANCEL:

EndDialog(hWnd, id); break;

case IDC_FILENAME: EnableWindow(GetDlgItem(hWnd, IDC_REVERSE),

Edit_GetTextLength(hWndCtl) > 0); break;

case IDC_REVERSE:

GetDlgItemText(hWnd, IDC_FILENAME, szPathname, _countof(szPathname));

// делаем копию исходного файла, чтобы случайно не повредить его if (!CopyFile(szPathname, FILENAME, FALSE)) {

chMB("New file could not be created."); break;

}

BOOL bIsTextUnicode;

if (FileReverse(FILENAME, &bIsTextUnicode)) { SetDlgItemText(hWnd, IDC_TEXTTYPE,

bIsTextUnicode ? TEXT("Unicode") : TEXT("ANSI"));

// запускаем Notepad, чтобы увидеть плоды своих трудов

STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi;

TCHAR sz[] = TEXT("Notepad ") FILENAME; if (CreateProcess(NULL, sz,

NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {

CloseHandle(pi.hThread);

CloseHandle(pi.hProcess);

}

570 Часть III. Управление памятью

}

break;

case IDC_FILESELECT:

OPENFILENAME ofn = { OPENFILENAME_SIZE_VERSION_400 }; ofn.hwndOwner = hWnd;

ofn.lpstrFile = szPathname; ofn.lpstrFile[0] = 0;

ofn.nMaxFile = _countof(szPathname); ofn.lpstrTitle = TEXT("Select file for reversing"); ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST; GetOpenFileName(&ofn);

SetDlgItemText(hWnd, IDC_FILENAME, ofn.lpstrFile); SetFocus(GetDlgItem(hWnd, IDC_REVERSE));

break;

}

}

///////////////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

 

chHANDLE_DLGMSG(hWnd, WM_INITDIALOG,

Dlg_OnInitDialog);

chHANDLE_DLGMSG(hWnd, WM_COMMAND,

Dlg_OnCommand);

}

 

return(FALSE);

 

}

///////////////////////////////////////////////////////////////////////////////

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int) {

DialogBox(hInstExe, MAKEINTRESOURCE(IDD_FILEREV), NULL, Dlg_Proc); return(0);

}

//////////////////////////////// End of File //////////////////////////////////

Обработка больших файлов

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

Глава 17. Проецируемые в память файлы.docx 571

чала файла. Закончив обработку данных в этом представлении, вы отключите его и спроецируете представление следующей части файла — и так до тех пор, пока не будет обработан весь файл. Конечно, это делает работу с большими файлами, проецируемыми в намять, не слишком удобной, но утешимся тем, что длина большинства файлов достаточно мала.

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

__int64 CountOs(void) {

//начальные границы представлений всегда начинаются по адресам,

//кратным гранулярности выделения памяти

SYSTEM_INFO sinf;

GetSystemInfo(&sinf);

// открываем файл данных

HAMDLE hFile = CreateFile(TEXT("C:\\HugeFile.Big"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

// создаем объект "проекция файла"

HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

DWORD dwFileSizeHigh;

__int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh); qwFileSize += (((_int64) dwFileSizeHigh) << 32);

// доступ к описателю объекта "файл" нам больше не нужен

CloseHandle(hFile);

__int64 qwFileOffset = 0, qwNum0f0s = 0;

while (qwFileSize > 0) {

// определяем, сколько байтов надо спроецировать

DW0RD dwBytesInBlock = sinf.dwAllocationGranularity; if (qwFileSize < sinf.dwAllocationGranularity)

dwBytesInBlock = (DWORD) qwFileSize;

PBYTE pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_HAP_READ,

(DWORD) (qwFileOffset >> 32),

// начальный байт

(DW0RD) (qwFileOffset & 0xFFFFFFFF),

// в файле

dwBytesInBlock);

// # число проецируемых байтов

572 Часть III. Управление памятью

// подсчитываем количество нулевых байтов в этом блоке

for (DWORD dwByte = 0; dwByte < dwBytesInBlock; dwByte++) { lf (pbFile[dwByte] == 0)

qwNumOfOs++;

}

//прекращаем проецирование представления, чтобы в адресном

//пространстве не образовалось несколько представлений одного файла

UnmapViewOfFile(pbFile);

//переходим к следующей группе байтов в файле

qwFileOffset += dwBytesInBlock; qwFileSize -= dwBytesInBlock;

}

CloseHandle(hFileMapping);

return(qwNumOfOs);

}

Этот алгоритм проецирует представления по 64 Кб (в соответствии с гранулярностью выделения памяти) или менее. Кроме того, функция MapViewOfFile требует, чтобы передаваемое ей смещение в файле тоже было кратно гранулярности выделения памяти. Подпрограмма проецирует на адресное пространство сначала одно представление, подсчитывает в нем количество нулей, затем переходит к другому представлению, и все повторяется. Спроецировав и просмотрев все 64килобайтовые блоки, подпрограмма закрывает объект «проекция файла».

Проецируемые файлы и когерентность

Система позволяет проецировать сразу несколько представлений одних и тех же файловых данных. Например, можно спроецировать в одно представление первые 10 Кб файла, а затем — первые 4 Кб того же файла в другое представление. Пока вы проецируете один и тот же объект, система гарантирует когерентность (согласованность) отображаемых данных. Скажем, если программа изменяет содержимое файла в одном представлении, это приводит к обновлению данных и в другом. Так происходит потому, что система, несмотря на многократную проекцию страницы на виртуальное адресное пространство процесса, хранит данные на единственной странице оперативной памяти. Поэтому, если представления одного и того же файла данных создаются сразу несколькими процессами, данные попрежнему сохраняют когерентность — ведь они сопоставлены только с одним экземпляром каждой страницы в оперативной памяти. Все это равносильно тому, как если бы страницы оперативной памяти были спроецированы на адресные пространства нескольких процессов одновременно.

Глава 17. Проецируемые в память файлы.docx 573

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

Кстати, функция CreateFile позволяет вашему процессу открывать файл, проецируемый в память другим процессом. После этого ваш процесс сможет считывать или записывать данные в файл (с помощью функций ReadFile или WriteFile). Разумеется, при вызовах упомянутых функций ваш процесс будет считывать или записывать данные не в файл, а в некий буфер памяти, который должен быть создан именно этим процессом; буфер не имеет никакого отношения к участку памяти, используемому для проецирования данного файла. Но надо учитывать, что, когда два приложения открывают один файл, могут возникнуть проблемы. Дело в том, что один процесс может вызвать ReadFile, считать фрагмент файла, модифицировать данные и записать их обратно в файл с помощью WriteFile, а объект «проекция файла», принадлежащий второму процессу, ничего об этом не узнает. Поэтому, вызывая для проецируемого файла функцию CreateFile, всегда указывайте нуль в параметре dwShareMode. Тем самым вы сообщите системе, что вам нужен монопольный доступ к файлу и никакой посторонний процесс не должен его открывать.

Файлы с доступом «только для чтения» не вызывают проблем с когерентностью — значит, это лучшие кандидаты на отображение в память. Ни в коем случае не используйте механизм проецирования для доступа к записываемым файлам, размещенным на сетевых дисках, так как система не сможет гарантировать когерентность представлений данных. Если один компьютер обновит содержимое файла, то другой, у которого исходные данные содержатся в памяти, не узнает об изменении информации.

Базовый адрес файла, проецируемого в память

Помните, как вы с помощью функции VirtualAlloc указывали базовый адрес региона, резервируемого в адресном пространстве? Примерно так же можно указать системе спроецировать файл по определенному адресу — только вместо функции

MapViewOfFile нужна MapViewOfFileEx:

PVOID MapViewOfFileEx(

HANDLE hFileMappingObject,

DWORD dwDesiredAccess,

DWORD dwFileOffsetHigh,

DWORD dwFileOffsetLow,

SIZE_T dwNumberOfBytesToMap,

PVOID pvBaseAddress);

Соседние файлы в предмете Программирование на C++