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

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

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

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

DWORD fdwAllocationType,

DWORD fdwProtect);

Эту функцию мы уже рассматривали (и очень подробно) в главе 15. Вызвать VirtualAlloc для передачи физической памяти представлению региона — то же самое, что вызвать VirtualAlloc для передачи памяти региону, ранее зарезервированному вызовом VirtualAlloc с флагом MEM_RESERVE. Получается, что региону, зарезервированному функциями MapViewOfFile или MapViewOfFileEx, — как и региону, зарезервированному функцией VirtualAlloc, — тоже можно передавать физическую память порциями, а не всю сразу. И если вы поступаете именно так, учтите, что все процессы, спроецировавшие на этот регион представление одного и того же объекта «проекция файла», теперь тоже получат доступ к страницам физической памяти, переданным региону.

Итак, флаг SEC_RESERVE и функция VirtualAlloc позволяют сделать табличную матрицу CellData «общедоступной» и эффективнее использовать память.

Примечание. Функция VirtualFree не годится для возврата физической памяти, переданной в свое время проецируемому файлу (созданному с флагом

SEC_RESERVE).

Файловая система NTFS поддерживает так называемые разреженные файлы (sparse files). Это потрясающая новинка. Она позволяет легко создавать и использовать разреженные проецируемые файлы (sparse memory-mapped files), которым физическая память предоставляется не из страничного, а из обычного дискового файла.

Вот пример того, как можно было бы воспользоваться этой новинкой. Допустим, вы хотите создать проецируемый в память файл (MMF) для записи аудиоданных. При этом вы должны записывать речь в виде цифровых аудиоданных в буфер памяти, связанный с дисковым файлом. Самый простой и эффективный способ решить эту задачу — применить разреженный MMF. Все дело в том, что вам заранее не известно, сколько времени будет говорить пользователь, прежде чем щелкнет кнопку Stop. Может, пять минут, а может, пять часов — разница большая! Однако при использовании разреженного MMF это не проблема.

Программа-пример MMFSparse

Эта программа (17-MMFSparse.exe), демонстрирует, как создать проецируемый в память файл, связанный с разреженным файлом NTFS. Файлы исходного кода и ресурсов этой программы находятся в каталоге 17-MMFSparse внутри архива, доступного на веб-сайте поддержки этой книги. После запуска MMFSparse на экране появляется окно, показанное ниже.

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

Когда вы щелкнете кнопку Create а 1MB (1024 KB) Sparse MMF, программа попытается создать разреженный файл «C:\MMFSparse». Если ваш диск С не является томом NTFS, у программы ничего не получится, и ее процесс завершится. А если вы создали том NTFS на каком-то другом диске, модифицируйте мою программу и перекомпилируйте ее, чтобы посмотреть, как она работает.

После создания разреженный файл проецируется на адресное пространство процесса. В поле Allocated Ranges (внизу окна) показывается, какие части файла действительно связаны с дисковой памятью. Изначально файл не связан ни с какой памятью, и в этом поле сообщается «No allocated ranges in the file» («В файле лет выделенных диапазонов»).

Чтобы считать байт, просто введите число в поле Offset и щелкните кнопку Read Byte. Введенное вами число умножается на 1024 (1 Кб), и программа, считав байт по полученному адресу, выводит его значение в поле Byte. Если адрес попадает в область, не связанную с физической памятью, в этом поле всегда показывается нулевой байт.

Для записи байта введите число в поле Offset, а значение байта (0-255) — в поле Byte. Потом, когда вы щелкнете кнопку Write Byte, смещение будет умножено на 1024, и байт по соответствующему адресу получит новое значение. Операция записи может заставить файловую систему передать физическую память ка- кой-либо части файла. Содержимое поля Allocated Ranges обновляется после каждой операции чтения или записи, показывая, какие части файла связаны с физической памятью на данный момент. Вот как выглядит окно программы после записи всего одного байта по смещению 1 024 000 (1000x1024).

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

На этой иллюстрации видно, что физическая память выделена только одному диапазону адресов — размером 65 536 байтов, начиная с логического смещения 983 040 от начала файла. С помощью Explorer вы можете просмотреть свойства файла MMFSparse, как показано ниже.

Заметьте: на этой странице свойств сообщается, что длина файла равна 1 Мб (это виртуальный размер файла), но на деле он занимает на диске только 64 Кб.

Последняя кнопка, Free All Allocated Regions, заставляет программу высвободить всю физическую память, выделенную для файла; таким образом, соответствующее дисковое пространство освобождается, а все байты в файле обнуляются.

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

Теперь поговорим о том, как работает эта программа. Чтобы упростить ее исходный код, я создал С++-класс CSparseStream (который содержится в файле SparseStream.h). Этот класс инкапсулирует поддержку операций с разреженным файлом или потоком данных (stream). В файле MMFSparse.срр я создал другой С++-класс, СMMFSparse, производный от CParseStream. Так что объект класса CMMFSparse обладает не только функциональностью CSparseStream, но и дополнительной, необходимой для использования разреженного потока данных как проецируемого в память файла. В процессе создается единственный глобальный экземпляр класса CMMFSparse — переменная g_mmf. Манипулируя разреженным проецируемым файлом, программа часто ссылается на эту глобальную переменную. Если файловая система тома не поддерживает разреженные файлы (это про-

веряется вызовом CSparseStream::DoesFileSystemSupportSparseStreams), обработ-

чик сообщения WM_INITDIALOG говорит об ошибке, и работа программы завершается.

Когда пользователь щелкает кнопку Create a 1MB (1024 KB) Sparse MMF, программа вызывает CreateFile для создания нового файла в дисковом разделе NTFS 5. Пока что это обычный, самый заурядный файл. Но потом я вызываю метод Initialize глобального объекта g_mmf, передавая ему описатель и максимальный размер файла (1 Мб). Метод Initialize в свою очередь обращается к CreateFileMapping и создает объект ядра «проекция файла» указанного размера, а затем вызывает MapViewOfFile, чтобы сделать разреженный файл видимым в адресном пространстве данного процесса.

Когда Initialize возвращает управление, вызывается функция Dlg_ShowAllocatedRanges. Используя Windows-функции, она перечисляет диапазоны логических адресов в разреженном файле, которым передана физическая память. Начальное смещение и длина каждого такого диапазона показываются в нижнем поле диалогового окна. В момент инициализации объекта g_mmf файлу на диске еще не выделена физическая память, и данное поле отражает этот факт.

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

При чтении объекта g_mmf возвращается либо реальное значение байта, если данному диапазону адресов передана физическая память, либо 0, если память не передана.

Моя программа также демонстрирует, как вернуть файл в исходное сотояние, высвободив все выделенные ему диапазоны адресов (после этого он фактически не занимает места на диске). Реализуется это так. Пользователь щелкает кнопку Free All Allocated Regions. Однако освободить все диапазо- ы адресов, выделенные файлу, который проецируется в память, нельзя. Поэтому первое, что делает программа, — вызывает метод ForceClose объекта

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

g_mmf. Этот метод обращается к UnmapViewOfFile, а потом — к CloseHandle, пе-

редавая описатель объекта ядра «проекция файла».

Далее вызывается метод DecommitPortionOfStream, который освобождает всю память, выделенную логическим байтам в файле. Наконец, программа вновь обращается к методу Initialize объекта g_mmf, и тот повторно инициализирует файл, проецируемый на адресное пространство данного процесса. Чтобы подтвердить освобождение всей выделенной памяти, программа вызывает функцию Dlg_ShowAllocatedRanges, которая выводит в поле строку «No allocated ranges in the file».

И последнее. Используя разреженный проецируемый файл в реальном приложении, вы, наверное, захотите при закрытии файла урезать его логический размер до фактического. Отсечение концевой части разреженного файла, содержащей нулевые байты, не влияет на занимаемый им объем дискового пространства, но позволяет Explorer и команде dir сообщать точный размер файла. С этой целью вы должны после вызова метода ForceClose использовать функции SetFilePointer u

SetEndOfFile.

Примечание. Детали реализации проецируемых файлов, размер которых

может увеличиваться, см. по ссылке (http://www.microsoft.com/msj/0499/win32/win320499.aspx).

MMFSparse.cpp

/****************************************************************************** Module: MMFSparse.cpp

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

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

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

#include <WindowsX.h> #include <WinIoCtl.h> #include "SparseStream.h" #include <StrSafe.h> #include "Resource.h"

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

// этот класс упрощает работу с разреженными проецируемыми файлами class CMMFSparse : public CSparseStream {

private:

 

 

HANDLE m_hFileMap;

// объект «проекция файла»

PVOID

m_pvFile;

// адрес начала проецируемого файла

 

 

 

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

public:

//создает разреженный MMF и проецирует его на адресное

//пространство процесса.

CMMFSparse(HANDLE hStream = NULL, DWORD dwStreamSizeMaxLow = 0, DWORD dwStreamSizeMaxHigh = 0);

// закрывает разреженный MMF

virtual ~CMMFSparse() { ForceClose(); }

//создает разреженный MMF и проецирует его на адресное

//пространство процесса

BOOL Initialize(HANDLE hStream, DWORD dwStreamSizeMaxLow,

DWORD dwStreamSizeMaxHigh = 0);

//оператор приведения MMF к BYTE возвращает адрес первого байта

//в разреженном MMF

operator PBYTE() const { return((PBYTE) m_pvFile); }

// позволяет явно закрывать MMF, не дожидаясь вызова деструктора

VOID ForceClose();

};

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

CMMFSparse::CMMFSparse(HANDLE hStream, DWORD dwStreamSizeMaxLow, DWORD dwStreamSizeMaxHigh) {

Initialize(hStream, dwStreamSizeMaxLow, dwStreamSizeMaxHigh);

}

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

BOOL CMMFSparse::Initialize(HANDLE hStream, DWORD dwStreamSizeMaxLow, DWORD dwStreamSizeMaxHigh) {

if (m_hFileMap != NULL) ForceClose();

// инициализируем значение NULL на случай, если что-то пойдет не так m_hFileMap = m_pvFile = NULL;

BOOL bOk = TRUE; // предполагаем, что все будет хорошо

if (hStream != NULL) {

if ((dwStreamSizeMaxLow == 0) && (dwStreamSizeMaxHigh == 0)) { DebugBreak(); // недопустимый размер потока

}

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

CSparseStream::Initialize(hStream);

bOk = MakeSparse(); // недопустимый размер потока данных if (bOk) {

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

m_hFileMap = ::CreateFileMapping(hStream, NULL, PAGE_READWRITE, dwStreamSizeMaxHigh, dwStreamSizeMaxLow, NULL);

if (m_hFileMap != NULL) {

//проецируем поток данных на адресное пространство процесса m_pvFile = ::MapViewOfFile(m_hFileMap,

FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0);

}else {

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

CSparseStream::Initialize(NULL);

ForceClose(); bOk = FALSE;

}

}

}

return(bOk);

}

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

VOID CMMFSparse::ForceClose() {

// очищаем все, что было успешно создано if (m_pvFile != NULL) {

::UnmapViewOfFile(m_pvFile); m_pvFile = NULL;

}

if (m_hFileMap != NULL) { ::CloseHandle(m_hFileMap); m_hFileMap = NULL;

}

}

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

#define STREAMSIZE (1 * 1024 * 1024) // 1 MB (1024 KB) HANDLE g_hStream = INVALID_HANDLE_VALUE;

CMMFSparse g_mmf;

TCHAR g_szPathname[MAX_PATH] = TEXT("\0");

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

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

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

chSETDLGICONS(hWnd, IDI_MMFSPARSE);

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

EnableWindow(GetDlgItem(hWnd, IDC_OFFSET), FALSE); Edit_LimitText(GetDlgItem(hWnd, IDC_OFFSET), 4); SetDlgItemInt(hWnd, IDC_OFFSET, 1000, FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_BYTE), FALSE);

Edit_LimitText(GetDlgItem(hWnd, IDC_BYTE), 3);

SetDlgItemInt(hWnd, IDC_BYTE, 5, FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_WRITEBYTE), FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_READBYTE), FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_FREEALLOCATEDREGIONS), FALSE);

//записать файл в папку, доступную для записи

GetCurrentDirectory(_countof(g_szPathname), g_szPathname); _tcscat_s(g_szPathname, _countof(g_szPathname), TEXT("\\MMFSparse"));

//проверяем, поддерживает ли том разреженные файлы

TCHAR szVolume[16];

PTSTR pEndOfVolume = _tcschr(g_szPathname, _T('\\')); if (pEndOfVolume == NULL) {

chFAIL("Impossible to find the Volume for the default document folder."); DestroyWindow(hWnd);

return(TRUE);

}

_tcsncpy_s(szVolume, _countof(szVolume), g_szPathname, pEndOfVolume - g_szPathname + 1);

if (!CSparseStream::DoesFileSystemSupportSparseStreams(szVolume)) { chFAIL("Volume of default document folder does not support sparse MMF."); DestroyWindow(hWnd);

return(TRUE);

}

return(TRUE);

}

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

void Dlg_ShowAllocatedRanges(HWND hWnd) {

// заполняем поле Allocated Ranges DWORD dwNumEntries;

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

FILE_ALLOCATED_RANGE_BUFFER* pfarb = g_mmf.QueryAllocatedRanges(&dwNumEntries);

if (dwNumEntries == 0) { SetDlgItemText(hWnd, IDC_FILESTATUS,

TEXT("No allocated ranges in the file")); } else {

TCHAR sz[4096] = { 0 };

for (DWORD dwEntry = 0; dwEntry < dwNumEntries; dwEntry++) { StringCchPrintf(_tcschr(sz, _T('\0')), _countof(sz) - _tcslen(sz),

TEXT("Offset: %7.7u, Length: %7.7u\r\n"), pfarb[dwEntry].FileOffset.LowPart, pfarb[dwEntry].Length.LowPart);

}

SetDlgItemText(hWnd, IDC_FILESTATUS, sz);

}

g_mmf.FreeAllocatedRanges(pfarb);

}

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

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

switch (id) { case IDCANCEL:

if (g_hStream != INVALID_HANDLE_VALUE) CloseHandle(g_hStream);

EndDialog(hWnd, id); break;

case IDC_CREATEMMF:

{

g_hStream = CreateFile(g_szPathname, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

if (g_hStream == INVALID_HANDLE_VALUE) { chFAIL("Failed to create file."); return;

}

// используя этот файл, создаем MMF размером 1Мб (1024 Кб) if (!g_mmf.Initialize(g_hStream, STREAMSIZE)) {

chFAIL("Failed to initialize Sparse MMF."); CloseHandle(g_hStream);

g_hStream = NULL;

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

return;

}

Dlg_ShowAllocatedRanges(hWnd);

// активизируем или отключаем остальные элементы управления

EnableWindow(GetDlgItem(hWnd, IDC_CREATEMMF), FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_OFFSET),

TRUE);

EnableWindow(GetDlgItem(hWnd, IDC_BYTE),

TRUE);

EnableWindow(GetDlgItem(hWnd, IDC_WRITEBYTE), TRUE);

EnableWindow(GetDlgItem(hWnd, IDC_READBYTE),

TRUE);

EnableWindow(GetDlgItem(hWnd, IDC_FREEALLOCATEDREGIONS), TRUE);

// переводим фокус в поле Offset SetFocus(GetDlgItem(hWnd, IDC_OFFSET));

}

break;

case IDC_WRITEBYTE:

{

BOOL bTranslated;

DWORD dwOffset = GetDlgItemInt(hWnd, IDC_OFFSET, &bTranslated, FALSE); if (bTranslated) {

g_mmf[dwOffset * 1024] = (BYTE) GetDlgItemInt(hWnd, IDC_BYTE, NULL, FALSE);

Dlg_ShowAllocatedRanges(hWnd);

}

}

break;

case IDC_READBYTE:

{

BOOL bTranslated;

DWORD dwOffset = GetDlgItemInt(hWnd, IDC_OFFSET, &bTranslated, FALSE); if (bTranslated) {

SetDlgItemInt(hWnd, IDC_BYTE, g_mmf[dwOffset * 1024], FALSE); Dlg_ShowAllocatedRanges(hWnd);

}

}

break;

case IDC_FREEALLOCATEDREGIONS:

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

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

//часть файла в исходное состояние

g_mmf.ForceClose();

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