    ФАЙЛЫ, ПРОЕЦИРУЕМЫЕ В ПАМЯТЬ 

    глава 8 
    
    Операции с файлами — то, что рано или поздно приходится делать практически 
во всех программах, и всегда это вызывает массу проблем. Должно ли приложение 
просто открыть файл, считать и закрыть его, или открыть, считать фрагмент в 
буфер и перезаписать его в другую часть файла? В Win32 многие из этих проблем 
решаются очень изящно — с помощью проецируемых в память файлов (memory-mapped files). 
    Как и виртуальная память, проецируемые файлы позволяют резервировать 
регион адресного пространства и передавать ему физическую память. Различие 
между этими механизмами состоит в том, что в последнем случае физическая 
память не выделяется из страничногофайла, аберется_из_файла, уже находящегося 
на диске. Как только файл спроецирован в память, к нему можно обращаться так, 
будто он целиком в нее загружен. 
    Проецируемые файлы применяются для:        ——.--..-
    • загрузки и выполнения ЕХЕ- и DLL-файлов. Это позволяет существенно 
экономить как на^азмере страничного файла, так и на времени, необходимом для 
подготовки приложения к выполнению; 
    • доступа к файлу данных, размещенному на диске. Это позволяет обойтись 
без операций файлового ввода/вывода и буферизации его содержимого; 
    • разделения данных между несколькими процессами, выполняемыми на одной 
машине. (В Win32 есть и другие методы для совместного доступа разных процессов 
к одним данным — но опять же они реализованы на основе проецируемых в память файлов.) 
    Все эти области применения проецируемых файлов мы и рассмотрим в данной главе. 

    Проецирование в память ЕХЕ- и DLL-файлов 

    При вызове из потока функции CreateProcess система: 
    1. Отыскивает ЕХЕ-файл, указанный при вызове CreateProcess. Если файл не 
найден, новый процесс не создается, а функция возвращает NULL. 
    2. Создает новый объект ядра «процесс». 
    3. Создает адресное пространство нового процесса (размером 4 Гб). 
    4. Резервирует регион адресного пространства — такой, чтобы в него 
поместился заданный ЕХЕ-файл. Желательное расположение этого региона 
указывается внутри самого ЕХЕ-файла. По умолчанию базовый адрес ЕХЕ-файла — 
0х00400000. Но 

    163 

    164 _.____________________________Windows для профессионалов 

    при создании исполняемого файла приложения это значение может быть 
изменено параметром компоновщика /BASE. 
    5. Отмечает, что физическая память, увязанная с зарезервированным 
регионом, — ЕХЕ-файл на диске, а не страничный файл. 
    Спроецировав ЕХЕ-файл на адресное пространство процесса, система 
обращается к разделу ЕХЕ-файла со списком DLL-модулей, содержащих необходимые 
программе функции. После этого система — вызовом LoadLibrary— поочередно 
загружает указанные (а при необходимости и дополнительные) DLL-модули. Всякий 
раз, когда для загрузки DLL вызывается LoadLibrary, система выполняет 
действия, аналогичные описанным выше в пп. 4 и 5:                              
    1. Резервирует регион адресного пространства — такой, чтобы в него мог 
поместиться заданный DLL-файл. Желательное расположение этого региона 
указывается внутри самого DLL-файла. По умолчанию Visual C++ присваивает 
DLLмодулям базовый адрес 0х10000000. Но при компоновке DLL-модуля это значение 
можно изменять с помощью параметра /BASE. У всех стандартных системных 
DLL-модулей, входящих в комплект поставки Windows NT и Windows 95, разные 
базовые адреса. 
    2. Если зарезервировать регион по желательному для DLL базовому адресу не 
удается (либо он слишком мал, либо занят каким-то еще ЕХЕ- или DLL-файлом), 
система пытается найти другой регион. Но по двум причинам такая ситуация 
весьма неприятна. Во-первых, если в DLL нет настроечной информации (fixup 
information), загрузка может вообще не получиться. (Настроечную информацию 
можно удалить из DLL при компоновке с параметром /FIXED. Это уменьшит размер 
DLLфайла, но тогда модуль должен грузиться только по указанному базовому 
адресу.) Во-вторых, системе приходится выполнять модификацию адресов 
(relocation) внутри DLL. В Windows 95 эта операция осуществляется по мере 
подкачки страниц в оперативную память. Но в Windows NT на это уходит 
дополнительная физическая память, выделяемая из страничного файла, да и 
загрузка такого DLL-модуля займет больше времени. 
    3. Отмечает, что физическая память, связанная с зарезервированным 
регионом, — DLL-файл на диске, а не страничный файл. Если Windows NT пришлось 
выполнять модификацию адресов из-за того, что DLL не удалось загрузить по 
желательному базовому адресу, она запоминает, что часть физической памяти для 
DLL связана со страничным файлом. 
    Если система почему-либо не свяжет ЕХЕ-файл с необходимыми ему 
DLL-модулями, на экране появится соответствующее сообщение, а адресное 
пространство процесса и объект «процесс» освобождаются. При этом CreateProcess 
возвращает FALSE, а прояснить причинасбоя помогает функция GetLastError. 
    После увязки ЕХЕ- и DLL-файлов с адресным пространством процесса начинает 
исподняться стартовый код ЕХЕ-файла. Подкачку страниц, буферизацию и 
кэширование система берет на себя. Например, если код в ЕХЕ-файле переходит к 
команде, не загруженной в память, возникает ошибка. Обнаружив ее, система 
перекачивает нужную страницу кода из образа файла на страницу оперативной 
памяти. Затем отображает страницу оперативной памяти на должный участок 
адресного пространства процесса, тем самым позволяя потоку продолжать 
выполнение кода так, будто все его страницы уже загружены в память. Все эти 
операции скрыты от приложения и периодически повторяются при каждой попытке 
процесса обратиться к коду или данным, отсутствующим в оперативной памяти. 

    _____________________Глава 8. Файлы, проецируемые в память 165 

    Несколько экземпляров ЕХЕ или DLL не могут совместно использовать 
статические данные 
    Когда Вы создаете новый процесс для уже выполняемого приложения, система 
просто открывает другое проецируемое в память представление (view) объекта, 
идентифицирующего образ (image) исполняемого файла, и создает новые объекты 
«процесс» и «поток» (для первичного потока). Этим объектам присваиваются 
идентификаторы нового процесса и потока. С помощью проецируемых в память 
файлов несколько одновременно выполняемых экземпляров'приложения могут 
совместно использовать один и тот же код, загруженный в оперативнукГпамять.    
    Здесь возникает небольшая проблема. В Win32 применяется линейное (flat) 
адресное пространство (размером 4 Гб). При компиляции и компоновке программы 
весь ее код и данные объединяются в нечто, так сказать, большое и цельное. 
Данные, конечно, отделены от кода, но только в том смысле, что они расположены 
вслед за кодом в ЕХЕ-файле'. Вот упрощенная иллюстрация того, как код и данные 
приложения загружаются в виртуальную память, а затем отображаются на адресное 
пространство процесса: 

    Адресное пространство 

    ЕХЕ-файл на диске              Виртуальная память программы 
    Страница кода 2   ——^   ---—— Страница кода 1 
    Раздел кода из 3 страниц 
    Страница кода 1   —"^—-—— Страница кода 2 
    —————————————              Страница данных 2 ' -—^    ^-— Страница кода 3 
    Раздел данных                  Страница кода 3   —'"v,--'"— Страница данных 1 
    из 2 страниц                ~~——————————7~    .^•'V.   ~~,-.——————————— 
    Страница донных 1 —-^   ^~-— Страница данных i 

    Теперь допустим, что запущен второй экземпляр программы. Система 
просто-напросто проецирует страницы виртуальной памяти, содержащие код и 
данные файла, на адресное пространство второго экземпляра приложения: 

    Адресное пространство                   Адресное пространство 
    второго экземпляра    Виртуальная память              первого экземпляра 
    Страница кода 1 —~^ ^-—— Страница кода 2 ——^ ^—— Страница кода 1 
    Страница кода 2 —-^'^—— Страница кода 1 —-r'^~~—— Страница кода 2 
    Страница кода 3 —-^ ^-— Страница данных 2 —-^ ^—— Страница кода 3 
    Страница данных 1 —~^/'~~—— Страница кода 3 —'""Х^-"'—— Страница данных 1 
    Страница данных 2 —""'^ ^~~~—— Страница данных 1 —~^ ^"'-— Страница данных 2 

    Если один экземпляр приложения изменяет какие-либо глобальные переменные, 
размещенные на странице данных, содержимое памяти изменяется для всех 
экземпляров этого приложения. Такое изменение.могло бы 
п^идест_и_к^катастрофическим последствиям и поэтому недопустимо""'' 
    Система предотвращает подобные ситуации, применяя «копирование при 
записи», заложенное в схему управления памятью. Всякий раз, когда программа 
пытается записывать что-то в файл, спроецированный в память, система 
перехватывает эту попытку, выделяет новый блок памяти, копирует в него нужную 
программе страницу и после этого разрешает 
    1. На самом деле содержимое файла разбито на отдельные разделы. Код 
находится в одном разделе, а глобальные переменные — в другом. Разделы 
выравниваются по границам страниц. [Они, как Вы помните, могут быть по 4 Кб 
(на xS6, MIPS и PowerPC) или по 8 Кб (на DEC Alpha).] Приложение определяет 
размер страницы через функцию GetSystemInfo. В ЕХЕ- или DLL-файле раздел кода 
обычно предшествует разделу данных. 

    166 _____________________________Windows для профессионалов 

    запись в новый блок памяти. Благодаря этому работа остальных экземпляров 
программы не нарушается. Вот что получится, когда первый экземпляр программы 
попытается изменить какую-нибудь глобальную переменную на второй странице данных: 

    Адресное пространство                                           Адресное пространство 
    второго экземпляра              Виртуальная память              первого экземпляра 
    Страница кода 1 —-~^ ^—— Страница кода 2 ——^ ^——— Страница кода 1 
    Страница кода 2 —~^^~~~—— Страница кода 1 ——^^~~-—— Страница кода 2 
    Страница кода 3 —-^^ —-— Страница данных 2 ^-—,, Страница кода 3 
    Страница данных 1 —-^^/'•~~—— Страница кода 3 —-^ ^-——'^Страница данных 1 
    Страница данных 2 —' ^~~~—— Страница данных I ——^-—^"Страница данных 2 
    Новая страница —— 

    Система выделяет новую страницу виртуальной памяти и копирует в нее 
содержимое страницы данных 2. Адресное пространство первого экземпляра 
программы изменяется так, чтобы отобразить новую страницу данных на тот же 
участок, что и исходную. Теперь процесс может изменить глобальную переменную, 
не затрагивая данных для другого экземпляра программы. 
    Аналогичная цепочка событий происходит и при отладке. Например, запустив 
несколько копий программы, Вы хотите отладить одну из них. Вызвав отладчик, Вы 
ставите в строке кода точку прерывания. Отладчик модифицирует код, заменяя 
одну из команд на языке ассемблера другой — заставляющей активизировать сам 
отладчик. После модификации кода все копии программы, доходя до исполнения 
измененной команды, приводили бы к его активизации. Чтобы этого избежать, 
система прибегает к «копированию при записи». Обнаружив попытку отладчика 
изменить код, она выделяет новый блок памяти, копирует туда нужную страницу и 
позволяет отладчику модифицировать код на этой копии2. 
    При загрузке процесса система просматривает все страницы образа файла. 
Физическая память из страничного файла передается сразу только тем страницам, 
что должны быть защищены атрибутом «копирование при записи». Эти страницы 
только передаются, но не изменяются. При обращении к такому участку образа 
файла в память загружается соответствующая страница. Если ее модификации не 
происходит, она может быть выгружена из памяти и при необходимости загружена 
вновь. Если же страница файла модифицируется, система перекачивает ее на одну 
из ранее переданных страниц в страничном файле. 
    Поведение Windows NT и Windows 95 одинаково, кроме ситуации, когда в 
память загружено два экземпляра одного модуля и никаких данных не изменено. В 
этом случае процессы под управлением Windows NT могут совместно использовать 
данные, а в Windows 95 каждый процесс получает свою копию этих данных. Но если 
в память загружен лишь один экземпляр модуля или же данные были модифицированы 
(что чаще всего и бывает), то Windows NT и Windows 95 ведут себя одинаково. 
    2. Заметьте: в ЕХЕ- или DLL-файле можно, создав глобальные переменные, 
сделать их доступными всем экземплярам файла. Коротко говоря, для этого надо 
разместить переменные в отдельном разделе, применив директиву компилятора 
^pragma data_seg( ). Затем укажите компоновщику параметр 
/SI:.CrTЮ^:uмя,ampliбymы, который подскажет ему, что данные в этом разделе 
должны быть доступны всем экземплярам (или проекциям) файла. Аргумент имя 
идентифицирует имя раздела, содержащего нужные переменные; аргумент атрибуты 
определяет атрибуты данных в этом разделе. Чтобы пере менные стали 
«общедоступными», примените атрибут RSW (read/shared/write). Подробнее о том, 
как сделать глобальные переменные доступными нескольким экземплярам DLL, см. 
главу 12. 

    .Глава 8. Файлы, проецируемые в память 167 


    Файлы данных, проецируемые в память 

    При загрузке ЕХЕ- или DLL-файла операционная система автоматически 
использует метод, описанный в предыдущем разделе. Однако на адресное 
пространство процесса можно спроецировать и файл данных. Это чрезвычайно 
удобно при манипуляциях с большими потоками данных. 
    Чтобы представить всю мощь такого применения механизма проецирования 
файлов, рассмотрим четыре возможных реализации программы, меняющей порядок 
следования байтов в файле на обратный. 

    Метод 1. Один файл, один буфер 

    Первый (и теоретически простейший) метод — выделение блока памяти, 
достаточного для размещения всего файла. Открываем файл, считываем его 
содержимое в блок памяти, закрываем. Располагая в памяти содержимым файла, 
можно поменять первый байт с последним, второй — с предпоследним и т. д. 
Процесс будет продолжаться, пока мы не поменяем местами два смежных байта, 
находящихся в середине файла. Закончив эту операцию, вновь открываем файл и 
перезаписываем его содержимое. 
    Этот довольно простой в реализации метод имеет два существенных 
недостатка. Вопервых, придется выделить блок памяти такого же размера, что и 
файл. Это терпимо, если файл небольшой. А если он занимает 2 Гб? Система 
просто не позволит приложению передать такой объем физической памяти. Значит, 
к большим файлам нужен совершенно иной подход. 
    Во-вторых, если перезапись вдруг_пт>ервется, содержимое файла будет 
испорчено. Простейшая мера предосторожности — создать копию исходного файла 
(потом ее можно удалить), но это потребует дополнительного дискового пространства. 

    Метод 2. Два файла, один буфер 

    Открываем существующий файл и создаем на диске новый — нулевой длины. 
Затем выделяем небольшой внутренний буфер размером, скажем, 8 Кб. 
Устанавливаем указатель файла в позицию 8 Кб от конца, считываем в буфер 
последние 8 Кб содержимого файла, меняем в нем порядок следования байтов на 
обратный и переписываем буфер в только что созданный файл. Повторяем эти 
операции, пока не дойдем до начала исходного файла. Конечно, если длина файла 
не будет кратна 8 Кб, операции придется немного усложнить, но это не страшно. 
Закончив обработку, закрываем оба файла и удаляем исходный файл. 
    Этот метод сложнее первого, но позволяет гораздо эффективнее использовать 
память, так как требует выделения лишь 8 Кб. Но и здесь не без проблем, и вот 
две главных. Во-первых, обработка идет медленнее, чем при первом методе: на 
каждой итерации перед считыванием приходится находить нужный фрагмент 
исходного файла. Во-вторых, этот метод потенциально может потребовать 
огромного пространства жесткого диска. Если длина исходного файла 400 Мб, 
новый файл постепенно вырастет до этой величины, и перед самым удалением 
исходного файла будет занято 800 Мб, т. е. на 400 Мб больше, чем следовало бы. 
Так что все пути ведут... к третьему методу. 

    Метод 3. Один файл, два буфера 

    Программа инициализирует два раздельных буфера, допустим, по 8 Кб и 
считывает первые 8 Кб файла в один буфер, а последние 8 Кб — в другой. Далее 
содержимое обоих буферов обменивается в обратном порядке: содержимое первого 
буфера записывается в конец, вто

    168 ______________________________Windows для профессионалов 

    рого — в начало того же файла. И при каждом проходе программа перемещает 
восьмикилобайтовые блоки в двух половинах файла. Разумеется, нужно 
предусмотреть какую-то обработку на случай, если длина файла не кратна 16 Кб, 
и обработка будет куда сложнее, чем в предыдущем методе. Но разве это испугает 
опытного программиста? 
    По сравнению с первыми двумя данный метод позволяет экономить пространство 
на жестком диске, так как все операции чтения и записи протекают в рамках 
одного файла. Что же касается памяти, то и здесь этот метод довольно 
эффективен, используя всего 16 Кб. Однако он, по-видимому, самый сложный в 
реализации. И, кроме того, как и первый, он может испортить файл данных, если 
процесс вдруг прервется. 
    Ну а теперь посмотрим, как тот же процесс реализуется, если применить 
файлы, проецируемые в память. 

    Метод 4. Один файл и никаких буферов 

    Вы открываете файл, указывая системе зарезервировать регион виртуального 
адресного пространства. Затем сообщаете, что первый байт файла следует 
спроецировать на первый байт этого региона, и обращаетесь к региону так, 
словно он на самом деле содержит файл. И если в конце файла есть нулевой байт, 
можно вызвать библиотечную С-функцию _strrev и поменять порядок следования 
байтов на обратный. 
    Огромный плюс этого метода в том, что всю работу по кэшированию файла 
выполняет сама система: не нужно выделять память, загружать данные из файла в 
память, переписывать их обратно в файл и т. д., и т. п. Но, увы, вероятность 
прерывания процесса, например из-за сбоя электросети, по-прежнему сохраняется, 
и от порчи данных Вы не застрахованы. 

    Использование проецируемых в память файлов 

    Для этого нужно выполнить три операции: 
    1. Создать или открыть объект ядра «файл», идентифицирующий дисковый файл, 
который Вы хотите использовать как проецируемый в память. 
    2. Создать объект ядра «проекция файла», чтобы сообщить системе размер 
файла и способ доступа к нему. 
    3. Указать системе: спроецировать в адресное пространство Вашего процесса 
после дний объект целиком или только его часть. 
    Закончив работу с проецируемым в память файлом, следует выполнить тоже три операции: 
    1. Сообщить системе об отмене проецирования на адресное пространство 
процесса объекта ядра «проекция файла». 
    2. Закрыть этот объект. 
    3. Закрыть объект ядра «файл». 
    Детальное рассмотрение этих операций — в следующих пяти разделах. 

    _____________________Глава 8. Файлы, проецируемые в память 169 



    Этап 1. Создание или открытие объекта ядра «файл» 

    Для этого Вы должны применять только функцию CreateFile: 

    HANDLE CreateFlle(LPCSTR IpFileName, DWORD dwDesiredAccess, 
    DWORD dwShareMode, LPSECURITY_ATTRIBUTES IpSecurityAttributes, 
    DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, 
    HANDLE hTemplateFile): 

    /\.    Хоть_и..тр^дног[редполржить об этом по названию, но CreateFile 
предназначена / т,'^. и для "'"Ж111™ существующего файла. Функция OpenFile из 
16-разрядной Win^лИД\ dows сохранена в Win32 API только для совместимости. 
Разрабатывая программу, избегайте OpenFile и всегда используйте только новую 
функцию — CreateFile. 
    Как видите, у функции CreateFile параметров немало. Здесь я сосредоточусь 
только на первых трех: IpFileName, dwDesiredAccess и dwShareMode. Об остальных 
параметрах и вообще об этой функции мы поговорим в главе 15. 
    Как Вы, наверное, догадались, первый параметр, IpFileName, идентифицирует 
имя создаваемого или открываемого файла (при необходимости вместе с путем). 
Второй — dwDesiredAccess — указывает способ доступа к содержимому файла. Здесь 
задается одно из четырех значений: 

    Значение                          Описание 

    О                                   Содержимое файла нельзя считывать или 
записывать. Указывайте это значение, если Вы хотите всего лишь получить 
атрибуты файла.^ 
    GENERIC_READ                    Чтение файла разрешено. 
    GENERIC_WRITE                   Запись в файл разрешена. 
    GENERIC_READ : GENERIC_WRITE    Разрешено и то и другое. 
    Создавая или открывая файл данных с намерением использовать его в качестве 
проецируемого в память, можно установить либо флаг GENERIC_READ (только для 
чтения), либо комбинированный флаг GENERIC_READ ! GENERIC_WRITE (чтение/запись). 
    Третий параметр, dwShareMode, указывает тип совместного доступа к данному файлу: 

    Значение                               Описание 

    О                                       Файл не подлежит открытию «со стороны». 
    FILE_SHARE_READ                        Попытка постороннего процесса 
открыть файл с флагом GENERIC_WRITE не удастся. 
    FILE_SHARE_WRITE                       Попытка постороннего процесса 
открыть файл с флагом GENERIC_READ не удастся. 
    FILE_SHARE_READ ! FILE_SHARE_WRITE     Посторонний процесс может открывать 
файл без ограничений. 
    Создав или открыв указанный файл, CreateFile возвращает его описатель, в 
ином случае —'идентификатор INVALID_HANDLE_VALUE. 

    170 ______________________________Windows для профессионалов 

    Большинство функций Win32, возвращающих те или иные описатели, при 
неудачном вызове дают NULL. Но CreateFik— исключение и в таких случаях 
возвращает идентификатор INVALID_HANDLE_VALUE, определенный как OxFFFFFFFF. 

    Этап 2. Создание объекта ядра «проекция файла» 

    Вызвав CreateFik, Вы указали операционной системе, где находится 
физическая память проекции файла (file mapping): на диске, в сети, на приводе 
CD-ROM или в другом месте. Теперь сообщите системе, какой объем физической 
памяти нужен для объекта «проекция файла». Для этого вызывается функция CreateFileMapping: 

    HANDLE CreateFileMapping(HANDLE hFile, LPSECURITY_ATTRIBUTES Ipsa, 
    DWORD fdwProtect, DWORD dwMaximumSizeHigh, 
    DWORD dwMaximumSizeLow, LPSTR IpszMapName); 

    Первый параметр, hFile, идентифицирует описатель файла, который Вы хотите 
спроецировать на адресное пространство процесса. Этот описатель Вы получили на 
предыдущем этапе, вызвав CreateFile. Параметр Ipsa — указатель на структуру 
SECURITY_ATTRIBUTES; обычно для установки защиты по умолчанию ему 
присваивается NULL. 
    Как я уже говорил в начале этой главы, создание файла, проецируемого в 
память, аналогично резервированию региона адресного пространства с последующей 
передачей ему физической памяти. Разница лишь в том, что физическая память для 
проецируемого файла — сам файл на диске, и для него не нужно выделять 
пространство в страничном файле. При создании объекта «проекция файла» система 
не резервирует регион и не увязывает его с физической памятью из файла (как 
это сделать, я расскажу в следующем разделе). Но как только дело дойдет до 
отображения физической памяти на адресное пространство процесса, системе 
понадобится точно знать атрибут защиты, присваиваемый страницам физической 
памяти. Поэтому в fdwProtect надо указать желательные атрибуты защиты. Обычно 
используют такие: 

    Атрибут защиты     Описание 

    PAGE_READONLY    Отобразив объект «проекция файла» на адресное 
пространство, можно считывать данные из файла. При этом Вы должны были 
передать в функцию CreateFile флаг GENERIC_READ. 
    PAGE_READWRITE   Отобразив объект «проекция файла» на адресное 
пространство, можно считывать данные из файла и записывать их. При этом Вы должны были 
передать в функцию CreateFile флаги GENERIC_READ ! GENERIC_WRITE. 
    PAGE_WRITECOPY   Отобразив объект «проекция файла» на адресное 
пространство, можно считывать данные из файла и записывать их. Запись приведет к созданию 
закрытой копии страницы. При этом Вы должны были передать в функцию CreateFile 
либо GENERIC_READ, либо GENERIC_READ ! GENERIC_WRITE. 
    В Windows 95 функции CreateFileMapping можно передать флаг PAGE_WRITECOPY; 
тем самым Вы скажете системе передать физическую память из страничного файла. 
Эта память резервируется для копии информации из файла данных, и лишь 
модифицированные страницы действительно записываются в страничный файл. 
Изменения не распространяются на исходный файл данных. Результат применения 
флага PAGE_WRITECOPY одинаков в Windows NT и в Windows 95. 

    .Глава 8. Файлы, проецируемые в память 171 

    Кроме рассмотренных выше атрибутов защиты страницы, существует еще и 
четыре атрибута секции (section attributes); их можно ввести в параметр 
fdwProtect функции CreateFileMapping побитовой операцией OR. Секция (section) 
— всего лишь еще одно название проекции (memory mapping). 
    Первый из этих атрибутов, SEQJMOCACHE, сообщает системе, что никакие 
страницы файла, спроецированного в память, кэшировать не надо. В результате 
при записи данных в файл система будет обновлять данные на диске чаще 
обычного. Этот флаг, как и атрибут защиты PAGE_NOCACHE, предназначен для 
разработчиков драйверов устройств и обычно в приложениях не используется. 

    Windows 95 игнориггует флаг SEC_NOCACHE. 

    Второй атрибут, SEC_IMAGE, указывает системе, что данный файл — 
Win32-nepeHOсимый исполняемый файл (Win32 portable executable file, РЕ-файл). 
Отображая этот файл на адресное пространство процесса, система просматривает 
содержимое файла, чтобы определить, какие атрибуты защиты следует присвоить 
различным страницам проецируемого образа (mapped image). Например, раздел кода 
РЕ-файла обычно проецируется с атрибутом PAGE_EXECUTE_READ, тогда как данные 
этого же файла — с атрибутом PAGE_READWRITE. Атрибут SEC_IMAGE заставляет 
систему спроецировать образ файла и автоматически подобрать подходящие 
атрибуты защиты страниц. 

    Windows 95 игнорирует флаг SEC_IMAGE. 

    Последние два атрибута (SEC_RESERVE и SEC_COMMIT) взаимоисключают друг 
друга и неприменимы для проецирования в память файла данных. Эти флаги мы 
рассмотрим в разделе «Совместный доступ процессов к данным через механизм 
проецирования». CreateFileMappmg их игнорирует. 
    Следующие два параметра этой функции (dwMaximumSizeHigh и 
dwMaximumSizeLow) самые важные. Основное назначение CreateFileMappmg — 
убедиться, что для объекта «проекция файла» доступен нужный объем физической 
памяти. Вот почему мы передаем системе максимальный размер файла в байтах. Так 
как Win32 позволяет работать с файлами, размер которых выражается 
64-разрядными числами, в параметре dwMaximumSizeHigh указывают старшие~32 
бита, а в dwMaximumSizeLow— младшие 32 бита этого значения. Для файлов 
размером 4 Гб и менее dwMaximumSizeHigh всегда равен нулю. Наличие 
64-разрядного значения подразумевает, что Win32 способен обрабатывать файлы 
длиной до 18 экзабайтов (exabyte, ЕВ). (1 экзабайт равен квинтиллиону, или 1 
152 921 504 606 846 976 байтам.) 
    Для создания объекта «проекция файла» таким, чтобы он отражал текущий 
размер файла, передайте в обоих параметрах нули. Так и следует поступить, если 
Вы собираетесь ограничиться считыванием или как-то обработать файл, не меняя 
его размера. Для дозаписи данных в файл выбирайте его размер максимальным, 
чтобы оставить пространство «для маневра». 
    Если Вы еще следите за моими рассуждениями, то, должно быть, подумали: 
что-то тут не все ладно. Очень, конечно, мило, что Win32 поддерживает файлы и 
объекты «проекция файла» размером вплоть до 18 ЕВ, но как это ты, интересно, 
собираешься спроецировать такой файл на адресное пространство процесса, 
ограниченное 4 Гб, из которых и использовать-то можно только 2 Гб? На этот 
вопрос я отвечу в следующем разделе. 

    172 _____________________________Windows для профессионалов 

    Чтобы Вы досконально разобрались, как работают функции CreateFile и 
CreateFileMapping, предлагаю один эксперимент. Возьмите код, приведенный ниже, 
соберите его (откомпилируйте и скомпонуйте ЕХЕ-файл) и запустите его под 
отладчиком. Пошагово выполняя этот код, переключитесь в окно командного 
процессора и запросите содержимое каталога «С:\». Обратите внимание на 
изменения, происходящие в каталоге при выполнении каждого оператора. 

    int WINAPI WinMain(HINSTANCE hInstExe, HINSTANCE hInstExePrev, 
    LPSTR pszCmdLine, int nCmdShow) { 
    // перед выполнением этого оператора, в каталоге С:\ 
    // еще нет файла "MMFTest.Dat" 
    HANDLE hrile = CreateFlle("C:\\MMFTest.Dat", 
    GENERIC_READ ; GENERIC_WRITE, 
    FILE_SHARE_REAO ; FILE_SHARE_WRITE. NULL. CREATE_ALWAYS, 
    FILE_ATTRIBUTE_NORMAL, NULL): 
    // перед выполнением этого оператора файл MMFTest.Dat существует, 
    // но имеет нулевую длину 
    HANDLE nfilemap = CreateFileMapping(hfile, NULL, PAGE_READWRITE, 
    0, 100, NULL): 
    
    // после выполнения предыдущего оператора размер файла MMFTest.Dat 
    // возрастает до 100 байтов 
    
    // очистка 
    CloseHandle(hfilemap); 
    CloseHandle(hfile); 
    
    // по завершении процесса файл MMFTest,Dat останется 
    // на диске и будет иметь длину 100 байтов 
    return(O); 
    ,} 

    Вызов CreateFileMapping с флагом PAGE_READWRITE заставляет систему 
проверять, чтобы размер проецируемого файла данных был не меньше, чем указано 
в dwMaximumSizeHigh и dwMaximumSizeLow. Если файл окажется меньше заданного, 
CreateFileMapping увеличит его размер до указанной величины. Это делается 
специально, чтобы выделить физичес кую память перед использованием файла в 
качестве проецируемого в память. Если объею «проекция файла» создан с флагом 
PAGE_READONLY или PAGE_WRITECOPY, то размер, переданный в функцию 
CreateFileMapping, не должен превышать физический размер файла на диске (так 
как Вы не можете что-то дописать в файл). 
    Последний параметр функции CreateFileMapping — IpszMapName — строка с 
нулевым байтом в конце; в ней указывается имя данного объекта «проекция 
файла», которое исполь зуется для доступа к объекту другого процесса, и об 
этом мы поговорим позже. (Совместное использование объектов ядра рассмотрено и 
в главе 2, но с более абстрактных позиций.) Но обычно совместное использование 
проецируемого в память файла не требуется, и поэтому в данном параметре 
передают NULL.                                     ' 
    Система создает объект «проекция файла» и возвращает его описатель в 
вызвавший функцию поток. Если объект создать не удалось, возвращается нулевой 
описатель (NULL). И здесь еще раз обратите внимание на отличительную 
особенность CreateFile: при ошибке она возвращает не NULL, а идентификатор 
INVALID_HANDLE_VALUE (определенный как OxFFFFFFFF). 

    Глава 8. Файлы, проецируемые в память 173 


    Этап 3. Проецирование файловых данных на адресное пространство процесса 

    Когда объект «проекция файла» создан, нужно, чтобы система, зарезервировав 
регион адресного пространства под данные файла, передала их как физическую 
память, отображенную на регион. Это делает функция Map ViewOfnie:'          '"~ 

    LPVOID MapViewOfFile(HANDLE hFileMapplngObject, 
    DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, 
    DWORD dwFileOffsetLow, DWORD dwNumber-OfBytesToMap); 

    Параметр hFileMappingObject идентифицирует описатель объекта «проекция 
файла», возвращаемый предшествующим вызовом либо CreateFileMapping, либо 
OpenFileMapping (ее мы рассмотрим чуть позже). Параметр dwDesiredAccess 
идентифицирует вид доступа к данным. Все правильно: придется опять указывать, 
как именно мы хотим обращаться к файловым данным. Можно задать одно из четырех значений: 

    Значение               Описание 

    FILE_MAP_WRITE        Файловые данные можно считывать и записывать. Вы 
должны были передать в функцию CreateFileMapping атрибут PAGE_READWRITE. 
    FILE_MAP_READ        Файловые данные можно только считывать. Вы должны 
были вызвать CreateFileMapping с любым из следующих атрибутов: PAGE_READONLY, 
PAGE_READWRITE или PAGE_WRITECOPY. 
    FILE_MAP_ALL_ACCESS  To же, что и FILE_MAP_READ. 
    FILE_MAP_COPY        Файловые данные можно считывать и записывать. Запись 
приводит к созданию закрытой копии страницы. Вы должны были вызвать 
CreateFileMapping с любым из следующих атрибутов: PAGE_READONLY, 
PAGE_READWRITE или PAGE_WRITECOPY. 
    Кажется странным и немного раздражает, что Win32 требует бесконечно 
указывать все эти атрибуты защиты. Могу лишь предположить, что это сделано, 
чтобы приложение максимально полно контролировало защиту данных. 
    Остальные три параметра относятся к резервированию региона адресного 
пространства и к отображению на него физической памяти. При этом не 
обязательно проецировать на адресное пространство весь файл сразу. Напротив, 
можно спроецировать лишь малую его часть, которая в таком случае называется 
представлением (view) — теперь-то Вам, наверное, понятно, откуда произошло 
название функции Map ViewOfFile. 
    Проецируя на адресное пространство процесса представление файла, нужно 
сделать две вещи. Во-первых, сообщить системе, какой байт файла данных считать 
в представлении первым. Для этого предназначены параметры dwFileOffsetHigh и 
dwFileOffsetLow. Поскольку Win32 поддерживает файлы длиной до 18 ЕВ, 
приходится определять смещение в файле как 64-разрядное число: старшие 32 бита 
передаются в параметре dwFileOffsetHigh, а младшие 32 — в параметре 
dwFileOffsetLow. Заметьте, что смещение в файле должно быть четным числом и 
кратно гранулярности выделения ресурсов в данной системе. (В настоящее время 
во всех реализациях Win32 она составляет 64 Кб.) О гранулярности выделения 
ресурсов см. раздел «Системная информация» в главе 6. 
    Во-вторых, от Вас потребуется указать размер представления, т. е. сколько 
байтов файла данных должно быть спроецировано на адресное пространство. Это 
равносильно тому, как если бы Вы задали размер региона, резервируемого в 
адресном пространстве. Размер указывается в параметре dwNumberOfBytesToMap, 
который представляет собой 32-бит

    174 _______________________     __ Windows для профессионалов 

    ную переменную, так как размер представления не может превышать 4 Гб. А 
если в этом параметре задан 0, система попытается спроецировать представление, 
начиная с указанного смещения и до конца файла. 

    В Windows 95, если Map ViewOfFile не найдет регион, достаточно большой для 
размещения всего объекта «проекция файла», возвращается NULL — независимо от 
того, какой размер представления _былзапрошен. 

    wows/ В Windows NT функция Map ViewOfFile ищет регион, достаточно большой 
для размещения запрошенного представления, не обращая внимания на размер 
самого объекта «проекция файла». 

    Если при вызове MapViewOfFile указан флаг FILE_MAP_COPY, система передаст 
физическую память из страничного файла. Размер передаваемого пространства 
определяется параметром dwNumberOfBytesToMap. Пока Вы лишь считываете данные 
из представления файла, страницы, переданные из страничного файла, не 
используются. Но стоит какомунибудь потоку в Вашем процессе совершить попытку 
записи по адресу, попадающему в границы представления файла, как система тут 
же берет из страничного файла одну из переданных страниц, копирует в нее 
исходные данные и проецирует ее на адресное пространство процесса. Так что с 
этого момента потоки Вашего процесса начинают обращаться к локальной копии 
данных и теряют доступ к исходным данным. 
    Создав копию исходной страницы, система меняет ее атрибут защиты с 
PAGE_WRITECOPY на PAGE_READWRITE. Рассмотрим пример: 

    HANDLE hFile. hFileMapping: 
    BYTE bSomeByte. *pbFile; 
    
    // открываем файл, который мы собираемся спроецировать в память 
    hFile = CreateFile(lpszName, GENERIC_READ : GENERIC_WRITE, 0, NULL, 
    OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
    
    // создаем для файла объект "проекция файла" 
    hFileMapping = CreateFileMappingChFile, NULL. PAGE + WRITECOPY, О, 0, NULL); 
    // Проецируем представление файла с атрибутом "копирование при записи"; 
    // система передаст столько физической памяти из страничного файла, 
    // сколько нужно для размещения всего файла. Первоначально все страницы 
    // в представлении получат атрибут PAGE_WRITECOPY. 
    pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_COPY, О, О, О): 
    
    // считываем байт из представления файла 
    bSomeByte = pbFile[0]; 
    
    // При чтении система не трогает страницы, переданные из страничного файла. 
    // Страница сохраняет свой атрибут PAGE_WRITECOPY. 
    
    // записываем байт в представление файла 
    pbFile[0] = 0: 
    // При первой записи система берет страницу, переданную из страничного файла, 
    // копирует исходное содержимое страницы, расположенной по запрашиваемому адресу 
    // копирует исходное содержимое страницы, расположенной по запрашиваемому адресу 


    Глава 8. Файлы, проецируемые в память 175 
    

    // в памяти, и проецирует новую страницу (копию) на адресное пространство процесса. 
    // Новая страница получает атрибут PAGE_READWRITE. 
    
    // записываем еще один байт в представление файла 
    pbFile[1] = 0; 
    
    // поскольку теперь байт располагается на странице с атрибутом PAGE_READWRITE, 
    // система просто записывает его на эту страницу 
    
    // Закончив работу с представлением проецируемого файла, прекращаем проецирование. 
    // Функция UnmapViewOfFile обсуждается в следующем разделе. 
    UnmapViewOfFile(pbFile); 
    
    // Вся физическая память, взятая из страничного файла, возвращается в систему. 
    // Все, что было записано на эти страницы, теряется. 
    
    // "уходя, гасите свет" 
    CloseHandle(hFileMapping); 
    CloseHandle(hFile): 

    ~7 Как уже упоминалось. Windows 95 сначала передает проецируемому файлу 
физическую память из страничного файла. Однако запись модифицированных страниц 
в страничный файл происходит только при необходимости. 

    Этап 4. Отключение файла данных от адресного пространства процесса 

    Когда необходимость в данных файла (спроецированного на регион адресного 
пространства процесса) отпадет, освободите регион вызовом функции: 

    BOOL UnmapViewOfFile(LPVOID IpBaseAddress); 

    Ее единственный параметр, IpBaseAddress, указывает базовый адрес 
возвращаемого системе региона. Он должен совпадать со значением, полученным 
после вызова MapViewOfFile. Вы обязаны вызывать функцию UnmapViewOfFile. Если 
Вы не сделаете этого, регион не освободится до завершения Вашего процесса. И 
еще: повторный вызов Map ViewOfFile приводит к резервированию нового региона в 
пределах адресного пространства процесса, но ранее выделенные регионы не освобождаются. 
    Для повышения производительности при работе с представлением файла система 
буферизует страницы данных в файле и не обновляет немедленно дисковый образ 
файла (disk image of file). Когда же Вы^окончив работу с представлением файла, 
вызываете UnmapViewOfFile, система перекачивает все измененные данные, 
накопленные в памяти, в дисковый образ файла. При необходимости можно 
заставить систему записать все измененные данные в дисковый образ файла, 
вызвав функцию Flush ViewOfFile: 

    BOOL FlushViewOfFileCLPVOID IpBaseAddress, 
    DWORD dwNumberOfBytesToFlusn); 

    Она требует адрес проецируемого представления, возвращенный в предыдущем 
вызове функции MapViewOfFile; кроме того, нужно указать количество байтов, 
переписываемых на диск. Если FlushViewOfFile вызвана при отсутствии измененных 
данных, она вернет управление основной программе — вот и все. 
    В случае проецируемых файлов, физическая память которых расположена на 
сетевом диске, FlushViewOfFile гарантирует, что файловые данные будут 
перекачаны с рабочей стан

    176 _____________________________Windows для профессионалов 

    ции. Но она не гарантирует, что сервер, обеспечивающий доступ к этому 
файлу, запишет данные на удаленный диск, так как он может просто кэшировать 
их. Для подстраховки при создании объекта «проекция файла» и последующем 
проецировании его представления используйте флаг FILE_FLAG_WRITE_THROUGH. При 
открытии файла с этим флагом функция FlusbViewOfFile вернет управление только 
после сохранения на диске сервера всех файловых данных. 
    У UnmapViewOfFile есть одна особенность. Если первоначально представление 
было спроецировано с флагом FILE_MAP_COPY, любые изменения, внесенные Вами в 
файловые данные, на самом деле производятся над копией этих данных, хранящихся 
в страничном файле. Вызванной в этом случае функции UnmapViewOfFile нечего 
обновлять в дисковом файле, и она просто инициирует возврат системе страниц 
физической памяти. Все изменения в данных на этих страницах теряются. 
    Поэтому о сохранении измененных данных следует заботиться самостоятельно. 
Как? Для уже спроецированного файла можно создать еще один объект «проекция 
файла» с атрибутом PAGE_READWRITE и спроецировать его представление на 
адресное пространств! i процесса с флагом FILE_MAP_WRITE. Затем надо 
просмотреть первое представление, отыс кивая страницы с атрибутом 
PAGE_READWRITE. Найдя страницу с таким атрибутом, Вы анализируете ее 
содержимое и решаете: записывать ее или нет. Если обновлять файл не нужно, Вы 
продолжаете просмотр страниц. А для сохранения страницы с измененными данными 
достаточно вызвать MoveMemory и скопировать страницу из первого представления 
файла во второе. Поскольку второе представление создано с атрибутом 
PAGE_READWRITE, функция MoveMemory обновит содержимое дискового файла. Так что 
этот метод вполне пригоден для анализа изменений и сохранения их в файле. 
    Windows 95 не поддерживает атрибут защиты «копирование при записи», 
поэтому при просмотре первого представления файла, проецируемого в память, Вы 
не сможете проверить страницы по флагу PAGE_READWRITE. Вам придется 
разработать свой метод. 

    Этапы 5 и 6. Закрытие объектов «проекция файла» и «файл» 

    Любой открытый объект ядра должен быть закрыт, иначе в процессе начнется 
утечка ресурсов. Конечно, по его завершении система автоматически закроет 
объекты, оставленные от крытыми. Но если процесс поработает еще какое-то 
время, может накопиться слишком много незакрытых описателей. Поэтому 
старайтесь придерживаться правил «хорошего тона» и составляйте код так, чтобы 
открытые объекты всегда закрывались, как только станут не нужны. Чтобы закрыть 
объекты «проекция файла» и «файл», дважды вызовите функцию CloseHandle. 
    Рассмотрим это подробнее на фрагменте псевдокода, проецирующего файл в память: 

    HANDLE hFile, hFileMapping: 
    PVOID pFile; 
    hFile = CreateFile(..,); 
    hFileMapping = CreateFileMapping(hFile,... ); 
    pFile = MapViewOfFile(hFileMapping,... ); 
    // оаботаем с файлом, спроецированным в память 


    _____________________Глава 8. Файлы, проецируемые в память 177 


    UnmapViewOfFile(pFile); 
    CloseHandle(hFileMapping)'9 
    CloseHandle(hFile);      J 

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

    HANDLE hFile, hFileMapping; 
    PVOID pFile; 
    hFile = CreateFile(,,, ): 
    hFileMapping = CreateFileMapping(hFile,...); 
    CloseHandle(hFile); 
    pFile = MapViewOfFile(hFileMapping.,., ): 
    CloseHandle(hFileMapping): 
    // работаем с файлом, спроецированным в память 
    UnmapViewOfFile(pFile): 

    При операциях с проецируемыми файлами чаще всего так и делают: открывают 
файл, создают объект «проекция файла» и с его помощью проецируют представление 
файловых данных на адресное пространство процесса. Поскольку система 
увеличивает внутренние счетчики объектов «файл» и «проекция файла», их можно 
закрыть в начале кода, тем самым исключив возможную утечку ресурсов. 
    Если Вы будете создавать из одного файла несколько объектов «проекция 
файла» или проецировать несколько представлений этого объекта, применить 
функцию CloseHandle в начале кода не удастся — описатели еще понадобятся Вам 
для дополнительных вызовов CreateFileMapping и MapViewOfFile. 

    Обработка массивных файлов 

    Выше я обещал рассказать, как спроецировать на адресное пространство 
размером 4 Гб файл длиной 18 ЕВ. Так вот, этого сделать нельзя. Вам придется 
проецировать не весь файл, а его представление, содержащее лишь небольшую 
часть данных. Вы начнете с того, что спроецируете представление самого начала 
файла. Закончив обработку данных в этом представлении, отключите его и 
спроецируете представление следующей части файла — и так, пока не будет 
обработан весь файл. Конечно, это делает работу с массивными файлами, 
проецируемыми в память, не слишком удобной, но утешимся тем, что длина 
большинства файлов намного меньше 4 Гб. 
    Рассмотрим сказанное на примере файла в 8 Гб. Ниже приведен текст 
подпрограммы, позволяющей в несколько этапов подсчитывать, сколько раз 
встречается буква / в том или ином ASCII-файле (признаюсь, я неравнодушен к 
этой букве): 

    __int64 WINAPI CountJs (void) { 
    HANDLE hFile, hFileMapping; 
    PBYTE pbFile; 
    SYSTEM_INFO si; 
    __int64 qwFlleSize, qwFiledffset = 0, qwNumOfJs = 0; 

    см. след. стр. 


    178 ____________________________Windows для професс 


    DWORD dwFileSizeHigh: 
    DWORD dwByte, dwBytesInBlock; 
    DWORD dwErr: 
    // нам нужно узнать гранулярность выделения ресурсов в данной системе, 
    // поскольку начальные границы представлений всегда начинаются с такого 
    // смещения в файле данных, которое кратно этой величине 
    GetSystemInfo(&si); 
    
    // открываем файл данных 
    hFile = CreateFile("c:\\HugeFile.Big", GENERIC_READ, 
    FILE_SHARE_READ, NULL, OPEN_EXISTING, 
    FILE_FLAG_SEQUENTIAL_SCAN, NULL); 
    
    if(hFile == INVALID_HANDLE_VALUE) 
    return(O); 
    
    // создаем объект "проекция файла" 
    hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0. 0. NULL): 
    
    // доступ к описателю объекта "файл" нам больше не нужен 
    CloseHandle(hFile): 
    
    if (hFileMapping == NULL ) { 
    return(O): 
    qwFileSize = GetFileSize(hFile, &dwFileSizeHigh); 
    qwFileSize += (((__int64) dwFileSizeHigh) « 32); 
    while (qwFileSize > 0) { 
    
    // определяем, сколько байтов надо спроецировать 
    if (qwFileSize < si.dwAllocationGranularity) 
    dwBytesInBlock = (DWORD) qwFileSize; 
    else 
    dwBytesInBlock = si.dwAllocationGranularity; 
    pbFile = MapViewOfFile(hFileMapping, 
    
    FILE_MAP_READ,                      // желательный вид доступа 
    (DWORD) (qwFileOffset » 32),       // начальный байт 
    (DWORD) (qwFileOffset & OxFFFFFFFF),// в файле 
    dwBytesInBlock);                    // число проецируемых байтов 
    
    // подсчитываем количество букв J в этом блоке 
    for (dwByte = 0; dwByte < dwBytesInBlock; dwByte++) { 
    if (pbFile[dwByte] == •J') 
    qwNumOfJs++; 
    } 
    // прекращаем проецирование представления, чтобы в адресном пространстве 
    // не образовалось несколько представлений одного файла 
    UnmapViewOfFile(pbFile); 


    ______________________Глава 8. Файлы, проецируемые в память 179 
    

    // переходим к следующей группе байтов в файле 
    qwFileOffset += dwBytesInBlock; 
    qwFileSize -= dwBytesInBlock: 
    CloseHandle(hFileMapping); 
    return(qwNumOfJs); 
    } 

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

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

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

    /\    Win32 позволяет создавать из одного и того же файла данных несколько 
объек/ ' \ тов «проекция файла». Но в этом случае у Вас нет гарантий, что 
содержимое /внимдниЕ\ представлений этих объектов будет когерентно. Такую 
гарантию Win32 дает только для нескольких представлений^одного..о.бъекта.
«проек1и1я файла». 

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

    180 ______________________________Windows для профессионалов 


    VVINDOW^ Windows 95 не поддерживает когерентность файлов в той же мере, 
что \ / 

    Windows NT. Взгляните для примера на следующий код: 
    
    BYTE bBuf[1]:           ) 
    DWORD dwNumBytesRead; 
    HANDLE hFile = CreateFlle(...); 
    HANDLE hFileMap = CreateFlleMapping(hFile,...); 
    PBYTE pbData = MapViewOfFile(nFileMap,...); 
    
    // меняем первый байт файла на заглавную букву "X" 
    pb0ata[0] = 'Х'; 
    
    // считываем первый байт файла в буфер 
    ReadFile(hFile, bBuf, 1, &dwNumBytesRead, NULL); 
    
    // проверяем: совпадает ли первый байт файла 
    // с байтом, прочитанным в буфер 
    if (pbData[0] == bBuf[0]) { 
    
    // то ли Windows 95, то ли нет 
    } else { 
    
    // точно Windows 95 
    } 

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

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

    Программа FileRev (FILEREV.EXE) (см. листинг на рис. 8-1) демонстрирует, 
как с помощью механизма проецирования расположить в обратном порядке 
содержимое текстового ANSI- или Unicode-файла. Файлы исходного кода и ресурсов 
этой программы находятся в каталоге FILEREV на компакт-диске, прилагаемом к 
книге. Запустив программу. Вы увидите такое окно: 

    _____________________ Глава 8. Файлы, проецируемые в память 181 

    Выбрав имя файла и щелкнув кнопку Reverse File Contents (обратить 
содержимое файла), Вы активизируете функцию, которая меняет порядок символов в 
файле на обратный. Программа корректно работает только с текстовыми файлами. В 
какой кодировке создан текстовый файл (ANSI или Unicode), FileRev определяет 
вызовом IsTextUnicode (см. главу 17). 

    ^VINDOW^ в Windows 95 функция IsTextUnicode, по сути, не реализована; она 
просто воз\ / вращает FALSE, а последующий вызов GetLastError дает 
ERROR_CALL_NOT_ v    IMPLEMENTED. Это значит, что программа FileRev, 
выполняемая в Windows 95, всегда считает, что файл содержит текст в ANSI-кодировке. 

    После щелчка кнопки Reverse File Contents программа создает копию файла с 
именем FILEREV.DAT. Делается это для того, чтобы не испортить исходный файл, 
изменив в нем порядок следования байтов на обратный. Далее программа вызывает 
функцию FileReverse— она меняет порядок байтов на обратный и после этого 
вызывает CreateFile, открывая FILEREV.DAT для чтения и записи. 
    Как я уже говорил, простейший способ «перевернуть» содержимое файла — 
вызвать функцию _strrev из стандартной библиотеки С. Но для этого последний 
символ в строке должен быть нулевой. И поскольку текстовые файлы не 
заканчиваются нулевым символом, программа FileRev подписывает его в конец 
файла. Для этого сначала вызывается функция GetFileSize: 

    DwFileSize = GetFile3ize(hFiie, NULL); 

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

    ((LPSTR) lpvFiie)[dwFile8ize / sizeof(CHAR)] = 0; 
    _strrev(lpvFlle); 

    В текстовом файле каждая строка завершается символами возврата каретки 
('\г') и перевода строки ('\п'). К сожалению, после вызова функции _strrev3TH 
символы тоже меняются местами. Поэтому для загрузки преобразованного файла в 
текстовый редактор придется заменить все пары «\п\г» на исходные «\г\п». В 
программе этим занимается следующий цикл: 
    
    // находим первое вхождение '\п' 
    Ipch = strchr(lpvFile, '\n'); 
    while (Ipch != NULL) { 
    *lpch++ = '\r';     // меняем '\п' на '\r' 
    
    *lpch++ = '\n';     // меняем '\г' на '\n' 
    // находим следующее вхождение 
    Ipch = strchr(lpch, '\n'); 

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

    182 ______________________________Windows для профессионалов 

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

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

    /\    Заметьте, что SetEndOfFile нужно вызывать после отмены проецирования 
пред/ ' \ ставления и закрытия объекта «проекция файла», иначе возникнет 
ошибка /внимдниЕ\ ERROR_USER_MAPPED_FILE. Эта ошибка означает, что операцию 
перемещения в конец файла нельзя выполнить на файле, сопоставленном с объектом 
«проекция файла». 

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

    'ОДе Edit Search Help 
    ———————————————————————————————————————————————————————— 
    /////////////////////// eliF fO dnE ///////////////////////   J 
    ) 
    ;)B(nruter 
    ;)corP_9lD .LI-UN 
    
    ,)UERELIF_DDI(ECRUOSERTNIEKflM ,exEtsnih(xoBgolaiD 
    ;)(59NIlilREDNUEDOCINUFINRfllilhc 
    ( )uohSdl»Cn tni .eniLdmCzsp RTSTPL ,uerPtsnih ECNflTSNIH 
    ,exEtsnih ECNflTSNIH( niaMnililt_ IPflNIIil tni 
    ///////////////////////////////////////////////////////////// 
    ) 
    ;)ESLftF(nruter                                                  ,1 
    ,»l                  __                 l_____^T 
    FILEREV.C 
    /*.***,*,..*...,,,....*...**..***.****.*********»*.,*,,.**...,..*.....,,*.,,,.. 
    
    Модуль: FileRev.С 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    #******»****************«**«***««*****************************.tt***-..**t**-t/ 
    ^include "..\CmnHdr.H"           /* см. приложение В */ 
    ffinclude <windows.h> 
    #include <windowsx.h> 
    ffinclude <tchar.h> 
    #include <string.h>              // для _strrev 
    #include "Resource.H" 
    //////////////////////'•- • . 1.1/11//.1\ ••;.-,.i'. "•^1:     '  1 

    CM. Ulftl. CHlp. 

    Рис. 8-1. Листинг программы FileRev 


    _____________________Глава 8. Файлы, проецируемые в память 183 


    ) 

    РИС. 8-1 (продолжение) 

    ffdefine FILENAME . _TEXT("FILEREV.DAT") 
    1|^ BOOL FileReverse (LPCTSTR pszPathname, PBOOL pfIsTextUnicode) { 
    ^ili';. 
    Ill     HANDLE hFile, hFileMap; 
    jl'    LPVOID IpvFile; 
    ||    LPSTR IpchANSI;     // всегда ANSI 
    ||||'    LPWSTR IpchUnicode; // всегда Unicode 
    в|    DWORD dwFileSize; 
    ill    int iUnicodeTestFlags = -1:      // использовать все проверки 
    Н|''    // предполагаем, что текст в Unicode 
    ШЦ:    *pfIsTextUnicode = FALSE; 
    jit    // открываем файл для чтения и записи 
    ||r    hFile = CreateFile(pszPathname, GENERIC_WRITE ; GENERIC_READ, 
    |||       О, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 
    if (hFile == INVALID_HANOLE_VALUE) { 
    // открыть файл не удалось 
    chMB(__TEXT("File could not be opened.")); 
    return(FALSE); 
    i 
    
    // получаем размер файла; я предполагаю, что файл меньше 4 Гб 
    dwFileSize = GetFileSize(hFile, NULL); 
    
    // Создаем объект "проекция файла". Он на 1 символ больше, чем размер 
    // файла, чтобы можно было дописать нулевой символ в конец файла для 
    // корректного завершения строки. Поскольку пока еще неизвестно, содержит 
    // ли файл ANSI- или Unicode-символы, я предполагаю худшее и добавляю 
    // размер WCHAR вместо CHAR. 
    
    hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 
    0, dwFileSize + sizeof(WCHAR), NULL): 
    if (hFileMap == NULL) { 
    // открыть объект "проекция файла" не удалось 
    chMB(__TEXT("File map could not be opened.")); 
    CloseHandle(hFile); 
    return(FALSE); 
    } 
    
    // получаем адрес, по коюрому проецируется в память первый байт файла 
    IpvFile = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0. О, 0); 

    CM. t7;f(i. i'lnp. 


    184 ______________________________Windows для профессионалов 


    РИС. 8-1 (продолжение) 

    if (IpvFile == NULL) { 
    // спроецировать представление файла не удалось 
    chMB(__TEXT("Could not map view of file.")); 
    CloseHandle(hFileMap); 
    CloseHandle(hFile); 
    return(FALSE); 
    } 
    /'/ делаем обоснованное предположение и том, в какой кодировке 
    // содержится в файле текст; ANSI или Unicode 
    *pfIsTextUnicode = IsTextUnicode(lpvFile, dwFileSize, &iUnicodeTestFlags); 
    if (!*pfIsTextUnicode) { 
    // при дальнейших операциях с файлом явно используем вместо 
    // Unicode-функций ANSI-функции, так как приложение (в ANSI- оно или 
    // в Unicode-формате) обрабатывает ANSI-файл 
    
    // записываем в конец файла нулевой символ 
    IpchANSI = (LPSTR) IpvFile; 
    IpchANSICdwFileSize / sizeof(CHAR)] = 0; 
    
    // "переворачиваем" содержимое файла 
    strrev(lpchANSI): 
    
    // преобразуем все комбинации "\п\г" обратно в "\г\п", чтобы 
    // сохранить нормальную последовательность кодов завершения 
    // строки в текстовом файле 
    IpchANSI = strchrO.pchANSI, '\n'):  // ищем первое вхождение '\п' 
    
    while (IpchANSI i= NULL) { 
    // вхождение найдено... 
    *lpchANSI++ = 'V: // заменяем '\п' на '\r' 
    
    *lpchANSI++ = '\n'; // заменяем '\г' на '\п' 
    IpchANSI = strchr(lpchANSI, '\n'):  // ищем следующее вхождение 
    } 
    } else { 
    // при дальнейших операциях с файлом явно используем вместо 
    // ANSI-функций Unicode-функции, так как приложение (в ANSI- оно или 
    // в Unicode-формате) обрабатывает Unicode-файл 
    
    // записываем в конец файла нулевой символ 
    IpchUnicode = (LPWSTR) IpvFile: 
    lpchUnicode[dwFileSize / sizeof(WCHAR)] = 0; 
    if ((lUnicodeTestFlags & IS_TEXT_UNICODE_SIGNATURE) != 0) { 
    // если первый символ - маркер порядка байтов Unicode (OxFEFF), 

    см. след. стр. 


    _____.._.___________Глава 8. Файлы, проецируемые в память 185 


    РИС. 8-1 (продолжение) 

    I / то оставим этот символ в начале файла 
    lpchUnicode++; 
    1 
    // "переворачиваем" содержимое файла 
    _wcsrev(lpchUnicode): 
    // преобразуем все комбинации "\п\г" обратно в "\с\п", чтобы 
    // сохранить нормальную последовательность кодов завершения 
    // строки в текстовом файле 
    IpchUnicode = wcscnr(lpchUnicode, L'\n'); // ищем первое вхождение '\п' 
    
    while (IpchUnicode != NULL) { 
    // вхождение найдено... 
    •lpchUnicode++ = L'V:// заменяем '\п' на 'V 
    *lpchUnicode++ = L'\n';// заменяем '\г' на '\п' 
    IpchUnicode = wcschr(lpchUnicode, L'\n'); // ищем следующее вхождение 
    } 
    
    // очищаем все перед завершением 
    UnmapViewOfFile(lpvFile); 
    CloseHandle(hFileMap); 
    // удаляем добавленный ранее концевой нулевой байт, и для этого помещаем 
    // указатель файла в конец, не считая сам нулевой байт, а затем даем 
    // команду установить в этом месте конец файла 
    SetFllePointerChFile, dwFileSize, NULL, FILE_BEGIN); 
    // SetEndOfFile нужно вызывать после закрытия объекта ядра 
    // "проекция файла" 
    SetEndOfFile(hFile): 
    CloseHandle(hFile); 
    return(TRUE): 
    
    /////////////////////////////////////////////////////////////////////////////// 
    BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_FILEREV, IDI_FILEREV); 
    // инициализируем диалоговое окно - отключаем все, недоступные пока 
    // управляющие элементы 
    EnableWindow(GetDlgItem(hwnd, IDC_REVERSE), FALSE); 

    см. след. стр. 


    186 _____________________________Windows для профессионалов 


    РИС. 8-1 (продолжение) 

    relurin. I HUL); 
    1 
    { 
    i / i / / i i i : 
    void Dlg_OnCommand (HWND nwnd, int id, HWND hwndCtl. UINT codeNotify) { 
    TCHAR szPathnameLMAX_PATH]; 
    _OPENFILENAME ofn: 
    BOOL fIsTextUnicode; 
    STARTUPINFO si: 
    PROCESS_INFORMATION pi; 
    switch (id) { 
    case IDC_FILENAME: 
    EnableWindow(GetDlgItem(hwnd, IDC_REVERSE), 
    Edit_GetTextLength(hwndCtl) > O): 
    break; 
    case IDC_FILESELECT: 
    chINITSTRUCT(ofn, TRUE); 
    ofn,hwndOwner = hwnd; 
    ofn.IpstrFile = szPathname: 
    ofn.lpstrFile[0] = 0; 
    . ofn.nMaxFile = chDIMOF(szPathname): 
    ofn. lpstr-ТШе = __TEXT( "Select file for reversing"); 
    ofn.Flags = OFN_EXPLORER i OFN_FILEMUSTEXIST; 
    GetOpenFileName(&ofn); 
    SetDlgItemText(hwnd, IDC_FILENAME, ofn.IpstrFile); 
    Set Focus(GetDlgItem(hwnd, IDC_REVERSE)); 
    break; 
    case IDC_REVERSE: 
    GetDlgIternText(hwnd, IDC_FILENAME, szPathname, chDIMOF(szPathname)): 
    // копируем исходный файл в FILEREV.DAT, чтобы не повредить данные 
    if (!CopyFile(szPathname, FILENAME, FALSE)) { 
    // копирование не удалось 
    chMB(__TEXT("New file could not be created.")): 
    break: 
    if (FileReverse(FILENAME, OTsTextUnicode)) { 
    // покажем, в какой кодировке содержатся символы в файле 
    SetDlgItemText(hwnd, IDC_TEXTTYPE, 
    fIsTextUnicode ? __TEXT("Unicode") : __TEXT("ANSI")); 
    // запустим Notepad, чтобы увидеть плоды своих трудов 

    т. след. стр. 


    ______________________Глава 8. Файлы, проецируемые в память 187 


    РИС. 8-1 (продолжение) 

    ChINITSTRUCT(si, TRUE): 
    // si.wShowWindow = SW_SHOW; 
    // sl.dwFlags = STARTF_USESHOWWINDOW: 
    if (CreateProcess(NULL, _TEXT("NOTEPAD.EXE ") FILENAME, 
    NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) ; 
    CloseHandle(pi.hThread): 
    CloseHandle(pi.hProcess): 
    ^ break: 
    case IDCANCEL: 
    EndDialog(hwnd, id); 
    break; 
    i / 
    BOOL CALLBACK Dig^Proc (HWNU hwnd, UINT uMsg, WPARAM wParam, LPARAM 
IParaifi) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM_INITOIALOG,  Dlg_0nlnit0ialog); 
    chHANDLE_DLGMSG(hwnd, WM_COMMAND,  Dlg_OnCom[nand); 
    } 
    return(FALSF); 
    int W1NAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    chWARNIFUNICODEUNDERWIN95(): 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_FILEREV). NULL, Dlg_Proc): 
    return(O); 
    } 
    ////////////////////////////////// конец файла i'1111111//111111 
/1111111111 / i 11 11 


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

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

    LPVOID MapViewOfFileEx(HANDLE hFileMappingObject, 
    DWORD dwDesiredAccess, DWORD dwFlleOffsetRigh, 
    DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap, 
    I D\/m"n 1 пйо роЛ-ЧН гос-с- \ 


    188 ______________________________Windows для профессионалов 


    Все параметры и возвращаемое этой функцией значение идентичны применяемым 
в Map ViewOfFile, кроме последнего — IpBaseAddress. В нем можно задать 
начальный адрес файла, проецируемого в память. Как и в случае VirtualAlloc, 
базовый адрес должен быть четным значением, кратным гранулярности выделения 
ресурсов в системе (обычно 64 Кб), иначе MapViewOfFileEx вернет NULL, сообщив 
тем самым об ошибке. 
    Если Вы укажете базовый адрес, не кратный гранулярности выделения 
ресурсов, то MapViewOfFileEx в Windows NT завершится с ошибкой, и GetLastError 
вернет код 1132 (ERROR_MAPPED_ALIGNMENT), а в Windows 95 базовый адрес будет 
округлен до меньшего значения, кратного гранулярности выделения ресурсов. 
    Если система не в состоянии спроецировать файл по этому адресу (чаще 
всего'из-за того, что файл слишком большой и перекрывает другие регионы 
зарезервированного адресного пространства), функция также возвращает NULL. В 
этом случае она не пытается подобрать подходящий диапазон адресов, в котором 
мог бы разместиться файл. Но если Вы укажете NULL в параметре IpBaseAddress, 
она поведет себя идентично Map ViewOfFile. 
    MapViewOfFileEx удобна, когда механизм проецирования файлов в память 
применяется для совместного доступа нескольких процессов к одним данным. 
Поясню. Допустим, нужно спроецировать файл в память по определенному адресу; 
при этом два или более приложений совместно используют одну группу структур 
данных, содержащих указатели на другие структуры данных. Отличный тому пример 
— связанный список. Каждый его узел, или элемент, хранит адрес другого узла. 
Для просмотра списка надо узнать адрес первого узла, а затем сделать ссылку на 
тот его подэлемент, что содержит адрес следующего узла. Но при использовании 
файлов, проецируемых в память, это весьма проблематично. 
    Если один процесс подготовил в проецируемом файле связанный список, а 
затем разделил его с другим процессом, не исключено, что второй процесс 
спроецирует этот файл в своем адресном пространстве на совершенно иной регион. 
А дальше будет вот что. Попытавшись просмотреть связанный список, второй 
процесс проверит первый узел списка, прочитает адрес следующего узла и, сделав 
на него ссылку... ничего не получит. Дело в том, что адрес следующего элемента 
в первом узле некорректен для второго процесса. 
    У этой проблемы два решения. Во-первых, второй процесс, проецируя файл со 
связанным списком на свое адресное пространство, может вызвать MapViewOfFileEx 
вместо Map ViewOfFile. Для этого второй процесс должен знать адрес, по 
которому файл спроецирован в адресном пространстве первого процесса на момент 
создания списка. Если оба приложения разработаны с учетом взаимодействия (а 
так чаще всего и делают), нужный адрес может быть просто заложен в код этих 
программ, или же один процесс будет как-то уведомлять другой (скажем, посылкой 
сообщения в окно). 
    А можно и так. Процесс, создающий связанный список, должен записывать в 
каждый узел смещение следующего узла в пределах адресного пространства. Тогда 
программа, чтобы получить доступ к каждому узлу, будет суммировать это 
смещение с базовым адресом проецируемого файла. Несмотря на простоту, этот 
способ не лучший: дополнительные операции замедлят работу программы и увеличат 
объем ее кода (так как компилятор для выполнения всех вычислений, естественно, 
сгенерирует дополнительный код). Кроме того, при этом способе вероятность 
ошибок значительно выше. Тем не менее он имеет право на существование, и 
поэтому компиляторы Microsoft поддерживают указатели со смещением относительно 
базового значения (based-pointers), для чего предусмотрено ключевое слово _based. 

    VwiNDows/ \95/  В Windows 95 при вызове MapViewOfFileEx следует указывать 
адрес в диапазоне от 0х80000000 до OxBFFFFFFF, иначе функция вернет NULL. 

    ______________________Глава 8. Файлы, проецируемые в память 189 


    VwiNDowsy В Windows NT при вызове MapViewOfFileEx следует указывать адрес 
в диапазо\NL/ не от 0х00010000 до Ox7FFFFFFF, иначе функция вернет NULL. 


    Особенности проецирования файлов на разных платформах Win32 

    Механизм проецирования файлов в Windows NT и Windows 95 реализован 
по-разному. Вы должны знать об этих отличиях, поскольку они могут повлиять на 
код программ и надежность используемых ими данных. 
    В Windows 95 представление всегда проецируется на раздел адресного 
пространства, расположенный в диапазоне от 0х80000000 до OxBFFFFFFF. Значит, 
функция Map ViewOfFile — после успешного вызова — вернет какой-нибудь адрес из 
этого диапазона. Но вспомните: данные в этом разделе доступны всем 
Win32-npoueccaM. Так что, если один из процессов отображает сюда представление 
объекта «проекция файла», принадлежащие этому объекту данные физически 
доступны всем Win32-npoueccaM, и неважно, проецируют ли они сами представление 
того же объекта. Если другой процесс вызывает MapViewOfFile, используя тот же 
объект «проекция файла», Windows 95 возвращает адрес памяти, идентичный тому, 
что она сообщила первому процессу. Поэтому два процесса обращаются к одним и 
тем же данным и представления их объектов когерентны. 
    В Windows 95 один процесс может вызвать MapViewOfFile и, воспользовавшись 
какой-либо формой связи, передать возвращенный ею адрес памяти потоку другого 
процесса. Как только этот поток получит нужный адрес, ему уже ничто не 
помешает получить доступ к тому же представлению объекта «проекция файла». Но 
прибегать к такой возможности не следует по двум причинам: 
    • приложение не будет работать под управлением Windows NT (и я только что 
рассказал — почему); 
    • если первый процесс вызовет UnmapViewOfFile, регион адресного 
пространства освободится. А значит, при попытке потока второго процесса 
обратиться к участку памяти, где когда-то находилось представление, возникнет 
нарушение доступа. 
    Чтобы второй процесс получил доступ к представлению проецируемого файла, 
его поток тоже должен вызвать MapViewOfFile. Тогда система увеличит счетчик 
числа пользователей объекта «проекция файла». И если первый процесс обратится 
к UnmapViewOfFile, регион адресного пространства, занятый представлением, не 
будет освобожден, пока второй процесс тоже не вызовет UnmapViewOfFile. А 
вызвав MapViewOfFile, второй процесс получит тот же адрес, что и первый. Таким 
образом, необходимость в передаче адреса от первого процесса второму отпадает. 
    В Windows NT механизм проецирования файлов реализован более удачно, чем в 
Windows 95: для доступа к файловым данным в адресном пространстве нужно просто 
вызвать MapViewOfFile. При обращении к этой функции система резервирует для 
проецируемого файла закрытый регион адресного пространства, и никакой другой 
процесс не получает к нему доступ автоматически. Чтобы посторонний процесс мог 
обратиться к данным того же объекта «проекция файла», его поток тоже должен 
вызвать MapViewOfFile, и система отведет регион для представления объекта в 
адресном пространстве второго процесса. 
    Адрес, полученный при вызове MapViewOfFile первым процессом, скорее всего 
не совпадет с тем, что получит при ее вызове второй процесс, — даже несмотря 
на то, что оба процесса проецируют представление одного и того же объекта. И 
хотя в Windows 95 адреса, 

    190 _____________________________Windows для профессионалов 

    получаемые процессами при вызове MapViewOfFile, совпадают, лучше не 
полагаться на эту особенность — иначе приложение не станет работать в Windows NT! 
    Рассмотрим еще одно различие механизмов проецирования файлов у Windows NT 
и Windows 95. Взгляните на текст программы, проецирующей два представления 
единственного объекта «проекция файла»: 

    #define STRICT 
    ffinclude <Windows.h> 
    int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE hinstPrev, 
    LPSTR IpCmdLine, int nCmdShow) { 
    HANDLE hFile, hFileMapping; 
    BYTE *pbFile, *pbFile2; 
    // открываем существующий файл; он должен быть больше 64 Кб 
    hFile = CreateFiledpCmdLine, GENERIC_READ ; GENERIC_WRITE, 0, 
    NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NOF!MAL, NULL): 
    
    // создаем из файла данных объект "проекция файла" 
    hFlleMapping = CreateFileMappingChFile, NULL, PAGE_READWRITE, 0, О, NULL); 
    
    // проецируем представление файла, начиная с нулевого смещения 
    pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, О, О): 
    
    // проецируем второе представление файла, начиная со смещения G^Jub 
    pbFile2 = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 65536, 0); 
    if (poFile + 65536 == pbFile2) { 
    // если адреса перекрываются, оба представления проецируются на один 
    // регион, и мы работаем в Windows 95 
    MessageBox(NULL, "We are running under Windows 95", NULL, MB_OK); 
    ise { 
    // если адреса не перекрываются, каждое представление размещается в 
    // своем регионе адресного пространства, и мы работаем в Windows NT 
    MessageBox(NULL. "We are running under Windows NT", NULL. MB_OK); 
    } 
    UnmapViewOfFile(pbFile2); 
    UnmapViewOfFile(pbFile); 
    CloseHandle(hFileMapping); 
    CloseHandle(hFile); 
    rRtlim(O') ; 

    Когда приложение проецирует файл под управлением Windows 95, ему отводится 
регион адресного пространства — достаточно большой для размещения всего 
объекта «проекция файла». Это происходит, даже если Вы просите MapViewOfFile 
спроецировать лишь малую часть такого объекта. Поэтому спроецировать второе 
представление того же объекта размером 1 Гб не удастся, даже если указать, что 
представление должно быть не более 64 Кб. 
    При вызове каким-либо процессом функции MapViewOfFile ему возвращается 
адрес в пределах региона, зарезервированного для целого объекта «проекция 
файла». Поэтому в показанной выше программе первый вызов этой функции дает 
базовый адрес региона, со

    __________________Глава 8. Файлы, проецируемые в память 191 

    держащего весь спроецированный файл, а второй — адрес, смещенный «вглубь» 
того же региона на 64 Кб. 
    Windows NT и здесь ведет себя совершенно иначе. Два вызова функции 
MapViewOfFile (как в показанном выше коде) приведут к тому, что Windows NT 
зарезервирует два региона адресного пространства. Объем первого будет равен 
размеру объекта «проекция файла», объем второго — размеру объекта минус 64 Кб. 
Хотя регионы разные, система гарантигц^т когерентность данных, так как оба 
представления созданы на~бснове одного объекта «проекция файла». А в Windows 
95 такие представления когерентны потому, что они расположены вопном участие 
памяти-                      ~   ~———————~ 

    Совместный доступ процессов к данным через механизм проецирования 

    Совместное использование данных несколькими процессами — одна из главных 
причин, по которой системы типа Microsoft Windows вытесняют с рынка 
ограниченные системы типа MS-DOS. В Win32 и в 16-разрядной Windows эта 
возможность реализуется несколькими способами. Один из чаще всего применяемых 
в 16-разрядной Windows — вызов SendMessage или PostMessagec использованием 
окна, принадлежащего другому процессу. Но в 16-разрядной Windows эти функции 
позволяли, передавать в другой процесс лишь одно 16-битное и одно 32-битное 
значение. Можно было еще выделить блок глобальной памяти (применив флаг 
GMEM_SHARE), а затем — при вызове SendMessage или PostMessage — передать его 
описатель в параметре wParam или IParam. Процесс, получивший это сообщение, 
вызывал функцию GlobalLock, чтобы узнать адрес блока памяти, и после этого 
записывал или считывал нужные данные.             ~ ~ 
    В Win32 этот метод не работает — ведь у каждого процесса свое адресное 
пространство, и «влезть» в чужие данные невозможно. А в 16-разрядной Windows 
это было слишком просто "^приложения частенько начинали манипулировать чужими 
данными, и в результате "влетели» другие программы. 
    B""Wm32 несколько приложений, выполняемых на одной машине, могут совместно 
использовать данные через механизм проецирования файлов. По сути, это 
единственный механизм, обеспечивающий такую возможность в среде Win32. Все 
прочие методы совместного использования и обмена данными — вроде тех, что 
основаны на вызове SendMessage или PostMessage (в том числе функции 
SendMessage с передачей нового оконного сообщения WM_COPYDATA), — так или 
иначе используют механизм проецирования файлов. 
    Совместное использование данных в этом случае происходит так: два или 
более процесса проецируют в память представления одного и того же объекта 
«проекция файла», т. е. разделяют между собой одни и те же страницы физической 
памяти. Поэтому, когда один процесс записывает данные в представление 
совместного объекта «проекция файла», изменения немедленно отражаются в 
представлениях других процессов. Но при этом все процессы должны использовать 
одинаковое имя объекта «проекция файла». 
    А вот что происходит при запуске приложения. При открытии ЕХЕ-файла на 
диске система вызывает CreateFile, с помощью CreateFileMapping создает объект 
«проекция файла» и, наконец, вызывает Map ViewOfFileEx, чтобы спроецировать 
ЕХЕ-файл на адресное пространство только что созданного процесса. Map 
ViewOfFileEx вызывается^вместо MapViewOfFile, чтобы представление файла было 
спроецировано по базовому адресу, значение 
которогохр^ннтся^в^амом.-ЕХЕ.-файле. Потом создается первичный поток процесса, 
адрес первого байта исполняемого кода заносится в регистр — указатель команд 
(IP), и процессор приступает к исполнению кода. 
    Если пользователь запустит второй экземпляр того же приложения, система 
увидит, что объект «проекция файла» для нужного ЕХЕ-файла уже существует, и не 
станет создавать 

    192 _____________________________Windows для профессионалов 

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

    Функции CreateFileMapping и OpenFf/eMappf'ng 

    Давайте еще раз рассмотрим функцию CreateFileMapping: 
    
    HANDLE CreateFileMapping(HANDLE hFile, LPSECURITY_ATTRIBUTES Ipsa, 
    DWORD fdwPrctect, DWORD dwMaximumSizeHigh, 
    DWORD dwMaximumSizeLow, LPSTR IpName); 

    Обращаясь к ней для создания объекта «проекция файла», можно присвоить 
объекту имя, передав в параметре IpName строку с нулевым байтом в конце. 
Например, процесс создав объект и присваивает ему имя MyFileMapObj: 

    HANDLE hFileMap = CreateFileMapping(..., "MyFileMapObj"); 

    При исполнении этого кода CreateFileMapping проверяет: существует ли 
объект «проекция файла» с указанным именем. Нет — создает его и присваивает 
ему это имя. Да — просто увеличивает счетчик числа пользователей данного 
объекта и возвращает его описатель, значение которого зависит от конкретного 
процесса. Кстати, размер существующего объекта при этом не меняется. 
    Вызвав функцию GetLastError, можно определить, был ли создан новый объект. 
Обычно к ней обращаются, чтобы выяснить причину неудачного вызова той или иной 
функции. Но в данном случае ее можно вызвать и при успешном выполнении 
CreateFileMapping. Если GetLastError возвращает ERROR_ALREADY_EXISTS, значит, 
CreateFileMapping предоставила Вам описатель уже существующего объекта. И, 
если Вы не хотите использовать этот объект, закройте его описатель. Ниже 
приведен фрагмент кода, гарантирующий работу CreateFileMapping по принципу 
«все или ничего»: 

    HANDLE hFileMap = CreateFileMapping(...); 
    if ((hFileMap != NULL) && (GetLastError() == ERROR_ALREADY_EXISTS)) { 
    CloseHandle(hFileMap); 
    hFileMap = NULL; 
    } 
    return(hFileMap); 

    Другой способ совместного использования несколькими процессами одного 
объекта «проекция файла» — вызов OpenFileMapping: 

    HANDLE OpenFileMapping(DWORD dwDesiredAccess, 
    BOOL bInheritHahdIe, LPSTR IpName); 

    Эта функция аналогична CreateFileMapping за тем исключением, что 
предполагает существование объекта «проекция файла»; если его нет, новый 
объект не создается. При этом какой-то процесс должен сначала создать объект 
вызовом CreateFileMapping, тогда другой процесс сможет открыть этот объект, 
вызвав OpenFileMapping. Если применить сказанное к моему примеру, все 
процессы, кроме первого, должны открывать объект «проекция файла» вызовом 
OpenFileMapping с передачей ей в параметре IpName строки с нулевым символом в конце: 

    HANDLE hFileMap = OpenFileMapping(..., "MyFileMapOb]"); 

    Первый параметр функции, dwDesiredAccess, определяет права доступа 
(FILE_MAP_ READ, FILE_MAP_WRITE, FILE_MAP_ALL_ACCESS или FILE_MAP_COPY), а 
второй — 

    ______________________ Глава 8. Файлы, проецируемые в память 193 


    bInheritHandle — указывает, должны ли порождаемые процессы автоматически 
наследовать описатель данного объекта «проекция файла». Значение, возвращаемое 
функцией, — «процессо-зависимый» описатель объекта «проекция файла», 
созданного первым процессом. 
    Если OpenFileMapping не найдет объект «проекция файла» с указанным именем, 
она вернет NULL. Если же возвращается допустимый описатель, то для 
проецирования данных в адресное пространство процессу остается всего лишь 
вызвать Map ViewOfFile или Map ViewOfFileEx. Закончив работу с открытым 
объектом, не забудьте вызвать функцию CloseHandle. 

    Наследование 

    Два процесса могут совместно использовать один объект «проекция файла» и 
так: первый создает наследуемый объект этого типа, а потом порождает дочерний 
процесс, наследующий объект «проекция файла», принадлежащий родительскому 
процессу. В этом случае описатель объекта, принадлежащий дочернему процессу, 
идентичен описателю родительского. 
    Чтобы создать наследуемый объект «проекция файла», вызовите 
CreateFileMapping, передав ей адрес структуры SECURITY_ATTRIBUTES, 
инициализированной следующим образом: 

    SECURITY_ATTRIBUTES sa; 
    sa.nLength = sizeof(sa): 
    sa.IpSecurityDescriptor = NULL; 
    sa.bInheritHandle = TRUE; 
    hFlleMap = CreateFileMapping(hFile, &sa, ...); 

    (В качестве альтернативы родительский процесс — если он использует объект 
«проекция файла», созданный другим процессом, —может вызвать OpenFileMapping и 
просто передать TRUE в параметре bInheritHandle.} 
    Когда родительский процесс готов создать дочерний, он должен вызвать 
CreateProcess и передать TRUE в параметре flnheritHandle: 
    
    BOOL CreateProcess(LPCTSTR IpszImageName, LPCTSTR IpszCommandLine, 
    LPSECURITY_ATTRIBUTES IpsaProcess, 
    LPSECURITY_ATTRIBUTES IpsaThread, 
    BOOL flnheritHandle, DWORD fdwCreate, LPVOID IpvEnvironment, 
    LPCTSTR IpszCurDir, LPSTARTUPINFO IpsiStartInfo, 
    LPPROCESS_INFORMATION IppiProcInfo); 

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

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

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

    194 ______________________________Windows для профессионалов 

    которые им нужно разделять с другими процессами. А создавать файл на диске 
и хранить там данные только с этой целью очень неудобно. 
    Прекрасно понимая это, Microsoft добавила возможность проецировать файлы 
непосредственно на физическую память из страничного файла, а не из специально 
создаваемого дискового файла. Этот способ даже проще стандартного — 
основанного на создании дискового файла, проецируемого в память. Во-первых, не 
надо вызывать CreateFile: ведь создавать или открывать специальный файл нет 
нужды. Вы просто вызываете, как обычно, CreateFileMapping и передаете (HANDLE) 
OxFFFFFFFF в параметре hFile. Тем самым Вы указываете системе, что создавать 
объект «проекция файла», физическая память которого на ходится на диске, не 
надо; вместо этого следует выделить физическую память из страничного файла. 
Объем выделяемой памяти определяется параметрами dwMaximumSizeHigh и dwMaximumSizeLow. 
    Создав объект «проекция файла» и спроецировав его представление на 
адресное про странство своего процесса, его можно использовать так же, как и 
любой другой регион памяти. Если Вы хотите, чтобы данные стали доступны другим 
процессам, вызовите CreateFileMapping и передайте в параметре IpName строку с 
нулевым символом в конце. Тогда посторонние процессы — если им понадобится 
сюда доступ — смогут вызвать CreateFileMapping или OpenFileMapping и передать 
ей то же имя. 
    Когда необходимость в доступе к объекту «проекция файла» отпадет, процесс 
должен вызвать CloseHandle. И когда все описатели объекта будут закрыты, 
система уничтожит объект, освободив память, переданную из страничного файла. 

    ^ВНИМАНИЕ\ 

    Есть одна интересная ловушка, в которую может попасть неискушенный 
программист. Попробуйте догадаться, что неверно в этом фрагменте кода: 
    
    HANDLE hFile = CreateFile(.,,); 
    HANDLE hMap = CreateFileMapping(hFile, ...); 
    if ( hMap == NULL) 
    return(GetLastError()): 

    Если вызов CreateFile не удастся, она вернет значение OxFFFFFFFF 
(INVALID_HANDLE_VALUE). Но программист, написавший этот код, не дополнил его 
проверкой на успешное создание файла. Поэтому, когда в дальнейшем код 
обращается к функции CreateFileMapping, в параметре hFile ей передается 
значение OxFFFFFFFF, что заставляет систему создать объект «проекция файла» из 
ресурсов страничного файла, а не из дискового_файла, как предполагалось в 
программе. Весь последующий код, который использует проецируемый файл, будет 
работать правильно. Но при уничтожении объекта «проекция файла» все данные, 
записанные в спроецированную память (страничный файл), пропадут. И разработчик 
будет долго чесать затылок, пытаясь понять, в чем дело! 

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

    Программа MMFShare (MMFSHARE.EXE) (см. листинг на рис. 8-2) демонстрирует, 
как про исходит обмен данными между двумя и более процессами с помощью файлов, 
проецируемых в память. Файлы исходного кода и ресурсов этой программы 
находятся в каталоге MMFSHARE на компакт-диске, прилагаемом к книге. 
    Чтобы понаблюдать за происходящим, нужно запустить минимум две копии 
программы MMFSHARE.EXE. Каждый экземпляр программы создаст свое диалоговое окно: 

    ___ Глава 8. Файлы, проецируемые в память 195 

    Чтобы переслать данные из одной копии MMFShare в другую, наберите 
какой-нибудь текст в поле Data (данные). Затем щелкните кнопку Create Mapping 
Of Data (создать проекцию данных). Программа вызовет функцию 
CreateFileMapping, чтобы создать объект «проекция файла» размером 4 Кб и 
присвоить ему имя MMFSharedData (ресурсы выделяются объекту из страничного 
файла). Увидев, что объект с таким именем уже существует, программа выдаст 
сообщение, что не может создать объект. А если такого объекта нет, программа 
создаст объект, спроецирует представление файла на адресное пространство 
процесса и скопирует данные из поля ввода в проецируемый файл. 
    Далее MMFShare прекратит проецировать представление файла, отключит кнопку 
Create Mapping Of Data и активизирует кнопку Close Mapping Of Data (закрыть 
проекцию данных). На этот момент проецируемый в память файл с именем 
MMFSharedData будет просто «сидеть» где-то в системе. Никакие процессы пока не 
проецируют представления на данные, содержащиеся в файле. 
    Если Вы теперь перейдете в другую копию MMFShare и щелкнете там кнопку 
Open Mapping And Get Data (открыть проекцию и получить данные), программа 
попытается найти объект «проекция файла» с именем MMFSharedData через функцию 
OpenFileMapping. Если ей не удастся найти объект с таким именем, программа 
выдаст соответствующее сообщение. В ином случае она спроецирует представление 
объекта на адресное пространство своего процесса и скопирует данные из 
проецируемого файла в поле ввода. Вот и все! Вы переслали данные из одного 
процесса в другой. 
    Кнопка Close Mapping Of Data служит для закрытия объекта «проекция файла», 
что высвобождает физическую память, занимаемую им в страничном файле. Если же 
объекта «проекция файла» нет, никакой другой экземпляр приложения MMFShare не 
сможет открыть этот объект и получить от него данные. Кроме того, если один 
экземпляр программы создал объект «проекция файла», то остальным повторить его 
создание и тем самым перезаписать данные, содержащиеся в файле, уже не удастся""——'"" 

    см. след. стр. 

    Рис. 8-2. Листинг программы MMFShare 


    196 _____________________________Windows для профессионалов 


    РИС. 8~2 (продолжение) 


    BOOL Dlg_OnInitD.i.alog (HWND hwnd, HWND hwndFocus. LPARAM IParam) { 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IOI_MMFSHARE, IDI_MMFSHARE): 
    
    // инициализируем поле ввода тестовыми данными 
    Edit_SetText(GetDlgItem(hwnd, IDC_DATA), __TEXT("Some test data")); 
    
    // отключаем кнопку Close, т.к. файл нельзя закрыть, 
    // если он не создан или не открыт 
    Button_Enable(GetDlgItem(hwnd, IDC_CLOSEFILE), FALSE): 
    return(TRUE); 
    i 
    ( 
    /.'! I I / I i I i'/ / I / I I I I ! '/У//.1///.'Л'// '////Л/ /', /'//' •' 
1 ' .'/, !,   1 ' 
    void Dlg_OnCommand (HWND hwnd, int id, HWND hwndCtI, UINT codeNotify) { 
    
    // описатель открытого файла, проецируемого в память 
    static HANDLE s_hFileMap = NULL; 
    HANDLE hFileMapT; 
    switch (id) { 
    case IDG_CREATEFILE: 
    
    if (codeNotify != BN_CLICKED) 
    break: 
    
    // создаем в памяти проецируемый файл, содержащий данные, набранные 
    // в поле ввода; файл занимает 4 Кб и называется MMFSharedData 
    s_hFileMap = CreateFileMapping((HANDLE) OxFFFFFFFF, 
    NULL, PAGE'_READWRITE, 0. 4 * 1024. __TEXT("MMFSharedData")); 
    if (s_hFileMap != NULL) { 
    if (GetLastError() == ERROR_ALREADY_EXISTS) { 
    chMB(__TEXT("Mapping already exists - not created.")); 
    CloseHandle(s_hFileMap); 
    } else { 
    // создание проецируемого файла завершилось успешно; 
    
    // проецируем представление файла на адресное пространство 
    LPVOID IpView = MapViewOfFile(s_hFileMap, 
    FILE_MAP_READ : FILE_MAP_WRITE, 0, О, О); 


    __________________.__Глава 8. Файлы, проецируемые в память 197 


    РИС. 8-2 (продолжение) 

    if ((BYTE *) IpView != NULL) { 
    
    // представление спроецировано успешно; поместим содержимое 
    // поля ввода в проецируемый файл 
    Edit_GetText(GetDlgIterri(hwnd, IDC_DATA), 
    (LPTSTR) IpView, 4 * 1024); 
    
    // прекращаем проецирование; это защитит 
    // данные от "блуждающих" указателей 
    UnmapViewOfFile((LPVOIO) IpView); 
    
    // пользователь не может создать сейчас еще один файл 
    Button_Enable(hwndCtl, FALSE); 
    
    // пользователь закрыл файл 
    Button_Enable(GetDlgItem(hwnd, IDC_CLOSEFILE), TRUE); 
    } else { 
    chMB(__TEXT( "Can't map view of file'')); 
    i 
    i 
    } else { 
    chMB(__TEXT("Can't create file mapping,")); 
    } 
    break; 
    case IDC_CLOSEFILE: 
    
    if (codeNotify != BN_CLICKED) 
    break; 
    if (CloseHandle(s_hFileMap)) { 
    // пользователь закрыл файл; новый файл создать можно, 
    // но закрыть его нельзя 
    Button_Enable(Get01gItem(hwnd, IDC_CREATEFILE), TRUE); 
    Button_Enable(hwndCtl, FALSE); 
    } 
    break: 
    case IDC_OPENFILE: 
    
    if (codeNotify != BN_CLICKED) 
    break; 
    
    // смотрим: не существует ли уже проецируемый в память файл 
    // с именем MMFSharedData 
    
    hFileMapT = OpenFlleMapping(FILE_MAP_READ : FILE_MAP_WRITE. 
    FALSE, __TEXT("MMFSharedData")): 

    см. след. стр. 

    198 ______________________________Windows для профессионалов 


    РИС. 8-2 (продолжение) 

    if (hFileMapT != NULL) { 
    
    // такой файл существует; проецируем его представление 
    // на адресное пространство процесса 
    LPVOID IpView = MapViewOfFile(hFileMapT, 
    FILE_MAP_READ ! FILE_MAP_WRITE, 0, О, О); 
    if ((BYTE *) IpView != NULL) { 
    
    // помещаем содержимое файла в поле ввода 
    Edit_SetText(GetDlgItem(hwnd, IDC_DATA), (LPTSTR) IpView); 
    UnniapViewOfFiie(( LPVOID) IpView); 
    } else { 
    chMB(__TEXT("Can't map view.")); 
    } 
    CloseHandle(hFileMapT); 
    } else { 
    chMB(__TEXT("Can't open mapping.")); 
    ^ i 
    break; 
    case IDCANCEL: 
    EndDialog(hwnd, id); 
    break; 
    BOOL CALLBACK Dlg_Proc (HWND nwnd, UINT uMsg, WPARAM wParam, LPARAM 
IParam) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd. WM_INITDIALOG, Dlg_OnInitDialog); 
    chHANOLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand): 
    } 
    return(FALSE): 
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 

    см. след. стр 

    _________________ .___ Глава 8. Файлы, проецируемые в память 199 


    РИС. 8-2 (продолжение) 

    chWARNIFUNICODEUNDERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_MMFSHARE), NULL, Dlg_Proc); 
    return(O): 
    \ 
    ( 
    ////////////////////////////////// конец файла I / I / I / / / / I I I I / 
/ / / I / I I / I / I / I / I / I / / 


    Частичная передача памяти проецируемым файлам 

    До сих пор мы видели, что система требует передавать проецируемым файлам 
всю физическую память либо в файле данных на диске, либо в страничном файле. 
Это значит, что память используется не очень эффективно. Давайте вернемся к 
содержанию раздела «В какой момент региону передают физическую память» (глава 
7). Допустим, Вы хотите сделать всю электронную таблицу доступной другому 
процессу. Если применить для этого механизм проецирования файлов, придется 
передать физическую память целой таблице: 

    CELLDATA CellData[200][256]; 

    Если структура CELLDATA занимает 128 байтов, показанный массив потребует 6 
553 600 (200 х 256 х 128) байтов физической памяти. Это многовато — тем более, 
что в таблице обычно заполняют всего несколько строк. 
    Очевидно, в данном случае, создав объект «проекция файла», желательно не 
передавать ему заранее всю физическую память. Функция CreateFileMapping 
предусматривает так^ю возможность, для чего в параметр fdwProtect нужно 
передать один из флагов: SEC_RESERVE или SEC_COMMIT. 
    Эти флаги имеют смысл, только если Вы создаете объект «проекция файла», 
использующий физическую память из страничного файла. Флаг SEC_COMMIT 
заставляет CreateFileMapping сразу же передать память из системного 
страничного файла. (То же самое происходит, если никаких флагов не указано.) 
Но когда Вы задаете флаг SEC_RESERVE, система не передает физическую память из 
своего страничного файла, а просто возвращает описатель объекта «проекция 
файла». Далее, вызвав MapViewOfFile или MapViewOfFileEx, можно создать 
представление этого объекта. При этом MapViewOfFile или MapViewOfFileEx 
резервирует регион адресного пространства, не передавая ему физической памяти. 
Любая попытка обращения по одному из адресов зарезервированного региона 
приведет к нарушению доступа. 
    Таким образом, мы имеем регион зарезервированного адресного пространства и 
описатель объекта «проекция файла», идентифицирующий этот регион. Другие 
процессы могут использовать данный объект для проецирования представления того 
же региона адресного пространства. Физическая память региону по-прежнему не 
передается, так что если потоки в других процессах попытаются обратиться к 
созданным ими представлениям, они тоже вызовут нарушение доступа. 
    А теперь самое интересное. Оказывается, все, что нужно для передачи 
физической памяти общему (совместно используемому) региону, — вызвать функцию VirtualAlloc: 

    LPVOID VirtualAUoc(LPVOID IpvAddress, DWORD cbSize, 
    DWORD fdwAllocationType, DWORD fdwProtect); 


    200 _ ___________________________Windows для профессионалов 


    Эту функцию мы уже рассматривали (и очень подробно) в главе 7. Вызвать 
VirtualAlloc для передачи физической памяти под представление региона — то же 
самое, что вызвать VirtualAlloc р,ля передачи памяти региону, первоначально 
зарезервированному вызовом VirtualAlloc с флагом MEM_RESERVE. Получается, что 
региону, зарезервированному функциями MapViewOfFile или MapViewOfFileEx, — как 
и региону, зарезервированному функцией VirtualAlloc, — тоже можно передавать 
физическую память порциями, а не всю сразу. И если Вы поступаете именно так, 
учитывайте, что все процессы, спроецировавшие на этот регион представление 
одного и того же объекта «проекция файла», теперь тоже получат доступ к 
страницам физической памяти, переданным региону. 
    Итак, флаг SEC_RESERVE и функция VirtualAlloc позволяют сделать табличную 
матрицу CellData «общедоступной» и эффективнее использовать память. 
    VwiNDowsy Обычно VirtualAlloc не срабатывает, если Вы передаете ей адрес 
памяти, выходящий за пределы диапазона от 0х00400000 до Ox7FFFFFFF. Однако при 
передаче физической памяти проецируемому файлу с флагом SEC_RESERVE, в 
VirtualAlloc нужно передать адрес, укладывающийся в диапазон от 0х80000000 до 
OxBFFFFFFF. Только тогда Windows 95 «поймет», что физическая память передается 
региону, зарезервированному под проецируемый файл, и даст благополучно 
выполнить вызов функции.                                                       

    /ВНИМАНИЕ\ 
    В Windows NT функция VirtualFree не годится для возврата физической 
памяти, переданной в свое время проецируемому файлу (созданному с флагом 
SEC_RESERVE). Однако в Windows 95 такого ограничения нет. 
