    ФАЙЛОВЫЕ СИСТЕМЫ 

    глава  14 

    Один из важнейших аспектов любой операционной системы — то, как она 
работает с файлами. Вспомним старую добрую MS-DOS: она, по сути, только и 
занималась файлами — особенно когда сверху «сидела» 16-разрядная Windows. 
Последняя заботилась обо всем, кроме файлового ввода/вывода, перекладывая его 
на MS-DOS. (Правда, напоследок 16-разрядная Windows взвалила на себя 
ответственность и за это: в ней появилась поддержка 32битного доступа к дискам 
и файлам, а при работе с файлом подкачки она научилась обращаться прямо к 
дисковому контроллеру.) 
    В Windows 95 и Windows NT поддержка 32-битного доступа к файлам и дискам 
значительно расширена. Кроме того, они поддерживают сразу несколько файловых систем. 
    Основная файловая система Windows 95 — FAT (file allocation table, таблица 
размещения файлов); она поддерживается и в Windows NT. FAT позволяет 
присваивать каталогам и файлам имена до 255 символов длиной. Эти имена 
хранятся на диске в кодировке Unicode, а это значит (кроме всего прочего), что 
они не нарушаются при копировании в системы, йатояьзующие другие языки. Еще 
одна особенность файловой системы FAT в том, что с каждым файлом в ней связаны 
3 временные метки: время создания файла, время последнего к нему обращения и 
время последней модификации. 
    "'Windows NT поддерживает не только FAT, но и NTFS (файловую систему NT), 
которая умеет все то же, что FAT, плюс кое-что еще. Самое главное — NTFS 
позволяет быстро восстанавливать данные на диске после сбоя системы. Другая ее 
особенность — она работает с носителями чрезвычайно высокой емкости. Добавлен 
и ряд средств защиты: например, появились файлы с атрибутом «только для 
исполнения», что сильно затрудняет внедрение в них вируса. NTFS — для 
совместимости с POSIX — поддерживает также жесткие связи (hard links), 
сохранение информации о времени последнего открытия файла и способна различать 
регистр букв в именах файлов (case-sensitive filenames). 
    NTFS разработана как расширяемая система. Предполагается, что в дальнейшем 
она будет поддерживать целый ряд новых функций, в том числе операции на базе 
транзакций (необходимые устойчивым к сбоям приложениям), номера версий файлов 
(контролируемые пользователем), многопоточность данных в пределах одного 
файла, гибкость правил именования файлов и присвоения атрибутов, а также 
поддержку самых распространенных файл-серверов. Там, где соображения 
безопасности стоят на первом месте, NTFS, несомненно, станет стандартом и, 
по-видимому, вытеснит системы, базирующиеся на FAT. 
    CDFS (файловая система CD-ROM) предназначена специально для приводов 
CD-ROM — они сейчас не роскошь, а просто необходимость. Все больше программных 
продуктов поставляются именно на компакт-дисках — в том числе Windows 95 и 
Windows NT. В будущем эти носители дистрибутивных пакетов станут еще 
популярнее ввиду таких преимуществ: 

    415 

    416 ____________________________Windows для профессионалов 

    • При массовом производстве поставлять программные продукты на 
компакт-дисках дешевле, чем на дискетах. В итоге розничная стоимость 
программных продуктов должна уменьшиться. 
    • Поскольку у рядового пользователя нет специального оборудования для 
публикации компакт-дисков, снижается острота проблемы «пиратства». Тем, кому 
нужна какая-то программа, придется приобрести ее легально, и это тоже должно 
привести к снижению конечной стоимости продукта. 
    • Компакт-диски надежнее дискет — они не подвержены влиянию 
электромагнитных излучений. 
    • Данные можно считывать прямо с CD-ROM — их не надо копировать на жесткий 
диск, что позволяет экономить драгоценное дисковое пространство. 
    • Установка приложений с компакт-диска гораздо проще, так как пользователю 
не приходится нянчиться с компьютером, периодически вставляя дискеты, 
требуемые программой установки. 
    То, что Microsoft ввела поддержку CD-ROM непосредственно в Windows NT и 
Windows 95, безусловно, поможет дальнейшему распространению компакт-дисков на рынке. 
    Самое приятное, что операционная система способна работать со всеми этими 
файловыми системами одновременно. В Windows NT можно запросто отформатировать 
один раздел жесткого диска для FAT, а другой — для NTFS и копировать файлы с 
любого раздела на дискету, отформатированную в FAT. 

    Правила именования файлов в Win32 

    Поскольку Win32 поддерживает несколько файловых систем, все они должны 
подчиняться неким общим правилам. Самое важное: файловая система должна 
организовывать файлы в иерархическое дерево каталогов — так, как это делается 
в FAT. Имена каталогов и файлов в полном имени файла (pathname) отделяются 
обратной косой чертой (\). Кроме правил формирования полного имени, действуют 
и правила именования каталогов и файлов: 
    • полное имя файла завершается нулевым символом; 
    • имена файлов и каталогов не должны содержать разделительного символа 
(\), символов с ASCII-кодами от 0 до 31, а также символов, явно запрещенных в 
какойлибо файловой системе; 
    • имена файлов и каталогов могут включать буквы разного регистра, но при 
поиске файлов и каталогов регистр букв не учитывается. Если файл с именем 
ReadMe.Txt существует, создание нового файла с именем README.TXT уже не допускается; 
    • точка (.) идентифицирует текущий каталог. Например, AREADME.TXT 
означает, что файл README.TXT находится в текущем каталоге; 
    • две точки (..) идентифицируют родительский каталог. Например, 
.AREADME.TXT означает, что файл README.TXT находится в родительском каталоге 
текущего каталога; 
    • точка (.), используемая как часть имени файла или каталога, считается 
разделителем компонентов имени. Например, в файле README.TXT точка отделяет 
имя файла от его расширения; 
    • имена файлов и каталогов не должны содержать некоторых специальных 
символов вроде <, >, :, " или ]. 
    Эти основополагающие правила должны соблюдать все файловые системы, 
поддерживаемые Win32. Допускаются лишь различия, вытекающие из разной 
интерпретации этих 

    _____________________________Глава 14. Файловые системы 417 

    правил и дополнительных возможностей конкретной файловой системы. 
Например, NTFS поддерживает защиту файлов и каталогов от несанкционированного 
доступа, a FAT — нет. 

    Общесистемные операции и работа с томами 

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

    DWORD GetLogicalDrives(void): 

    Функция просто возвращает 32-битное значение, каждый бит которого 
указывает, существует ли соответствующее логическое устройство. Например, бит 
0 установлен, если в системе есть диск А, а если присутствует и диск Z, то 
установлен и бит 25. 
    Определить, присвоена ли данная буква какому-либо логическому устройству, 
можно так: 

    BOOL DoesDriveExist (TCHAR cDnveLetter) { 
    cDriveLetter = (TCHAR) CharUpper(cDriveLetter): 
    return(GetLogicalDrives() & (1 « (cDriveLetter - ___TEXT('A')))): 
    } 

    Результат вызова GetLogicalDrives можно использовать и для подсчета числа 
логических дисков в системе: 

    UINT GetNumDnvesInSys (void) { 
    DWORD dw = GetLogicalDrlvesO; 
    UINT uDnvesInSys = 0; 
    // повторяем, пока не будут найдены все диски 
    while (dw != 0) { 
    if (dw & 1) { 
    
    // если младший бит установлен, диск существует 
    uDrivesInSys++; 
    } 
    
    // сдвигаем данные на 1 бит вправо 
    i »= 1; 
    
    // возвращаем число логических дисков 
    return(uDrivesInSys); 

    GetLogicalDrives работает быстро, но дает не очень-то много. 
пшкзнри_инфогшации. А вот функция GetLogicalDriveStrings не требует битовых 
операций и сообщает куда больше: 

    DWORD GetLogicalDnveStrings(DWORD cchBuffer, LPTSTR IpszBuffer): 

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

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

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

    DWORD dw = GetLogicalDriveStrings(0, NULL); 
    LPTSTR IpDriveStrings = HeapAlloc(GetProcessHeap(), 0, dw * sizeof(TCHAR)); 
    GetLogicalDriveStrlngs(dw. IpDriveStrings); 

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

    A:\<nuU> 
    B:\<nuU> 
    C:\<null> 
    D:\<null> 
    E:\<nuU> 
    F:\<null> 
    G:\<null> 
    <null> 

    Теперь, зная имена корневых каталогов на каждом логическом диске в 
системе, Вы, вероятно, захотите выяснить конкретные типы дисков. Для этого 
служит функция GetDriveType: 

    UINT GetDriveType(LPTSTR IpszRootPathName); 

    Она возвращает тип диска, идентифицируемого параметром IpszRootPathName. 
Вот список возможных значений: 

    Идентификатор            Описание 

    DRIVE_UNKNOWN          Тип устройства определить нельзя. 
    DRIVE_NO_ROOT_DIR       Корневого каталога не существует. 
    DRIVE_REMOVABLE         Съемный диск — возвращается для дисководов (гибких дисков). 
    DRIVE_FIXED              Несъемный диск — возвращается для жестких дисков. 
    DRIVE_REMOTE            Удаленный диск — возвращается для сетевых дисков. 
    DRIVE_CDROM           Привод CD-ROM. 
    DRIVE_RAMDISK           Диск, эмулируемый в оперативной памяти, — RAM-диск. 


    Определение информации о томах 

    Разрабатывая УУш32-приложения, помните, что пользователь может работать с 
любой комбинацией FAT, NTFS и CDFS, а также то, что в будущем появятся другие 
файловые системы. Любая из новых файловых систем будет непременно следовать 
базовым правилам, и Вы, "приложив минимум дополнительных усилий, сможете 
писать программы, корректно работающие независимо от файловой системы. Если 
Вашей программе потребуется специфическая информация о конкретной файловой 
системе, вызовите функцию GetVolumeInformaUon: 

    BOOL GetVolumeInforniation(LPTSTR, IpRootPathName, 
    LPTSTR IpVolumeNameBuffer, DWORD nVolumeNameSize, 
    LPDWORD IpVolumeSerialNumber, LPDWORD IpMaximumComponentLength, 
    LPDWORD IpFileSystemFlags, LPTSTR IpFileSystemNameBuffer, 
    DWORD nFileSystemNameSize): 

    _______________________________Глава 14. Файловые системы 419 

    Она возвращает специфичную для файловой системы информацию, связанную с 
каталогом, определяемым параметром IpRootPathName. Большинство прочих 
параметров — указатели на буферы и переменные типа DWORD, заполняемые самой функцией. 
    Метка дискового тома возвращается в IpVolumeNameBuffer. Параметр 
nVolumeNameSize определяет максимальный размер буфера в символах. В переменную 
типа DWORD, на которую указывает параметр IpVolumeSerialNumber, функция 
записывает серийный номер тома. Если эта информация Вас не интересует, 
передайте NULL вместо IpVolumeSerialNumber. ~~Серийный номер полезен для 
идентификации диска, вставляемого в дисковод. Начиная с MS-DOS версии 4.0, 
команда FORMAT записывает на диск серийный номер. В итоге, даже если у двух 
дисков одинаковые метки томов, их серийные номера уникальны. При смене дисков 
метка тома может совпасть, но серийные номера будут разными, и приложение 
вполне способно проверить, не сменил ли пользователь диск. 
    Параметр IpMaximumComponentLength указывает на переменную типа DWORD, куда 
записывается максимальное количество символов, допустимое в именах каталогов и 
файлов. Для файловых систем FAT, NTFS и CDFS это значение равно 255. Во многих 
программах длина буферов для полных и кратких имен файлов жестко «зашита» в 
код. Ни в коем случае так не делайте! Пока программа оперирует файлами и 
путями с краткими именами, все о'кэй, но лишь дело дойдет до файлов с длинными 
именами, Вы столкнетесь с целым ворохом проблем, начиная от переполнения стека 
и кончая нарушением защиты памяти. 
    Разрабатывая приложение, создайте файлы с длинными-предлинными именами и 
огромными-преогромными полными именами, а потом «похороните» какие-нибудь 
файлы данных, с которыми работает Ваша программа, глубоко в недрах иерархии 
каталогов — тогда Вы узнаете, как ведет себя программа в этом случае. Лучше 
отловить ошибки, связанные с файловой системой, при разработке, чем после продажи. 
    Другое соображение, о котором легко забыть, — это Unicode (только в FAT и 
NTFS). Если Вы пользуетесь Unicode, буферы должны быть вдвое больше. Когда Вы 
запрашиваете пути и имена файлов, система сама выполняет нужные 
преобразования, но буферы должны быть достаточно большими, чтобы в них 
уместились результаты преобразований. 
    В переменную типа DWORD, на которую указывает IpFileSystemFlags функции 
GetVolumeInformation, записываются флаги, сообщающие о файловой системе. Их 
допустимые значения: 

    Идентификатор флага            Описание 

    FS_CASE_IS_PRESERVED           При записи файла на диск регистр букв в его 
имени сохраняется. 
    FS_CASE_SENSITIVE               Файловая система поддерживает поиск файлов 
с учетом регистра букв в именах. 
    FS_UNICODE_STORED_ON_DISK    Файловая система поддерживает хранение на 
диске имен файлов в Unicode. 
    FS_PERSISTENT_ACLS              Файловая система оперирует со списками 
контроля доступа (access control lists, ALCs) (только в NTFS). 
    FS_FILE_COMPRESSION            Файловая система поддерживает сжатие на 
уровне отдельных файлов. В NTFS можно сжимать отдельные файлы, а в FAT 
сжимаются лишь тома целиком. Таким образом, для NTFS это флаг будет 
установлен, а для FAT — сброшен. 
    FS_VOL_IS_COMPRESSED          Файловая система поддерживает сжатие на 
уровне томов. В NTFS можно сжимать отдельные файлы, а в FAT сжимаются лишь 
тома целиком. Таким образом, для NTFS это флаг будет сброшен, а для FAT — установлен. 

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

    Параметр IpFileSystemNameBuffer указывает на буфер, в который функция 
записывает имя файловой системы (FAT, NTFS или CDFS), а параметр 
nFileSystemNameSize определяет максимальный размер этого буфера в символах. 
    Определяя наличие в дисководе съемного носителя (дискета, компакт-диск), 
вызовите сначала функцию: 

    SetErrorMode(SEM_FAILCRITICALERRORS); 

    и тогда при вызове GetVolumeInformation система не выведет на экран 
сообщения об ошибке, если в дисководе не будет диска. 
    Большая часть информации, возвращаемой функцией GetVolumeInformation, 
определяется при форматировании диска и изменить ее без переформатирования 
нельзя. Без повторного форматирования можно изменить лишь метку тома. Для 
этого вызовите: 

    BOOL SetVolumeLabel(LPTSTR IpRootPathName, LPTSTR IpVolumeName); 

    Первый параметр этой функции задает корневой каталог файловой системы, где 
Вы хотите изменить метку тома. Если он равен NULL, меняется метка на текущем 
диске данного процесса. Параметр IpVolumeName определяет новую метку. Если он 
равен NULL, метка удаляется с диска. 
    И еще одна функция, пригодная для определения информации о дисковом томе: 

    BOOL GetDiskFreeSpace(LPTSTR IpszRootPathName, 
    LPDWORD IpSectorsPerCluster, LPDWORD IpBytesPerSector, 
    LPDWORD IpFreeClusters, LPDWORD IpClusters): 

    Она возвращает статистику о дисковом пространстве на томе, заданном в 
параметре IpszRootPathName. Все байты, доступные на гибких и жестких дисках, 
объединены в сектора (обычно по 512 на сектор), которые группируются в 
кластеры. В FAT количество секторов на кластер может заметно варьироваться: 

    Тип диска                  Секторов на кластер 

    Гибкий диск на 360 Кб                2 
    Гибкий диск на 1.2 Мб                4 
    Жесткий диск на 200 Мб              8 
    Жесткий диск на 400 Мб              32 

    Минимальным объемом дискового пространства, который можно выделить файлу, 
является кластер. Например, файл размером 10 байтов занял бы на 
360-килобайтном диске 2 сектора, или 1 Кб (2 х 512), а на 200-мегабайтном 
жестком диске — 8 секторов, или 4 Кб (8 х 512). 
    Теперь допустим, что на дискете расположены 2 файла по 1 Кб, и попробуем 
сохранить их на 200-мегабайтном жестком диске, где есть всего 4 Кб свободного 
пространства. Первый будет скопирован, а для второго места не хватит. В случае 
носителей очень высокой емкости проблемы, связанные с кластерами, могут стать 
куда серьезнее. Готовя первое издание этой книги, я заменил 260-мегабайтный 
жесткий диск на гигабайтный, который решил разбить на два раздела по 512 Мб. В 
каждом разделе 1 кластер содержал 32 сектора. Значит, файл размером в 1 байт 
требует минимум 16 Кб дискового пространства. Когда я записал на диск файлы 
общим объемом примерно 200 Мб, получилось, что на нем попусту пропало около 
100 Мб — почти половина емкости моего прежнего диска! Я был просто поражен, 
столкнувшись на практике с проблемой кластеров, и поспешил переразметить новый 
жесткий диск на несколько разделов примерно по 250 Мб, так как кластеры на 
250мегабайтном диске состоят лишь из 8 секторов каждый. 

    _____________________________Глава 14. Файловые системы 421 

    VwiNoows/ Microsoft разработала новую файловую систему FAT32, подобную 
FAT, но ре\95/ тающую проблему кластеров. Однако эта система — более 
экономичная — \/   несовместима с некоторыми программами MS-DOS и 16-разрядной 
Windows, так как в ней изменены дисковые структуры. Microsoft считает, что 
большинство пользователей предпочтет экономию дискового пространства, а 
устаревшие программы заменит новыми. Впрочем, эти изменения не нарушат работы 
приложений, манипулирующих с дисковыми томами через Win32 API. 

    \WINDOWS/ 
    На момент написания книги Windows NT еще не поддерживала FAT32. Поэтому 
FAT32 не годится для тех дисковых томов, которые Вы хотите использовать как в 
Windows 95 (OSR 2), так и в Windows NT. 

    На основе значений, возвращаемых GetDiskFreeSpace, можно вычислить объемы 
общего, свободного и занятого дискового пространства: 

    DWORD dwSectorsPerCluster, dwBytesPerSector: 
    DWORD dwFreeClusters, dwClusters: 
    DWORD dwTotalDiskSpace. dwFreeDiskSpace, dwUsedDiskSpace; 
    GetDiskFreeSpace("C:\\", &dwSectorsPerCluster, 
    &dwBytesPerSector, &dwFreeClusters, &dwClusters); 
    dwTotalDiskSpace = dwSectorsPerCluster * dwBytesPerSector * dwClusters; 
    dwFreeDiskSpace = dwSectorsPerCluster * dwBytesPerSector * dwFreeClusters; 
    dwUsedDiskSpace = dwSectorsPerCluster * dwBytesPerSector * (dwClusters - uwhieeCiubtbisi. 
    В Windows NT 4 появилась новая функция GetDiskFreeSpaceBx: 
    BOOL GetDiskFree8paceEx(LPCTSTR IpDirectoryName, 
    PULARGE_INTEGER IpFreeBytesAvailableToCaller, 
    PULARGE_INTEGER IpTotalNumberOfBytes, 
    PULARGE_INTEGER IpTotalNumberOfFreeBytes): 

    Эта функция, которая будет введена и в следующие версии Windows 95, похожа 
на GetDiskFreeSpace с тем исключением, что сама перемножает число байтов в 
кластере на число доступных кластеров. В беззнаковом длинном целом значении, 
на которое указывает параметр IpTotalNumberOfFreeBytes, возвращается число 
байтов, доступных вызвавшему функцию потоку. Это значение полезно при работе в 
системе с разграничением доступа к дисковому пространству. 

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

    Программа Disklnfo (DISKINFO.EXE) (см. листинг на рис. 14-1) демонстрирует 
применение большей части только что рассмотренных функций. Файлы исходного 
кода и ресурсов для этой программы находятся в каталоге DISKINFO на 
компакт-диске, прилагаемом к книге. При запуске она открывает диалоговое окно 
Disk Volume Information Viewer (просмотр информации о дисковом томе). На 
следующих четырех иллюстрациях показаны диалоговые окна этой программы с 
результатами, полученными на разных логических дисках. 

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


    Logical drive strings: j^g^M^——^e^——i 
    Drive type:  Removable 
    Volume information                                , 
    Volume name:      BLANK 
    Serial number:      4105463BB4 
    Component length: 255 
    Flags:            FS„CASE_IS_PRESERVED 
    FS_UNICODE_STORED_ON_DISK 
    File System:        FAT 
    Disk free space 
    Sectors/Cluster:              1 
    Bytes/Sector:            512                                               
    Free clusters:            2B47 
    Clusters:                 2B47 
    _____________________________________I                                     
                                 I 
    Drive type:   Fixed 
    Volume information 
    Volume name: 
    Serial number:      1029773316 
    Component length: 255 
    Flags:            FS_CASE_1S_PRESERVED 
    FS_UNICODE_STORED_ON_DISK 
    File System:         FAT 
    Disk free space 
    Sectors/Cluster:            16 
    Bytes/Sector:              512 
    Free clusters:            7155 
    Clusters:                63970 


    ____________________________Глава 14. Файловые системы 423 


    Drive type:  CD-ROM 
    Volume information 
    Volume name:      abba_1 
    Serial number:      1427123684 
    Component length: 221 
    Flags: 
    FS_CASE_SENSITIVE           , 
    File System:        CDFS 
    Disk free space 
    Sectors/Cluster:             1 
    Bytes/Sector:             2048 
    Free clusters:                0 
    Clusters:               231967 
    Drive type: Fixed 
    Volume information 
    Volume name: 
    Serial number:     955807662 
    Component length: 255 
    Flags:           FS CASEJS PRESERVED 
    FS^CASE SENSITIVE 
    FS_UN!CODE_STORED ON_DISK 
    FS PERSISTENT ACLS 
    F8^FILE_COMPRESSION 
    File System:       NTFS 
    Disk free space 
    Sectors/Cluster:            2 
    Bytes/Sector:            512 
    Free clusters:         102152 
    Clusters:             562432 

    В комбинированном списке (вверху окна) можно просмотреть все подключенные 
к системеТюгйческие диски. Эта информация — результат вызова 
GetLogicalDriveStrings. Когда Вывы^ираете'такой-то логический диск, остальные 
поля диалогового окна изменяются, чтобы показать информацию о нем. Содержимое 
поля Drive Type (тип диска) обновляется вызовом GetDriveType, полей в разделе 
Volume Information (информация о томе) — вызовом GetVolumeIn'formation, а 
полей в разделе Disk Free Space (свободное пространство на диске) — вызовом 
GetDiskFreeSpace. 

    424 _____________________________ Windows для профессионалов 


    DISKINFO.C 
    /„^,,^^,*,^...^..*,*.»^,.^,^^^,^^^.**..^.^..»„„**,,* 
    Модуль: DiskInfo.C 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    ******++*****************+****************************-lr****^***l^^»t**++*^+-)-+/ 
    Sinclude ",,\CmnHdr. H"           /* см. приложение В */ 
    ftinclude <windows.h> 
    ftinclude <windowsx.h> 
    #include <tchar.h> 
    «include <stdio.h>               // для sprintf 
    #include <string.h>              // для strchr 
    #include "Resource.H" 
    void Dlg_FillDriveInfo (HWND hwnd, LPTSTR IpszRootPathName) { 
    // переменные для обработки информации о типе диска 
    int nDriveType; 
    LPCTSTR p; 
    // переменные для обработки информации о томе 
    TCHAR szBuf[200]; 
    TCHAR lpvolumeNameBuffer[200]; 
    DWORD dwVolumeSerialNumber, dwMaximumComponentLength: 
    DWORD dwFileSystemFlags; 
    TCHAR lpFileSystemNameBuffer[50]; 
    // переменные для обработки информации о дисковом пространстве 
    DWORD dwSectorsPerCluster, dwBytesPerSector; 
    DWORD dwFreeClusters, dwClusters; 
    // получаем информацию о типе диска 
    nDriveType = OetDriveType(lpszRootPathName); 
    switch (nDriveType) { 
    case DRIVE_UNKNOWN; 
    p = __TEXT("Cannot be determined."); 
    break; 
    case DRIVE_NO_ROOT_DIR: 
    p = __TEXT("Path does not exist,"); 
    break: 
    case DRIVE_REMOVABLE: 
    p = __TEXT("Removable"): 
    break; 

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


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


    _____________________________Глава 14. Файловые системы 425 


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

    case DRIVE_FIXEO: 
    Р = __ТЕХТ("Fixed"); 
    break; 
    case DRIVE_REMOTE: 
    p = __TEXT("Remote"); 
    break; 
    case DRIVE_CDROM: 
    P = __TEXT("CD-ROM"); 
    break; 
    case DRIVE_RAMDISK; 
    P = __TEXT("RAM disk"); 
    break; 
    default: 
    P = __TEXT("Unknown"); 
    break; 
    SetWindowText(Get01gItem(nwnd, IDC_DRIVETYPE), p); 
    // получаем информацию о томе 
    
    if (GetVolumeInformation(lpszRootPathName, IpVolumeNameBuffer, 
    сhDIMOF(IpVolumeNameBuffer), &dwVolumeSerialNumber, 
    &dwMaximumCoinponent Length, &dwFileSystemFlags, 
    IpFileSystemNameBuffer, chDIMOF(lpFileSystemNameBuffer))) { 
    
    _stprintf(szBuf, __TEXT("%s\n%u\n%u\n"), 
    IpVolumeNameBuffer, dwVolumeSerialNumber, 
    dwMaxiinumComponentLength); 
    
    if (dwFileSystemFlags & FS_CASE_IS_PRESERVED) 
    _tcscat(szBuf, __TEXT("FS_CASE_IS_PRESERVED")); 
    _tcscat(szBuf, __TEXT("\n")): 
    if (dwFileSystemFlags & FS_CASE_SENSITIVE) 
    _tcscat(szBuf, __TEXT("FS_CASE_SENSITIVE")); 
    _tcscat(szBuf, __TEXT("\n")); 
    if (dwFileSystemFlags & FS_UNICODE_STORED_ON_DISK) 
    _tcscat(szBuf, __TEXT("FS_UNICODE_STOREO_ON_DISK")): 
    _tcscat(szBuf, __TEXT("\n")); 
    if (dwFileSystemFlags & FS_PERSISTENT_ACLS) 
    _tcscat(szBuf, __TEXT("FS_PERSISTENT_ACLS")); 
    _tcscat(szBuf. __TEXT("\n")); 

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

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


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

    if (dwFileSystemFlags & FS_FILE_COMPRESSION) 
    _tcscat(szBuf, __TEXT("FS_FILE_GOMPRESSION")); 
    _tcscat(szBuf, __TEXT("\n")); 
    if (dwFileSystemFlags & FS_VOL_IS_COMPRESSED) 
    _tcscat(szBuf, __TEXT("FS_VOL_IS_COMPRESSED")); 
    _tcscat(szBuf, __TEXT("\n")); 
    _tcscat(szBuf, IpFileSystemNanieBuffer); 
    } else { 
    _tcscpy(szBuf, __TEXT("NO VOLUME INFO")); 
    s 
    SetWindowText(GetDlgItem(hwnd. IDC_VOLINPO), szBuf); 
    // получаем информацию о дисковом пространстве 
    
    if (GetDiskFreeSpace(lpszRootPathName, &dwSectorsPerCluster, &dwBytesPerSector, 
    &dwFreeClusters, &dwClusters)) { 
    
    _stprintf(szBuf, „_TEXT("%u\n%Li\n%u\n%u"), 
    dwSectorsPerCluster, dwBytesPerSector, 
    dwFreeClusters, dwClusters); 
    } else { 
    _tcscpy(szBuf, __TEXT("NO\nDISK\nSPACE\nINFO")); 
    } 
    SetWindowText(GetDlgItem(hwnd, IDC_DISKINFO), szBuf); 
    lllllllllll/IHI/llll/lillllllllllll/lll//l/l/lllllllllllllllll/lllllllll/tllll 
    
    BOOL 01g_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    DWORD dwNuniBytesForDriveStrings; 
    HANDLE hHeap; 
    LPTSTR pszAHDrives, pszDrive; 
    TCHAR szLogDnve[100]; 
    HWND  hwndCtI = GetDlgItem(hwnd, IDC^LOGDRIVES); 
    int nNumDrives = 0, nDriveNum; 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_DISKINFO, IDI_DISKINFO); 
    // получаем число байтов, требуемых для хранения всех строк 
    // с логическими дисками 
    dwNumBytesForDriveStnngs = GetLogicalDriveStrings(0, NULL) * sizeof (TCHAR): 
    // эта платформа поддерживает функцию GetLogicalDriveStrings 
    
    // выделяем память из кучи для строковых имен дисковых устройств 
    hHeap = GetProcessHeapO; 

    I A(. след. стр. 

    _____________________________Глава 14. Файловые системы 427 


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

    pszAllDrives = (LPTSTR) HeapAlloc(hHeap, 
    HEAP_ZERO_MEMORY, dwNuinBytesForDriveSt rings); 
    
    // считываем строковые имена устройств в наш буфер 
    GetLogicalDriveStrings(dwNumBytesForDriveStrings / sizeof(TCHAR), pszAllDrives); 
    
    // анализируем содержимое буфера и заполняем комбинированный список 
    pszDrive = pszAllDrives; 
    while (pszDrive[0] != 0) { 
    ComboBox_AddString(hwndCtl, pszDrive); 
    nNumDrives++: 
    pszDrive = _tcschr(pszDrive, 0) + 1; // переходим к следующей строке 
    \ 
    { 
    HeapFree(hHeap, 0, pszAllDrives); 
    
    // инициализируем информацию о томе первого жесткого диска, чтобы 
    // избежать считывания информации с дисковода, в котором нет дискеты 
    for (nDriveNum = 0; nDriveNum < nNumDrives: nDriveNum++) { 
    ComboBox_GetLBText(hwndCtl, nDriveNum, szLogDrive); 
    if (GetDriveType(szLogDrive) == DRIVE_FIXED) 
    break; 
    » i 
    if (nDriveNum == nNumOrives) { 
    // в системе нет устройств с жесткими дисками; 
    // используем первое устройство 
    ComboBox_GetLBText(hwndCtl, nDriveNufn = 0, szLogDrive); 
    i I 
    
    // выбираем первый жесткий диск или просто первый диск 
    // если жестких дисков нет 
    ComboBox_SetCurSel(hwndCtl. nDriveNum); 
    Dlg_FillDriveInfo(nwnd, szLogDrive); 
    return(TRUE); 
    } 
    IIIIIIIIIIIIIIIIIIIIIIIIIII//IIIIII//IIII//I/I////III/IIII//IIIIIHIIIII/IIIIH 
    
    void Dlg_OnCommand (HWND hwnd, int id, HWND hwndCtI, UINT codeNotify) { 
    TCHAR szLogDrive[100]; 
    switch (id) { 
    case IDC_LOGDRIVES: 
    
    if (codeNotify != CBN_SELGHANGE) 
    break; 

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

    428 _._-_____________________________Windows для профессионалов 


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

    ComboBox_GetText(hwndCtl, szLogDrive, chDIMOF(szLogDrive)); 
    Dlg_FillDriveInfo(hwnd, szLogDrive): 
    break; 
    case IDCANCEL: 
    End0ialog(hwnd, id); 
    break; 
    //////////////////////////////////////////l/////-ll/л///-'/'l' 
    BOOL CALLBACK Dlg_Proc (HWNO hwnd, UINT uMsg, WPARAM wParam, LPARAM IParam) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM^INITDIALOG, Dlg_OnInitDialog); 
    chHANDLE^DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand): 
    } 
    retur-n(FALSE); 
    ///////////////////////////////////////////
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    chWARNIFUNICODEUNDERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_DISKINFO), NULL, Dlg_Proc): 
    return(O); 
    /////////////конец файла ///////////////////


    Операции с каталогами 

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

    BOOL GetCurrentDirectory(DWORD ccriCurDir, LPTSTR IpszCurDir); 

    Она записывает текущий путь для процесса в буфер, на который указывает 
IpszCurDir. Параметр cchCurDir определяет максимальный размер буфера в 
символах. При ошибке функция возвращает 0, в ином случае — число символов, 
скопированных в буфер, не считая завершающего строку нулевого символа. Если 
длина буфера недостаточна для записи теку

    _____________________________Глава 14. Файловые системы 429 

    щего пути, возвращаемое значение сообщает число символов, необходимое для 
хранения пути. Чтобы убедиться в успешном завершении GetCurrentDirectory, 
напишите нечто вроде: 

    TCHAR szCurDir[MAX_PATH]; 
    DWORD dwResult = GetCui-rentDirectory(sizeof (szCurDir) / sizeof(TCHAR), szCurDir); 
    if (dwResult == 0) { 
    // полный провал 
    } else { 
    if (dwResult < (sizeof(szCurDir) / sizeof(TCHAR))) { 
    // буфер достаточен 
    } else { 
    // буфер недостаточен 

    Видите, как здесь используется МАХ_РАТН? Это значение определено в 
WINDEF.H как 260. Для MS-DOS многие компиляторы С определяют макрос _МАХ_РАТН 
всего лишь как 80. Различие в значениях обусловлено длинными именами файлов, 
поддерживаемыми теперь в Windows 95 и Windows NT. Уж и не знаю, как еще 
подчеркнуть значимость поддержки длинных имен файлов, — ведь слишком многие 
программы создают буферы для имен файлов вот так: 

    char szFileName[13];       // "имя_файла" + '.' + "расширение" + нулевой_байт 

    Для длинных имен файлов они слишком малы, а значит, высока вероятность 
записи за границы буфера, ведь в таких программах не предполагается, что имя 
файла займет более 13 символов. 
    Один из способов обработки длинных имен — значительно увеличить размеры 
буферов. И здесь можно использовать МАХ_РАТН, как в одном из предыдущих 
примеров. К сожалению, заголовочные файлы Win32 не определяют макрос MAX_FILE, 
но его можно определить самостоятельно — тоже как 260. 
    Правда, этот способ годится только на сегодняшний день; будущая файловая 
система, возможно, позволит работать с именами файлов до 512 символов. Поэтому 
лучше создавать буферы для имен файлов динамически, предварительно вызывая 
GetVolumeInformation и проверяя значение, возвращаемое в буфер, на который 
указывает параметр IpMaximumComponentLength. 

    Смена текущего каталога 

    Процесс может изменить свой текущий каталог вызовом: 

    BOOL SetCurrentDirectory(LPTSTR IpszCurDir); 

    SetCurrentDirectory изменяет текущий каталог только для вызвавшего ее 
процесса, не влияя на другие процессы. Но если процесс, вызвавший 
SetCurrentDirectory, порождает после этого новый процесс, тот наследует 
текущий каталог родительского процесса, установленный последним вызовом SetCurrentDirectory. 

    Определение системного каталога 

    Кроме собственного текущего каталога, приложение может определить 
системный каталог^_вызвав: 

    UINT GetSystemDir-ectory(LPTSTR IpszSysPath. UINT cchSysPath): 


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

    Эта функция помещает в буфер IpszSysPath имя системного каталога, которое 
выглядит • примерно так:                                                       
    
    C:\WINDOWS\SYSTEM      (Windows 95)                                        
    C:\WINNT\SYSTEM32      (Windows NT) 

    Значения, возвращаемые GetSystemDirectory, следует интерпретировать так 
же, как возвращаемые GetCurrentDirectory. Приложения обычно не работают с 
системным каталогом. Кроме того, в случае разделяемой установки Windows (когда 
компьютеры, объединен\У ные в сеть, работают под управлением одной копии 
Windows, запускаемой с сервера) системный каталог защищен, и создание в нем 
новых или изменение существующих файлов попросту невозможно. Такая защита 
особенно хороша тем, что файлы в системном каталоге не «по зубам» никаким вирусам. 

    Определение основного каталога Windows 

    Если процессу надо создать или изменить файл, совместно используемый 
несколькими процессами, можно задействовать основной каталог Windows. Путь к 
нему определяется вызовом: 

    UINT GetWindowsDirectory(LPTSTR IpszWinPath, UINT cchWinPath): 

    Эта функция записывает в буфер, на который указывает параметр IpszWinPath, 
имя основного каталога Windows. Обычно оно выглядит как-то так: 

    C:\WINDOWS      (Windows 95) 
    C:\WINNT        (Windows NT) 

    При разделяемой установке Windows система создает для каждого пользователя 
его личный основной каталог Windows. Это единственный каталог, который 
принадлежит исключительно конкретному пользователю. Если тот хочет скрыть от 
других пользователей часть своих файлов, их следует размещать в основном 
каталоге Windows или в его подкаталогах. 

    Создание и удаление каталогов 


    И, наконец, еще 3 функции, оперирующие с каталогами: 

    BOOL CreateDirectory(LPTSTR IpszPath, LPSECURITY_ATTRIBUTES Ipsa); 
    BOOL CreateDirectoryEx(LPCTSTR IpTemplateDirectory, LPCTSTR IpNewDirectory, 
    LPSECURITY_ATTRIBUTES IpSecuntyAttributes); 
    BOOL RemoveDirectory(LPTSTR IpszDir-); 

    Эти функции, как и подсказывают их имена, позволяют процессу создавать и 
удалять каталоги. При создании каталога процесс может инициализировать 
структуру SECURITY_ATTRIBUTES и присвоить каталогу особые атрибуты, чтобы, 
например, другой пользователь не мог «войти» в созданный каталог или удалить 
его. Разграничение доступа возможно только при создании каталога в разделе 
NTFS. Поскольку CreateDirectory и CreateDirectoryEx не возвращают описателей, 
элемент bInheritHandle структуры SECURITY_ATTRIBUTES игнорируется. 
    CreateDirectoryEx позволяет присвоить новому каталогу атрибуты, связанные 
с каким-либо существующим каталогом (указав, например, надо ли сжимать 
файлы в каталоге).         • 
    При успешном выполнении все 3 функции возвращают TRUE, в ином случае —   | 
FALSE. RemoveDirectory дает ошибку, если каталог не пуст (т. е. содержит 
какие-то файлы   • или подкаталоги) или если процесс не имеет прав на удаление 
этого каталога.              

    _____________________________ Глава 14. Файловые системы 431 


    Копирование, удаление, перемещение и переименование файлов 

    В 16-разрядной Windows и MS-DOS всегда не хватало функций для копирования 
файлов из одного места в другое. Ее приходилось реализовать самостоятельно: 
открывать файл-источник, создавать файл-приемник, а затем, используя буфер, 
считывать фрагмент файла-источника в память и записывать его в файл-приемник. 
Дойдя до конца, оба файла нужно было закрыть. При этом в метку времени 
конечного файла заносилось время его создания, а не последнего обновления 
исходного файла. Ну и, конечно, приходилось вызывать еще несколько функций. 

    Копирование 

    В Win32 копирование файлов осуществляется простым вызовом системной функции: 

    BOOL CopyFile(LPTSTR IpszExistingFiie, LPTSTR IpszNewFile, 
    BOOL fFailIfExists); 

    CopyFile копирует файл, указанный в параметре IpszExistingFiie, в новый 
файл, полное имя которого задано в параметре IpszNewFile. Параметр 
fFailIfExists указывает: должна ли функция сообщить об ошибке, если на диске 
уже существует файл с именем, указанным в IpszNewFile. Если файл с таким 
именем есть и параметр fFailIfExists равен TRUE, функция отказывается от 
копирования. В ином случае она удаляет существующий файл и создает новый. При 
благополучном завершении CopyFile возвращает TRUE. Копировать можно либо 
неоткрытые файлы, либо открытые только для чтения. Функция сообщает об ошибке, 
если существующий файл открыт каким-либо процессом для записи. В Windows NT 4 
появилась новая функция CopyFileEx: 

    BOOL CopyFileEx(LPCWSTR IpExistingFileName, LPCWSTR IpNewFileName, 
    LPPROGRESS_ROUTINE IpProgressRoutine, LPVOID IpOata, 
    LPBOOL pbCancel, DWORD dwCopyFlags); 

    Она поможет Вам информировать пользователя о ходе операции копирования. 
Первые два ее параметра идентичны первым двум параметрам функции CopyFile. 
Третий — IpProgressRoutine — может быть NULL (если Вы не хотите уведомлять 
пользователя о ходе операции) или адресом функции обратного вызова, 
периодически вызываемой при копировании файла. (Чуть позже я расскажу об этой 
функции.) Параметр IpData — 32-битное значение, передаваемое функции обратного 
вызова, а pbCancel — указатель на переменную типа BOOL, заполняемую самой 
функцией CopyFileEx. Функция обратного вызова может заставить CopyFileEx 
прекратить копирование. Если копирование прекращается досрочно, то по 
завершении CopyFileEx значение bCancel будет TRUE. Если же файл скопирован 
полностью, bCancel становится FALSE. Параметр dwCopyFlags может быть любой 
комбинацией следующих флагов: 

    Флаг                     Описание 

    COPY_FILE_FAIL_IF_EXISTS  Заставляет CopyFileEx отказаться от копирования, 
если файл с именем, указанным в параметре IpNewFileName, уже существует. 
    COPY_FILE_RESTARTABLE   Заставляет CopyFileEx сохранять информацию о 
прогрессе операции копирования. Если операция прерывается, то при следующем 
обращении к CopyFileEx копирование возобновляется с того места, где было прервано. 

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

    Функция обратного вызова, если Вы указываете ее адрес, должна выглядеть так: 

    DWORD WINAPI CopyProgressRoutine( 
    LARGE_INTEGER TotalFileSize, LARGEJNTEGER TotalBytesTransferred, 
    LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, 
    DWORD dwStreamNumber, DWORD dwCallbackReason, 
    HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID IpData) { 
    } 

    Первые два параметра определяют размер файла и число байтов, скопированных 
на данный момент. Используя их значения, диалоговое окно отображает процент 
уже скопированных данных. Кстати, файл может состоять из нескольких потоков. 
(К сожалению, в документации на Win32 API об этом говорится очень туманно.) Но 
это дело будущего, а пока большинство файлов состоит из одного потока. 
Следующие три параметра CopyProgressRoutine определяют поток файла, копируемый 
в данный момент (dwStreamN umber), размер этого потока (StreamSize) и число 
уже скопированных байтов (StreamBytesTransferred). Отсчет потоков в файле идет 
с 1. 
    Параметр dwCallbackReason указывает причину обращения к функции обратного 
вызова. А причины могут быть таковы: 

    Причина                        Описание 

    CALLBACK_STREAM_SWITCH      Только что начато копирование потока. 
    CALLBACK_CHUNK_FINISHED     Скопирована очередная порция данных текущего потока. 

    Последние три параметра содержат описатели файла-источника и 
файла-приемника, а также 32-битное значение, передаваемое в функцию 
CopyFileEx. CopyProgressRoutine должна возвращать одно из значений: 

    Возвращаемое значение     Описание 

    PROGRESS_CONTINUE      Продолжить копирование. 
    PROGRESS_CANCEL        Прервать копирование и удалить файл-приемник. 
    PROGRESS_STOP            Прервать копирование, но не удалять 
файл-приемник. Это позволит впоследствии продолжить копирование. Возвращать 
это значение следует, только если CopyFileEx вызвана с флагом COPY_FILE_RESTARTABLE. 
    PROGRESS_QUIET          Продолжить копирование, но CopyProgressRoutine 
больше не вызывать. 

    Удаление 

    Удалить файл еще проще, чем скопировать: 

    BOOL DeleteFile(LPTSTR IpszFileName): 

    Удалив файл, заданный в параметре IpszFikName, функция возвращает TRUE при 
благополучном завершении или сообщает об ошибке, если файл не существует или 
открыт какимто процессом. 
открыт какимто процессом. 


    _____________________________Глава 14. Файловые системы 433 


    VWINDOWS/ 
    В Windows 95 функция DeleteFile удалит и открытый файл — в Windows NT j 
это невозможно. Удаление открытого файла может привести к необратимой потере 
данных. Так что закрывайте файлы перед их удалением с помощью функции DeleteFile. 


    Перемещение 

    Для этого предусмотрены 2 функции: 

    BOOL MoveFile(LPTSTR IpszExisting. LPTSTR IpszNew); 

    и 

    BOOL MoveFileEx(LPTSTR IpszExisting, LPTSTR IpszNew. DWORD fdwFlags); 

    Обе перемещают существующий файл (указанный в параметре IpszExisting) в 
другое место (указанное в параметре IpszNew ). В последний параметр нужно 
включать имя файла. Например, следующий оператор не переместит файл CLOCK.EXE 
из каталога WINNT на диске С в корневой каталог того же диска: 
    MoveFile("C:\\WINNT\\CLOCK.EXE", "C;\\"); 

    А такой — переместит: 

    MoveFile("С:\\WINNT\\CLOCK.ЕХЕ", "С:\\CLOCK.EXE"); 

    Перемещение файла не всегда равнозначно копированию файла в новое место с 
последующим удалением оригинала. Если файл перемещается между каталогами в 
пределах одного диска, то Movefile и MoveFileEx вообще не станут перемещать 
данные, хранящиеся в файле. Функции просто удаляют имя файла в первом каталоге 
и регистрируют его в другом. «Копирование» файлов корректировкой списка 
элементов в каталоге гораздо быстрее, так как реальной пересылки данных не 
происходит. К тому же при этом требуется меньше дискового пространства. При 
перемещении же файла с одного диска на другой создается дубликат исходного 
файла, а оригинал потом уничтожается. Если бы система делала то же самое при 
перемещении файла между каталогами одного диска, на нем могло бы не хватить места. 
    При успешном выполнении обе функции возвращают TRUE. Перемещение может не 
состояться, если на диске-приемнике не хватает места или на нем уже есть файл 
с именем, указанным в IpszNew. 
    Хотя по именам данных функций об этом не догадаешься, они позволяют 
переименовывать и каталоги. Например, чтобы изменить имя каталога UTILITY на 
TOOLS, напишите оператор: 

    MoveFile("С:\\UTILITY", "С:\\TOOLS"); 

    Конечно, было бы удобнее, если бы Movefile и MoveFileEx могли перемещать 
целое дерево подкаталогов, но на такие радикальные операции они не способны. 
Чтобы переместить" дерево подкаталогов в другое место на том же диске, 
придется использовать FindFirstFile, FindFirstFileEx, FindNextFile и FindClose 
(о них — далее в главе) и трижды «пройти» по иерархии каталогов. При этом на 
первом проходе надо вызвать CreateDirectory для создания аналогичной структуры 
каталогов в новом месте. На втором — вызвать MoveFile и переместить каждый 
файл по отдельности, а на последнем — RemoveDirectory, чтобы удалить старую 
иерархию каталогов. 

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


    Различия между MoveFf/e и MoveFf/eEx 

    Имея дополнительный параметр fdwFlags, функция MoveFileEx обеспечивает 
больший контроль над перемещением файлов или переименованием каталогов, чем Movefile. 

    VWINDOWS/ 
    В Windows 95 функция MoveFileEx всегда возвращает FALSE. Последующий вызов 
GetLastError дает код ERROR_CALL_NOT_IMPLEMENTED. 

    Особенности MoveFileEx проявляются, когда перемещение файла не состоялось 
из-за того, что файл с заданным именем уже есть. Чтобы удалить существующий 
файл и несмотря ни на что присвоить перемещаемому файлу то же имя, укажите при 
вызове MoveFileEx флаг MOVEFILE_REPLACE_EXISTING. При переименовании каталога 
этот флаг не действует. 
    По умолчанию MoveFileEx не перемещает файл с диска на диск. Чтобы 
разрешить и такое перемещение, укажите флаг MOVEFILE_COPY_ALLOWED. В Windows 
NT функция Movefile сама вызывает MoveFileEx и передает ей этот флаг, так что, 
используя Movefile вместо MoveFileEx, беспокоиться о нем не надо. Как и 
предыдущий флаг, он тоже не действует при переименовании каталога. 
    Если указан флаг MOVEFILE_DELAY_UNTIL_REBOOT, система — в момент вызова 
функции — ни переместит файл, ни переименует каталог. Вместо этого она 
сохраняет в реестре список файлов, перемещенных с использованием этого флага. 
При перезагрузке компьютера система просмотрит реестр и переместит или 
переименует все нужные файлы. Эта операция состоится сразу после проверки 
устройств и до создания страничных файлов. 
    Флаг MOVEFILE_DELAY_UNTIL_REBOOT обычно применяют установочные программы. 
Допустим, Вы получили новый драйвер к своей видеоплате. При его установке 
система не может удалить старый или перезаписать его, так как тот по-прежнему 
используется ею. В этом случае программа Setup копирует файл нового драйвера в 
другой каталог и не трогает имеющийся. Закончив, Setup вызывает MoveFileEx, 
указывая в параметре IpszExisting текущий путь к новому файлу, а в параметре 
IpszNew — место, где этот файл должен находиться. Кроме того, она передает 
флаг MOVEFILE_DELAY_UNTIL_REBOOT. Тогда система, добавив новый путь к своему 
списку в реестре, вернет управление Setup. В начале перезагрузки старый 
видеодрайвер будет заменен новым — вот и все. 
    Флаг MOVEFILE_WRITE_THROUGH используется только в Windows NT 4. Вызванная 
с данным флагом, MoveFileEx не вернет управление, пока файл не будет 
перемещен. Этот флаг полезен, когда MoveFileEx копирует файл на другой диск с 
последующим удалением оригинала. Но MOVEFILE_WRITE_THROUGH игнорируется, если 
задан вместе с MOVEFILE_DELAY_UNTIL_REBOOT. 
    Другое отличие MoveFileEx от Movefile в том, что первая удаляет файл 
несколько необычным образом. С помощью MoveFileEx можно удалить файл, передав 
ей "NULL в параметре IpszNew. По сути, тем самым Вы сообщаете системе, что 
хотите переместить существующий файл (IpszExisting) в никуда, и результат — 
его удаление. 

    Переименование 

    Функции RenameFik в Win32 нет. Переименование файла выполняется функциями 
Movefile или MoveFileEx. Для этого его нужно «переместить» внутри каталога, в 
котором он расположен. Чтобы переименовать CLOCK.EXE в WATCH.EXE, примените оператор: 

    MoveFile("С:\\WINNT\\GLOGK.EXE", "С:\\WINNT\\WATCH.EXE"); 


    _____________________________Глава 14. Файловые системы 435 

    Так как мы не перемещаем файл с диска на диск или из каталога в каталог, 
система просто удаляет элемент CLOCK.EXE из списка каталога WINNT и добавляет 
в него новый элемент — WATCH.EXE. Все, файл переименован. 
    Установочная программа может скопировать файл нового видеодрайвера в 
системный каталог под другим именем, затем вызвать MoveFileEx опять-таки с 
флагом MOVEFILE_DELAY_UNTIL_REBOOT. Но на этот раз, поскольку файл уже 
находится в нужном каталоге, перезагрузка системы приведет к переименованию 
файла, а не копированию. 
    Выло бы неплохо расширить возможности этих функций в работе с файлами, 
добавив поддержку символов подстановки. Разве это не замечательно — написать 
прямо вот так: 

    DeleteFile("*.BAK"); 

    и «одним махом» удалить все ВАК-файлы из текущего каталога? Но сейчас для 
этого нужно сначала создать список всех ВАК-файлов в текущем каталоге, затем 
вызвать DeleteFik для каждого удаляемого файла. При создании списка можно 
использовать функции FindFirstFile, FindFirstFileEx, FindNextFile и FindClose. 

    Поиск файлов 

    Файлы использует почти любая программа. А поскольку приложение может 
создавать множество файлов и зачастую читает файлы других программ (например, 
Microsoft Excel «понимает» файлы Lotus 1-2-3), то и поиск их стал совершенно 
рутинной задачей — настолько, что Microsoft разработала набор стандартных 
диалоговых окон, помогающих находить файлы на дисках. Однако для некоторых 
приложений диалоговых окон File Open и File Save As недостаточно. Им нужно так 
искать файлы или так обращаться к ним, что никакие стандартные окна тут не помогут. 
    Одна из частых операций — преобразование имени файла (или его имени с 
указанием частичного пути) в полное имя. В 16-разрядной Windows такое 
преобразование осуществляется вызовом OpenFile с флагом OF_PARSE. В Win32 для 
этого служит функция: 

    DWORD GetFullPathName(LPCTSTR IpszFile, DWORD cchPath, 
    LPTSTR IpszPath. LPTSTR *ppszFilePart); 

    Она принимает имя файла (и необязательную информацию о пути) через 
параметр IpszFile. Затем, используя данные о текущем каталоге и текущем диске 
процесса, формирует полное имя файла и записывает его в буфер, на который 
указывает параметр IpszPath. Максимальный размер буфера в символах задается 
параметром cchPath. Параметр ppszFilePart — адрес переменной типа LPTSTR; в 
нее функция поместит указатель на тот символ внутри буфера IpszPath, с 
которого начинается собственно имя файла (после имен каталогов). Эту 
Информацию приложение иногда использует, формируя заголовок своего окна. 
    Например, если я работаю с Wordpad и открываю файл с именем HIMOM.TXT, то 
строкой заголовка станет: 

    HIMOM.TXT - Wordpad 

    Параметр ppszFilePart введен просто для удобства. Ведь адрес имени файла 
можно получить и так: 

    szFilePath = strrchr(szPath, '\\') + 1; 

    GetFullPathName на самом деле никаких файлов не ищет — просто преобразует 
имя файла в его полное имя (вместе с путем). Фактически она даже не обращается 
к диску. 
    Если же Вы действительно хотите просмотреть диск и найти какой-то файл, 
обращайтесь к функции: 

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

    DWORD SearchPath(LPCTSTR IpszPath, LPCTSTR IpszFile, 
    LPCSTR IpszExtension, DWORD cchReturnBuffer, 
    LPTSTR IpszReturnBuffer, LPTSTR *plpszFilePart): 

    Она ищет файл в указанном Вами списке каталогов. Список передается через 
параметр IpszPath. Если он равен NULL, функция ищет файл в следующих 
каталогах"— и в таком порядке: 

    1. Каталог, из которого запущена данная программа.                         
    2. Текущий каталог.                                                        
    3. Системный каталог Windows. 
    4. Основной каталог Windows. 
    5. Каталоги, перечисленные в переменной окружения PATH. 

    Искомый файл задается параметром IpszFile. Если в строку, передаваемую в 
этом параметре, включается и расширение, то параметр IpszExtension должен быть 
NULL; другой способ передачи расширения — через параметр IpszExtension, и 
тогда оно должно начинаться с точки. Расширение подписывается к имени файла, 
только если его там нет. Ну а последние 3 параметра имеют тот же смысл, что и 
последние 3 параметра функции GetFullPathName.   ; 
    Другой метод поиска файлов — просмотр содержимого всего диска. Вы 
указываете системе начальный каталог и файл, который нужно найти, через 
функцию FindFirstFile: 

    HANDLE FindFirstFile(LPTSTR IpszSearchFile, LPWIN32_FIND_DATA Ipffd); 

    Она сообщает системе, что Вы намерены искать именно файл. Параметр 
IpszSearchFile указывает на строку (с нулевым символом в конце), содержащую 
имя файла. В имя можно включать символы подстановки (* и ?) и начальный путь. 
Параметр Ipffd— адрес структуры LPWIN32_FIND_DATA: 

    typedef struct _WIN32_FIND_DATA { 
    DWORD dwFileAttributes; 
    FILETIME ftCreationTime; 
    FILETIME ftLastAccessTime; 
    FILETIME ftLastWnteTime; 
    DWORD nFileSizeHigh; 
    DWORD nFileSizeLow; 
    DWORD dwReservedO; 
    DWORD dwReservedl; 
    CHAR cFileName[ MAX_PATH ]; 
    CHAR cAlternateFileName[ 14 ]; 
    } WIN32_FIND_DATA, * PWIN32_FIND_DATA, *LPWIN32_FIND_DATA; 

    Обнаружив файл, соответствующий заданной спецификации и расположенный в 
заданном каталоге, функция заполняет элементы структуры и возвращает 
описатель, а при отсутствии такого файла — возвращает код INVALID_HANDLE_VALUE 
и не изменяет со-   I держимое структуры.                                      
                        ; 
    Функция FindFirstFile в случае неудачи возвращает INVALID_HANDLE_VALUE, а 
не NULL. 

    ^ВНИМАНИЕ \ 
    Структура WIN32_FIND_DATA содержит информацию о найденном файле: атрибуты, 
метки времени и размер. Элемент cFileName содержит истинное имя файла. Его 
обычно и используют. В элемент cAlternateFileName помещается так называемый 
псевдоним файла. 

    _____________________________Глава 14. Файловые системы 437 

    Допустим, Вы работаете с программой, написанной для 16-разрядной Windows. 
Открыв диалоговое окно File Open, Вы видите список файлов в текущем каталоге. 
Но что в нем появится, если текущий каталог расположен на томе NTFS, а длина 
имен файлов в нем составляет в среднем по 50 символов? 
    Под OS/2 программа, не способная распознавать имена файлов в формате, 
принятом в HPFS, файлов просто не «увидит». В случае Win32 Microsoft решила (и 
правильно!), что такие файлы должны быть доступны пользователю. Так как 
приложение для 16-разрядной Windows не работает с длинными именами файлов, 
система преобразует их в стандарт «8.3». Преобразованное, или альтернативное 
имя (псевдоним) и записывается в элемент cAlternateFileName структуры WIN32_FIND_DATA. 
    Для файлов с краткими именами содержимое элементов cFileName и 
cAlternateFileName, конечно, совпадает. Для длинных — в элемент cFikName 
записывается настоящее имя, а в элемент cAlternateFileName — псевдоним. 
Например, система может преобразовать длинное имя файла «Hello Mom and Dad» в 
псевдоним «HELLOM-1». 
    Существует такая функция: 

    DWORD GetShortPathName(LPCTSTR IpszLongPath, LPTSTR IpszShortPath, 
p^QRp cchBuffer); 
    
    /ВНИМАНИЕ\ 
    В параметре IpszLongPath Вы передаете ей адрес буфера, содержащего длинное 
имя файла, а она возвращает соответствующее краткое имя — через буфер, на 
который указывает параметр IpszShortPath. Размер этого буфера в символах Вы 
передаете в параметре cchBuffer. Функция возвращает число символов, 
скопированных ею в буфер. 

    Если FindFirstPile нашла подходящий файл, Вы можете вызвать FindNextFile 
для поиска следующего файла, удовлетворяющего спецификации, переданной при 
вызове FindFirstFile: 

    BOOL FindNextFile(HANDLE hFindFile, LPWIN32_FIND_DATA Ipffd): 

    Первый ее параметр — описатель, полученный предыдущим вызовом 
FindFirstFile. Второй — вновь указывает на структуру WIN32_FIND_DATA. Это 
может быть и другой экземпляр структуры, а не тот, что Вы указали при вызове FindFirstFile. 
    При успешном выполнении FindNextFile возвращает TRUE и заполняет структуру 
WIN32_FIND_DATA; в ином случае она возвращает FALSE. 

    VWINDOWS/ 
    В Windows 95 функции FindFirstFile и FindNextFile реализованы с ошибкой. 
Эта операционная система интерпретирует символ подстановки «?» как «любой 
символ», а не «отсутствие символа или любой символ». Поэтому, если Вы 
передадите FindFirstFile строку FIL??.???, файл FILEA.TXT функция найдет, а 
файл FILE.TXT — нет. В Windows NT этой ошибки нет, и функция обнаружит оба файла. 
    Закончив поиск, закройте описатель, возвращенный FindFirstFile, вызвав: 

    BOOL FindClose(HANDLE hFindFile); 

    /внимдниЕ\ 
    Это один из тех (весьма редких в Win32) случаев, когда для закрытия 
описателя CloseHandle не применяется. Вместо нее вызывается FindClose — она 
удаляет и кое-какую служебную информацию, которую использовала система. 
    FindFirstFile и FindNextFile позволяют просматривать только те файлы (и 
подкаталоги), что находятся в одном, указанном Вами каталоге. Чтобы «пройти» 
по всей иерархии каталогов, придется написать свою рекурсивную функцию. 

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

    В Windows NT 4 появилась новая функция FindFirstFileEx: 

    HANDLE FindFirstFileEx(LPCTSTR IpFileName, 
    FINDEX_INFO_LEVELS finfolevelld, LPVOID IpFindFileData. 
    FINDEX_SEARCH_OPS fSearchOp, LPVOID IpSearchFilter, 
    DWORD dwAdditionalFlags); 

    Если Вы убедите своих пользователей работать в Windows NT 4, эта функция 
упростит Вам жизнь. Ее параметр IpFileName содержит стартовый каталог и 
спецификацию (с возможностью применения символов подстановки) имени нужного 
Вам файла или каталога. Параметр flnfobevelld определяет тип возвращаемой 
информации для найденных файлов или каталогов (в Windows NT 4 в нем надо 
передать FindExInfoStandard). Параметр IpFindFileData — указатель на структуру 
данных, куда записывается информация о найденном файле или каталоге. Каждому 
типу flnfoLevelId соответствует своя структура данных. Если в flnfoLevelld 
задан тип FindExInfoStandard, параметр IpFindFileData должен указывать на 
структуру WIN32_FIND_DATA. 
    Именно благодаря параметрам fSearchOp и IpSearchFilter функция 
FindFirstFileEx мощнее FindFirstFile. Они позволяют вести поиск с 
дополнительными фильтрами. Вы можете передать через fSearchOp только один из 
следующих флагов: 

    Флаг                       Описание 

    FindExSearchNameMatch         Обычный поиск по спецификации, указанной 
через параметр IpFileName. 
    FindExSearchLimitToDirectories     Осуществляется поиск только имен 
каталогов. (Некоторые файловые системы этот флаг игнорируют.) 
    FindExSearchLimitToDevices        Осуществляется поиск только имен устройств. 

    Каждому значению fSearchOp может соответствовать своя структура данных с 
дополнительными критериями поиска. В настоящее время ни одна из 
документированных операций поиска не имеет дополнительных критериев, поэтому 
вместо параметра IpSearchFilter следует передавать NULL. Но представьте, что 
существуют такие операции поиска, в результате которых FindFirstFileEx 
возвращает файлы, модифицированные в определенном диапазоне значений, или 
файлы с определенной текстовой строкой. Тогда IpSearchFilter указывал бы на 
структуру, задающую этот диапазон значений либо текстовую ANSI- или Unicode-строку. 
    Последний параметр, dwAdditionalFlags, позволяет устанавливать 
дополнительные флаги. На сегодня определен лишь один — 
FIND_FIRST_EX_CASE_SENSITIVE. По умолчанию все \^т32-функции сравнивают имена 
файлов и каталогов, не учитывая регистра букв. А этот флаг меняет их 
поведение, заставляя учитывать регистр букв при поиске. Добавить его в Windows 
NT было просто необходимо, так как приложения, рассчитанные на подсистему 
POSIX, способны создавать чувствительные к регистру букв имена, например: 
ReadMe.txt, README.TXT и readme.txt. Win32-пpилoжeниe сможет открыть только 
один из этих файлов, а остальные окажутся ему недоступны. Так вот, флаг 
FIND_FIRST_EX_CASE_SENSITIVE обеспечит доступ этому приложению ко всем 
перечисленным файлам. 
    FindFirstFileEx, как и FindFirstFile, возвращает описатель, который в 
дальнейшем можно использовать при вызове FindNextFile и FindClose. 

    /внимАниЕ\ 
    Это один из тех (весьма редких в Win32) случаев, когда для закрытия 
описателя CloseHandle не применяется. Вместо нее вызывается FindClose — она 
удаляет и кое-какую дополнительную информацию, которую использовала система. 

    _____________________________Глава 14. Файловые системы 439 


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

    Программа DirWalk (DIRWALK. EXE) (см. листинг на рис. 14-2) демонстрирует 
применение функций FindFirstFile, FindNextFile, FindClose, GetCurrentDirectory 
и SetCurrentDirectory для просмотра всего дерева каталогов на дисковом томе. 
Файлы исходного кода и ресурсов для этой программы находятся в каталоге 
DIRWALK на компакт-диске, прилагаемом к книге. Она начинает с корневого 
каталога на текущем диске и выводит в диалоговое окно список, отражающий все 
дерево каталогов диска. На моей машине диалоговое окно Directory Walker 
выглядит так: 
    
    IO.SYS 
    BOOTLOG.TXT 
    AUTOEXEC.DOS 
    IO.DOS 
    MSDOS.DOS 
    SUHDLOG.BAK 
    COMMAND.COM 
    BOOTSECT.DOS 
    COMMAND.DOS 
    D011V200.SYS 
    MSDOS— 
    SETUPLOG.OLD 
    SUHDLOG.DAT 
    SUHDLOG— 
    DETLOG.TXT 
    В О ОТ. ВАК 
    BOOT.INI 
    AUTOEXEC.BAT 
    CONFIG.DOS 
    DRVSPACE.BIN 
    SETUPLOG.TXT 

    Получив свое сообщение WM_INITDIALOG, диалоговое окно проводит кое-какую 
инициализацию и вызывает функцию DirWalk — ее текст содержится в файле DIRWALK.C: 

    void DirWalk(HWND hwndTreeLB, LPCTSTR pszRootPath); 

    Параметр hwndTreeLB — описатель окна списка, куда функция выводит 
информацию, а pszRootPath определяет стартовый каталог. В последнем параметре 
передается «\\», поэтому просмотр начинается с корневого каталога. Здесь, 
конечно, можно задать любой другой каталог. 
    При вызове DirWalk инициализирует ряд переменных, а потом обращается к 
функции DirWalkRecurse. Рекурсивная функция периодически вызывает саму себя — 
по мере прохождения разных уровней дерева каталогов. Перед началом просмотра 
диска DirWalk запоминает текущий каталог во временной переменной и 
устанавливает новый текущий каталог в соответствии со значением pszRootPath. 
    Далее поток обращается к DirWalkRecurse, которая, добавив в список имя 
текущего каталога, вызывает FindFirstFile, чтобы получить имя первого файла в 
текущем каталоге. Если файл найден, он добавляется в список, и программа 
вызывает FindNextFile, чтобы узнать имя следующего файла в каталоге. Показав 
имена всех файлов, DirWalkRecurse проверяет элемент fRecurse структуры 
DIRWALKDATA, чтобы узнать, надо ли просматривать подкаталоги. В данном случае 
значение этого элемента всегда TRUE. Я добавил элемент fRecurse потому, что 
эти функции используются в программе FILECHNG.EXE, представленной в конце главы. 

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

    Для входа в подкаталог DirWalkRecurse вызывает FindFirstChildDir. Эта 
крошечная функция, размещенная в DIRWALK.C, — просто надстройка над 
FindFirstFile. Она отфильтровывает файлы и возвращает только подкаталоги. А 
вспомогательная функция FindNextChildDir — аналогичная надстройка над 
FindNextFile, также отделяющая имена файлов. 
    DirWalkRecurse входит в каждый из найденных подкаталогов, вызывает саму 
себя для его просмотра и, закончив, делает вызов: 

    SetCurrentDirectory(__TEXT("..")): 

    восстанавливая тем самым текущий до ее вызова каталог. 
    Перед просмотром дерева каталогов DirWalk создает в стеке локальную 
структуру DW типа DIRWALKDATA, где содержится информация, используемая 
функцией DirWalkRecurse. 
    Начав писать программу, я разместил все элементы этой структуры как 
локальные переменные в DirWalkRecurse. При каждом рекурсивном вызове в стеке 
создавался новый набор этих переменных. Но вскоре я увидел, что при просмотре 
глубоко вложенных каталогов поглощается значительный объем стека. Поэтому я 
стал искать более эффективный способ хранения этих переменных. Попробовал 
объявить элементы DIRWALKDATA статическими переменными. Таким образом, подумал 
я, будет только один набор переменных, и они вообще не займут места в стеке. 
Все, кажется, неплохо, но тогда DirWalk и DirWalkRecurse не удастся 
использовать в многопоточном приложении. Если 2 потока захотят просмотреть 
дерево каталогов одновременно, они задействуют одни и те же статические 
переменные, что приведет к нежелательным эффектам. Это соображение заставило 
меня снова модифицировать программу. 
    Я поместил все эти переменные в статическую локальную память потока, 
указав _declspec (thread) перед объявлением каждой из переменных. Это должно 
привести к созданию нового набора переменных для каждого потока, порожденного 
в процессе. Единственное, что мне здесь не понравилось, — набор переменных 
создается для каждого потока, в том числе и для тех, что не собираются 
обращаться к DirWalk и DirWalkRecurse. 
    А может, попробовать динамическую локальную память потока? Тогда я 
зарезервировал бы только TLS-индекс. При этом DirWalk вызывала бы НеарАНос для 
выделения памяти под структуру DIRWALKDATA и сохраняла бы ее адрес с помощью 
TIsSetValue. «Динамический» подход решает проблему выделения лишней памяти для 
потоков, не вызывающих DirWalk, плюс к тому память, отводимая под структуру 
данных, оказывается занятой лишь на время вызова DirWalk. Перед возвратом 
управления DirWalk освобождает данный буфер. 
    Дойдя до этого места, я наконец-то сообразил: нужно создать структуру .в 
стеке потока и при каждом рекурсивном вызове просто передавать указатель не 
нее. Это решение Вы и найдете в тексте программы. Переменные размещаются в 
стеке, память под них выделяется только при необходимости, а в случае 
рекурсивного вызова в стек передается лишь четырехбайтовый указатель. 
По-моему, я выбрал лучший из возможных компромисс. 
    Несмотря на все усилия, оказалось, что DirWalk и DirWalkRecurse 
по-пг>ежнемуне годятся для многопоточной программы. И знаете, почему? Из-за 
SetCurrentDirectory. Информация о текущем каталоге хранится в переменной, 
глобальной для всего процесса. Изменяя текущий каталог в одном потоке. Вы 
изменяете его и для остальных. Чтобы приспособить DirWalk и DirWalkRecurse к 
многопоточной среде, надо избавиться от вызовов SetCurrentDirectory. Сделать 
это можно, сохраняя «пройденный» путь в строковой переменной и передавая в 
FindFirstDir полный путь, а не сокращенные пути — относительные к текущему 
каталогу. Но мной владело желание, помимо всего, продемонстрировать применение 
функций GetCurrentDirectory и SetCurrentDirectory, так что эту модификацию 
программы я оставляю Вам — упражняйтесь! 

    _____________________________Глава 14. Файловые системы 441 


    DIRWALK.C 
    //********************************* 
    Модуль: DirWalk.C 
    Авторы: Джим Харкинс (Jim Harkins) и Джеффри Рихтер (Jeffrey Richter) 
    Примечание: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    *.*...*.*.,.*......*.....**..,,,****.**.......,**....,,*..,.,...*..**....*.**,/ 
    (tinclude "..\CmnHdr.H"           /* см. приложение В «/ 
    ftinclude <windows,h> 
    ftinclude <windowsx.h> 
    ftinclude <tchar.h> 
    ftinclude <stdliP.h> 
    ftinclude <stdio.h>               // для sprintf 
    ftinclude Otring. h> 
    Sinclude "Resource.H" 
    ll/lllll/llll/lllllllllllll/lllll/lllll/llilll/llllllllllllllllllllllll/lilllll 
    static BOOL IsChildDir (WIN32_FIND_DATA *lpFindData) { 
    return(((lpFindData->dwFileAttributes & FILE_ATTRIBUTE_OIRECTORY) != 0) && 
    (lstrcmp(lpFindData->cFileName, __TEXT(".")) !=0)&& 
    (lstrcmp(lpFindData->cFileName, __TEXT(", .")) !=0)); 
    t 
    f 
    llillllllllllltlllllllll/lll/lllllll////lll/l/ll/H//lll/llfl//llllllllllll//l/ 
    static BOOL FindNextChildDir (HANDLE tiFindFile, 
    WIN32_FIND_DATA *lpFindData) { 
    BOOL fFound = FALSE; 
    do { 
    fFound = FindNextFile(riFindFile, IpFindData); 
    } while (fFound && !IsChildDir(lpFindData)); 
    return(fFound); 
    } 
    IIIIIIIIIIH/llll/l//l/l/llll///llllllllll//l/ltlll/lllll/l/llllllllllillllllll 
    static HANDLE FindFirstChildDir (LPTSTR szPath, 
    WIN32_FIND_DATA *lpFindData) { 
    BOOL fFound; 
    HANDLE hFindFile = FindFirstFile(szPath, IpFindData); 
    if (hFindFile != INVALID_HANDLE_VALUE) { 
    fFound = IsChildDir(lpFindData); 

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

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

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


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

    if (ifFound) 
    fFound = FindNextChildDir(hFindFile, IpFindData); 
    if (!fFound) { 
    FindClose(hFindFile); 
    hFindFile = INVALID_HANDLE_VALUE; 
    I j 
    return(hFindFile): 
    i 
    IIIHIIIII/lllllllllllillllllllllllllllllllllllllllllll/llllllllllll/llllllllll 
    
    II чтобы сократить объем используемого стекового пространства, 
    // я создаю один экземпляр структуры DIRWALKDATA как локальную 
    // переменную, а указатель на нее передаю в DirWalkRecurse 
    
    // данные, используемые DirWalkRecurse 
    typedef struct { 
    HWND  hwndTreeLB;         // описатель окна списка для вывода 
    int   nDepth;             // глубина рекурсии 
    BOOL  fRecurse;           // установить в TRUE для просмотра подкаталогов 
    TCHAR szBuf[1000],        // буфер для форматирования вывода 
    int   nindent;            // число символов для сдвига вправо 
    BOOL  f0k;                // флаг управления циклом 
    BOOL  fIsDir;             // флаг управления циклом 
    WIN32_FIND_DATA FindData; // информация о файле 
    } DIRWALKDATA, *LPDIRWALKDATA; 
    ../,//,//////////////////////////////////////////////.////////////////////////////// 
    
    // Просматриваем дерево каталогов и заполняем окно списка именами 
    // файлов. Если установлен pDW->fRecurse, то просматриваем и все 
    // подкаталоги - с помощью рекурсивного вызова DirWalkRecurse. 
    static void DirWalkRecurse (LPDIRWALKDATA pDW) { 
    HANDLE hFind; 
    pDW->nDepth++; 
    pDW->n!ndent = 3 * pDW->nDepth; 
    _stprintr(pDW->szBur. __TEXT("%*s"), pDW->n!ndent, __TEXT("")): 
    GetCurrentDirectory(chDIMOF(pDW->szBuf) - pDW->n!ndent, 
    &pDW->szBuf[pDW->nIndent]); 
    ListBox_AddString(pDW->hwndTreeLB, pDW->szBuf); 
    hFind = FindFirstFile(__TEXT("*,*"), &pDW->FindData); 
    pDW->fOk = (hFind != INVALID_HANDLE_VALUE); 
    * 

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

    _____________________________Глава 14. Файловые системы 443 


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

    while (pDW->fOk) { 
    pOW->fIsDir = pDW->FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; 
    if (!pDW->f!sDir !; (!pDW->fRecurse && IsChildDir(&pDW->FindData))) { 
    ^stprintf(pDW->szBuf, 
    pDW->fIs01r ? __TEXT("%*s[%s]") : __TEXT("%*s%s"), 
    pDW->nIndent, __TEXT(""), pDW->FindData.cFileName); 
    ListBpx_AddString(pDW->hwndTreeLB, pDW->szBuf); 
    } 
    pDW->fOk = FindNextFile(hFind, &pDW->FindData); 
    } 
    if (hFind != INVALID_HANDLE_VALUE) 
    FindClose(hFind); 
    if (pDW->fRecurse) { 
    // получаем первый дочерний каталог 
    hFind = FindFirstChildDir(__TEXT("*.*"), &pOW->FindData); 
    pDW->fOk = (hFind != INVALID_HANDLE_VALUE); 
    while (pDW->fOk) { 
    // входим в дочерний каталог 
    if (SetCurrentDirectory(pDW->FindData.cFileName)) { 
    
    // Выполняем рекурсивный обход дочернего каталога. 
    // Помните, что значения некоторых элементов pDW 
    // будут изменены этим вызовом. 
    DirWalkRecurse(pDW); 
    
    // возвращаемся в родительский каталог дочернего каталога 
    SetCurrentDirectory(__TEXT("..")); 
    } 
    pDW->fOk = FindNextChildDir(hFind, &pDW->FindData); 
    } 
    if (hFind != INVALID_HANDLE_VALUE) 
    FindClose(hFind); 
    } 
    pDW->nDepth—; 
    } 
    /////////////////////////////////////////////////////////////////////7////////7 
    
    // Просматриваем структуру каталогов и заполняем окно списка именами 
    // файлов. Эта функция подготавливает вызов DirWalkRecurse, которая 
    // и осуществляет основную работу. 
    void DirWalk ( 
    HWND hwndTreeLB,    // окно заполняемого списка 

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

    444 _____________________________ Windows для профессионалов 

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


    LPCTSTR pszRootPath,   // начальная точка просмотра дерева 
    BOOL fRecurse) {       // входить в подкаталоги, если TRUE 
    TCHAR szCurrDir[_MAX_OIR]; 
    DIRWALKDATA OW; 
    // очищаем окно списка 
    ListBox_ResetContent(hwndTreeLB); 
    // сохраняем текущий каталог для последующего восстановления 
    GetCurrentDirectory(chDIMOP(szCurrDir), szCurrDir); 
    
    // устанавливаем текущим тот каталог, 
    // с которого мы хотим начать просмотр 
    SetCurrentOirectoгу(pszRootPath); 
    
    // nDepth используется для управления сдвигом строк в окне списка 
    // вправо. Значение -1 заставляет выводить каталоги первого уровня 
    // в крайнюю левую позицию. 
    OW. nDepth = -1; 
    DW.hwndTreeLB = hwndTreeLB; 
    DW.fRecurse = fRecurse; 
    // вызываем рекурсивную функцию для просмотра дерева каталогов 
    DlrWalkRecurse(&DW); 
    
    // восстанавливаем текущим тот каталог, который и был таковым 
    // перед вызовом функции 
    SetCuг rent Directory(szCurrDir); 
    } 
    .///////////////.////////////////////////////////////V/////////////,'/"///////////' 
    BOOL Dlg_OnInitDialog (HWND hwnd, HWNO nwndFocus, LPARAM IParam) { 
    RECT re; 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_DIRWALK, IDI_DIRWALK): 
    DirWalk(GetDlgItem(hwnd, IDC_TREE), __TEXT("\\"), TRUE); 
    GetClientRect(hwnd, &rc); 
    
    SetWindowPos(GetDlgItem(hwnd, IDC_TREE), NULL, 
    0, 0, re. right, re.bottom, SWP_NOZORDER); 
    return(TRUE): 
    } 

    I'M. след. стр. 

    _____________________________Глава 14. Файловые системы 445 


    РИС. 14-2  (продолжение) 
    llllllllllllllllllllllllll/11111111/lllllllllllillll/llllllllllllllllllltllllll 
    void Dlg_OnSize (HWND hwnd, UINT state, int ex, int cy) { 
    SetWindowPos(QetDlgItem(hwnd, IDC_TREE), NULL, 0. 0, ex. cy, SWP_NOZOROER); 
    \ 
    void Dlg_OnCommand (HWND hwnd, int id, HWND hwndCtl. UINT codeNotify) { 
    switch (id) { 
    case IOCANCEL: 
    EndDialog(hwnd, id); 
    break; 
    case IDOK: 
    
    // вызываем рекурсивную функцию для "прохода" по дереву 
    DirWalk(GetDlgItem(hwnd, IDC_TREE). __TEXT("\\"), TRUE); 
    break; 
    \ 
    BOOL CALLBACK Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPAHAM 
iParam) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM_INITDIALOG,  Dlg_OnInitDialog); 
    chHANDLE_DLGMSG(hwnd, WM_SIZE,         Dlg_OnSize); 
    chHANDLE_DLGMSG(hwnd, WM_COMMAND,      Dlg_OnCommand); 
    return(FALSE); 
    i, 
    ////////////////•      , / ,•   !  ;  ,••,'/     ,  ' '       ,   :   :   
    int WINAPI _tWlnMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLlne, int nCrndShow) { 
    ChWARNIFUNICODEUNDERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_OIRWALK), NULL, Dlg_Proc); 
    return(O); 


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


    Уведомления об изменениях в файловой системе 

    Есть масса программ, которым нужно узнавать об изменениях, происходящих в 
файловой системе. Возможно, Вы этого не замечали, но стандартное диалоговое 
окно Open (как и Explorer) автоматически обновляет свое содержимое при любых 
изменениях в файловой системе. Чтобы увидеть это в действии, проделайте 
эксперимент. В любом приложении, использующем стандартное диалоговое окно File 
Open, выберите из меню File команду Open. Из командной строки скопируйте файл 
с гибкого диска в каталог, отображаемый в данный момент диалоговым окном. 
Когда файл будет скопирован, диалоговое окно автоматически обновится и покажет 
в каталоге новый файл. 
    Этой возможности давно ожидали от 16-разрядной Windows, и, по сути, в ней 
была функция такого рода — FileCDR; приложение могло использовать ее для 
получения уведомлений об изменениях в файловой системе. Но с FileCDR возникало 
сразу две проблемы: вопервых, она незадокументирована и, во-вторых, не 
позволяет получать уведомления более чем одному приложению сразу. Если другая 
программа вызывает FileCDR, предыдущая перестает получать уведомления. 
    Возможность динамического уведомления приложений об изменениях в файловой 
системе требовалась столь многим, что в Windows 95 и в Windows NT ввели ее 
прямую поддержку. Вот как все это работает. Сначала приложение сообщает 
системе, что оно заинтересовано в таких уведомлениях, для чего вызывает 
FindFirstChangeNotification: 

    HANDLE FindFirstChangeNotification(LPTSTR IpszPath, 
    BOOL fWatchSubTree, DWORD fdwFilter): 

    Первый параметр этой функции определяет стартовый каталог. Можно указать 
как корневой каталог диска, так и любой подкаталог. Задав подкаталог, Вы уже 
не получите уведомлений о событиях в «вышестоящих» каталогах. Для наблюдения 
за деревьями каталогов, расположенных на разных дисках, надо сделать несколько 
вызовов FindFirstChangeNotification — по одному на каждый интересующий Вас диск. 
    Параметр fWatchSubTree сообщает системе, нужно ли наблюдать и за 
подкаталогами, входящими в каталог IpszPath. Если он равен FALSE, посылаются 
уведомления лишь о событиях, происшедших непосредственно в указанном каталоге. 
    Третий параметр указывает тип интересующих Вас изменений и представляет 
собой набор флагов, комбинируемых побитовой операцией OR: 

    Флаг                              Описание 

    FILE_NOTIFY_CHANGE_FILE_NAME     Создание, переименование или удаление файла. 
    FILE_NOTIFY_CHANGE_DIR_NAME      Создание, переименование или удаление каталога. 
    FILE_NOTIFY_CHANGE_ATTRIBUTES     Изменение атрибута файла. 
    FILE_NOTIFY_CHANGE_SIZE            Изменение размера файла. 
    FILE_NOTIFY_CHANGE_CREATION      Изменение времени создания файла. 
    FILE_NOTIFY_CHANGE_LAST_WRITE    Изменение времени последней записи в файл. 
    FILE_NOTIFY_CHANGE_LAST_ACCESS    Изменение времени последнего обращения к файлу. 
    FILE_NOTIFY_CHANGE_SECURITY       Изменение дескриптора защиты (security 
descriptor) файла или каталога. 

    Система зачастую буферизует изменения, происходящие с файлами. Например, 
размер файла не меняется, пока информация из буфера не переписана на диск. 
Уведомление об 

    _____________________________Глава 14. Файловые системы 447 

    изменении размера файла отправляется только после фактической записи 
данных на диск, а не после их модификации программой. 
    При успешном выполнении FindFirstChangeNotification возвращает описатель, 
который Ваш поток может использовать при вызове таких синхронизирующих 
функций, как WaitForSingleObject или WaitForMultipleObjects. Если Вы передали 
в FindFirstChangeNotification неверный параметр^^апрймёр,' путь 
к'несуществующему каталогу), функция возвращает INVALID_HANDLE_VALUE. 

    ^ВНИМАНИЕ \ 
    Как и FindFirstFile, функция FindFirstChangeNotification возвращает в 
случае ошибки код INVALID_HANDLE_VALUE, а не NULL. 

    Лично мне кажется, что эту функцию стоило бы назвать не 
FindFirstChangeNotification, а как-то вроде CreateFileChangeNotification, 
потому что никаких изменений она на самом деле не ищет, а просто создает 
объект «уведомление об изменении файла» и возвращает его описатель. 
    Получив описатель .объекта-уведомления. Вы можете использовать его в 
вызовах WaitForSingleObject или WaitForMultipleObjects. Объект переходит в 
свободное состояние всякий раз, когда в файловой системе происходит изменение, 
соответствующее критериям, указанным при вызове FindFirstChangeNotification. 
Этот объект можно рассматривать как событие со сбросом вручную, в которое 
встроена дополнительная логика: событие становится свобоТГНЫм при каком-либо 
изменении в файловой системе. После возврата из WaitForSingleObject или 
WaitForMultipleObjects программа «понимает», что ей надо вновь просмотреть 
дерево каталогов, начиная с IpszPath, и обновить информацию о файлах и каталогах. 
    Система накапливает информацию об изменениях и сообщает сразу обо всех. 
Например, если пользователь ввел команду: 

    deltree .    (Windows 95) 
    rmdir . /s   (Windows NT) 

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

    BOOL FindNextChangeNotification(HANDLE hChange); 

    Эта функция сбрасывает объект-уведомление в занятое состояние по аналогии 
с ResetEvent. Но здесь и проявляются отличия объекта-уведомления от события со 
сбросом вручную. При просмотре дерева каталогов Ваш поток может быть вытеснен 
потоком командного процессора, и тот продолжит удаление файлов и каталогов. 
Вызов FindNextChangeNotification позволит выяснить, не случилось ли именно 
так, и, если с момента последнего перевода объекта-уведомления в незанятое 
состояние в файловой системе произошли новые изменения, объект не сбрасывается 
в занятое состояние, оставаясь свободным. 
    Таким образом, если Ваш поток снова ждет, когда объект станет незанятым, 
его ожидание немедленно прекращается, и Вы вновь «проходите» по дереву 
каталогов. После каждого вызова FindNextChangeNotification следует обязательно 
дождаться перевода объекта-уведомления в незанятое состояние. Иначе Ваш поток 
пропустит изменение в файловой системе. 

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

    Как обычно, когда уведомления об изменениях файлов больше не нужны, 
закройте объект-уведомление вызовом: 

    BOOL FindCloseChangeNotification(HANDLE hChange); 


    /внимдниЕ\ 
    Это один из тех (весьма редких в Win32) случаев, когда для закрытия 
описателя CloseHandle не применяется. Вместо нее вызывается 
FindCloseChangeNotification, которая удаляет записи о том, что произошло вслед 
за последним переводом объекта «уведомление об изменении файла» в свободное состояние. 

    Как я уже говорил, проблема в том, что FindFirstChangeNotification 
сообщает только о самом факте изменения. Чтобы узнать, что же произошло на 
самом деле, надо просмотреть каталог или целое дерево каталогов. Это не совсем 
удобно, и поэтому в Windows NT 4 добавлена функция: 

    BOOL ReadDirectoryChangesW(HANDLE hDirectory, 
    LPVOID IpBuffer, DWORD nBufferLength, 
    BOOL bWatchSubtree, DWORD dwNotifyFilter, 
    LPDWORD IpBytesReturned, LPOVERLAPPED IpOverlapped, 
    LPOVERLAPPED_COMPLETION_ROUTINE IpCompletlonRoutine); 

    Поскольку в конце ее имени стоит буква W, она существует только в 
Unicode-версии, т. е. возвращает строки исключительно в кодировке Unicode. 
Первый параметр функции — описатель каталога; его можно получить таким вызовом 
CreateFile (об этой функции см. главу 15): 

    HANDLE hdir = CreateFile( 
    "C:\\Program Files",                // путь к каталогу 
    FILE_LIST_DIRECTORY,                // доступ (ReadDirectoryChangesW 
    // требует FILE_LIST_DIRECTORY) 
    FILE_SHARE_READ ; FILE_SHARE_DELETE,// режим совместного доступа 
    NULL,                               // дескриптор защиты 
    OPEN_EXISTING,                      // режим создания 
    FILE_FLAG_BACKUP_SEMANTICS          // атрибуты 
    // (для каталога нужен BACKL)P_SEMANTICS) 
    NULL);                               // игнорируется при открытии 
    // файла или каталога 
    Закончив работу с описателем каталога, закройте его: 
    CloseHandle(hdir); 
    Аргументы IpBuffer и nBufferLength определяют адрес и длину (в байтах) 
блока памяти, содержащего массив структур FILE_NOTIFY_INFORMATION: 
    typedef struct _FILE_NOTIFY_INFORMATION { 
    DWORD NextEntryOffset; 
    DWORD Action; 
    DWORD FileNameLength; 
    WCHAR FileName[1]; 
    } FILE_NOTIFY_INFORMATION; 

    При каких-либо изменениях в файловой системе ReadDirectoryChangesW 
заполняет этот блок памяти, регистрируя истинную природу изменений. Поскольку 
структуры могут быть разной длины, элемент NextEntryOffset содержит число 
байтов, занимаемых данной записью. Отсчитав это число байтов от начала текущей 
структуры, Вы получаете адрес следующей. Если NextEntryOffset содержит 0, 
значит, Вы в конце списка. Элемент Action сообщает, почему данная запись 
включена в список. Вот возможные причины: 

    _____________________________Глава 14. Файловые системы 449 


    Значение элемента Action               Описание 
    
    FILE_ACTION_ADDED                  Файл добавлен в каталог. 
    FILE_ACTION_REMOVED                Файл удален из каталога. 
    FILE_ACTION_MODIFIED                Файл модифицирован (например, изменены 
атрибуты или метка времени). 
    FILE_ACTION_RENAMED_OLD_NAME     Файл переименован, и это его старое имя. 
    FILE_ACTION_RENAMED_NEW_NAME    Файл переименован, и это его новое имя. 

    Элемент FileNameLength сообщает длину собственно имени модифицированного 
файла. Его значение указывается в байтах, и, чтобы получить длину имени в 
символах, надо поделить значение FileNameLength на sizeof(WCHAR), т. е. на 2. 
Кроме того, здесь не учитывается нулевой символ в конце строки с именем. 
Элемент FileName содержит первый символ имени файла. А само имя всегда 
хранится в кодировке Unicode без нулевого символа в конце. 
    Через параметр bWatchSubtree можно определить, отслеживать ли подкаталоги 
в указанном каталоге. В параметр dwNotifyFilter надо записать типы 
интересующих Вас изменений. Вы можете передать через него любую комбинацию тех 
же флагов FILE_NOTIFY_*, что и в FindFirstChangeNotification. 
    Если Вы открыли описатель каталога с помощью CreateFile (описанным выше 
способом), то ReadDirectoryChangesW будет работать в синхронном режиме. Это 
значит, что она не вернет управления, не заполнив блок памяти с массивом 
структур. При синхронном вызове"]? 'ed'dDTrectoryChanges W параметр 
IpBytesReturned является адресом переменной типа DWORD, которая содержит число 
байтов, записанных в буфер (на него указывает параметр IpBuffer). 
    Однако ReadDirectoryChangesW способна работать и в асинхронном режиме; 
тогда она возвращает управление сразу после вызова, а отслеживание изменений в 
каталоге берет на себя система. Если таковые происходят, система уведомляет 
Вас об этом одним из трех способов (я вкратце расскажу о них). Но прежде 
(чтобы ReadDirectoryChangesW начала работать асинхронно) Вы должны открыть 
описатель каталога, вызвав CreateFile с флагом FILE_FLAG_OVERLAPPED: 

    HANDLE hdir = CreateFile( 
    "C:\\Program Files",                 // путь к каталогу 
    FILE_LIST_DIRECTORY,                // доступ (ReadDirectoryChangesW 
    // требует FILE_LIST_DIRECTORY) 
    FILE_SHARE_READ : FILE_SHARE_DELETE, // режим совместного доступа 
    NULL,                                // дескриптор защиты 
    OPEN_EXISTING,                      // режим создания 
    FILE_FLAG_BACKUP_SEMANTICS ;        // атрибуты 
    FILE_FLAG_OVERLAPPED             // (для каталога нужен BACKUP_SEMANTICS, 
    // а для асинхронной работы - OVERLAPPED) 
    NULL);                               // игнорируется при открытии 
    // файла или каталога 

    Теперь ReadDirectoryChangesW вернет управление сразу после вызова. 
    Чтобы система уведомляла Вас об изменениях в каталоге, выберите один из 
трех способов: 
    • Выделите память для структуры OVERLAPPED и присвойте ее элементу hEvent 
описатель объекта ядра «событие». Затем передайте адрес этой структуры в 
ReadDirectoryChangesW как параметр IpOverlapped. Далее где-то в программе 
вызовите WaitForSingleObject или какую-нибудь другую функцию ожидания. При 

    450 ____________________________Windows для профессионалов 

    изменениях в каталоге система автоматически переведет объект «событие» в 
свободное состояние. 
    • Передайте адрес функции завершения асинхронного (overlapped) 
ввода/вывода в ReadDirectoryChangesW как параметр IpCompletionRoutine. При 
изменениях в каталоге система автоматически отправит АРС-сообщение в очередь 
потока, вызвавшего ReadDirectoryChangesW. Функция завершения будет вызвана, 
когда Ваш поток перейдет (вызовом любой из функций: WaitForSingleObjectEx, 
WaitForMultipleObjectsEx, SleepEx и т. д.) в «тревожное» состояние. 
    • Свяжите описатель каталога с портом завершения ввода/вывода (I/O 
completion port) и, вызвав GetQueuedCompktionStatus, заставьте поток ожидать 
порт завершения. 

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

    Программа FileChng (FILECHNG.EXE) (см. листинг на рис. 14-3) отслеживает 
изменения в дереве каталогов с помощью трех только что рассмотренных 
функций-»осведомителей». Файлы исходного кода и ресурсов для этой программы 
находятся в каталоге FILECHNG на компакт-диске, прилагаемом к книге. При 
запуске на экране появляется диалоговое окно File Change Notifications 
(уведомления об изменениях файлов): 

    Filters 
    Г" Filename     Г Attributes     f Lastwrite time      Г" Creation time 
    Г- Dirname      Г" Size          I" Last access time   Г~ Security 
    Г" Include subdirectories                                           |      
    File list                                            Notification count    0 

    Перед тем как программа начнет отслеживать изменения в файловой системе, 
ей нужно сообщить, за чем именно она должна наблюдать. Для этого настройте 
параметры в разделе Filters (фильтры). Кнопка Start заставляет программу 
опросить элементы управления диалогового окна и вызвать 
FindFirstChangeNotification, чтобы система уведомила ее об изменениях в 
файловой системе. Кроме того, обнуляется счетчик Notifications Counts (общее 
число уведомлений), и начинается первый проход по дереву (с указанного 
каталога); в результате в окне появляется список каталогов и файлов. 
    Далее программа ждет освобождения объекта «уведомление об изменении 
файла». Когда в файловой системе происходит какое-то изменение, отвечающее 
критериям выбранного фильтра, объект-уведомление переходит в свободное 
состояние, и поток FileChng возобновляет исполнение, увеличивая счетчик 
Notifications Counts, и повторно просматривает дерево каталогов. После каждого 
прохода поток вызывает FindNextChangeNotification и снова 

    ____________________________Глава 14. Файловые системы 451 

    ждет, когда освободится объект-уведомление. Мониторинг изменений 
прекращается щелчком кнопки Stop или изменением одного из критериев фильтра. 
    Как ведет себя программа, ясно из ее текста, но я все же хочу привлечь 
Ваше внимание к функции WinMain, структура которой отличается от той, что Вы 
видели в других программах-примерах. Я хотел написать программу FileChng так, 
чтобы у нее был только один поток управления. Для этого мой поток должен 
как-то приостанавливать себя, пока в его очереди не появится сообщение или 
пока объект-уведомление не перейдет в свободное состояние. Основательно 
поломав голову, я вспомнил о функции MsgWaitForMultipleObjects. 
    Это было как раз то, что доктор прописал. Она аналогична 
WaitForMultipleObjects — ^только проверяет еще и оконные сообщения. В то же 
время применение MsgWaitForMultipleObjects означало, что я не смог бы 
использовать в этой программе модальные диалоговые окна, так как для обработки 
сообщений они требуют вызова GetMessage, а это лишает возможности ждать 
освобождения объекта-уведомления. Поскольку программа в большей степени занята 
обработкой сообщений, мне пришлось использовать CreateDialog вместо DialogBox 
и создать немодальное диалоговое окно. После чего самому написать и цикл 
обработки сообщений. 
    И еще несколько слов о том, что делает программа. Сначала она вызывает 
CreateDialog, чтобы создать немодальное диалоговое окно — пользовательский 
интерфейс этой программы. Затем поток начинает выполнение цикла обработки 
сообщений, в котором постоянно проверяет значение переменной fQuit, чтобы 
определить момент, когда программа должна завершиться. В момент запуска 
программы fQuit получает значение FALSE, и оно меняется на TRUE, когда из 
очереди извлекается сообщение WM_QUIT. 
    Попав в цикл обработки сообщений, поток проверяет, действителен ли 
описатель объекта «уведомление об изменении файла», и сохраняет результат 
проверки в переменной f\Vait4FileChanges. Значение описателя недействительно, 
если пользователь еще не «нажал» кнопку Start. Далее программа делает вызов: 

    dwResult = MsgWaitForMultipleObjects( 
    (fWait4FileChanges) ? 1 : 0, &s_hChange, FALSE, INFINITE, QS_ALLEVENTS); 

    Если описатель объекта-уведомления действителен, данный вызов сообщает 
системе, что надо ждать либо перехода этого объекта в свободное состояние, 
либо появления сообщения в одной из очередей потока. Если же описатель 
недействителен, поток ожидает только сообщения. 
    Когда система возобновляет выполнение потока, он проверяет причину этого 
события. Если это произошло из-за изменения файла, увеличивается счетчик 
Notification Count, просматривается дерево каталогов и вызывается 
FindNextChangeNotification. Затем поток возвращается в начало цикла и вновь 
вызывает MsgWaitForMultipleObjects. 
    Если же поток возобновлен из-за появления оконного сообщения, оно 
извлекается из очереди вызовом PeekMessage с флагом PM_REMOVE. Выбранное 
сообщение передается в IsDialogMessage. Этот вызов дает возможность 
пользователю «перемещаться» по элементам управления в немодальном диалоговом 
окне с помощью клавиатуры. Если сообщение не связано с такими действиями 
пользователя, поток делает проверку на WM_QUIT. При положительном результате 
проверки переменная fQuit устанавливается как TRUE, и это вызывает выход из 
цикла перед началом следующей итерации. 
    Для сообщений других типов вызывается TranslateMessage, а за ней — 
DispatchMessage, как и в обычном цикле обработки сообщений. 
    Вы наверняка заметите, что здесь я вызываю в цикле PeekMessage. Зачем? А 
затем, чтобы у обработки сообщений пользовательского интерфейса был более 
высокий приоритет, чем у обработки уведомлений об изменениях в файловой системе. 

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


    FILECHNG.C 
    Модуль: FileChng.C 
    Авторы: Джим Харкинс (Jim Harkins) и Джеффри Рихтер (Jeffrey Richter) 
    Примечание: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    ftinclude "..\CmnHdr.H"           /* см. приложение В */ 
    #include <windows,h> 
    #include <windowsx.h> 
    #include <tchar.h> 
    #include <stdio.h> 
    Sinclude <stdlib.h> 
    ftinclude <string,h> 
    ^include "Resource.H" 
    lllll/H////////////i///ll/llll/////l///l////il//ili///////lll/lllill/llll/llll 
    // следующие функции напрямую заимствованы из программы DirWalk.C 
    static BOOL IsChildDir (WIN32_FIND_OATA *lpFindData) { 
    return(((lpFindData->dwFileAttnbutes & FILE_ATTRIBUTE_OIRECTORY) != 0) && 
    (lstrcmp(lpFindOata->cFileName, __TEXT(".")) != 0) && 
    (lstrcmp(lpFindData->cFileName, __TEXT(", .")) !=0)); 
    1 
    /////////////////////////////////////////////////////////////////////////////// 
    static BOOL FindNextChildDir (HANDLE hFindFile, WIN32_FIND_DATA 
*lpFindData) { 
    BOOL fFound = FALSE; 
    do { 
    fFound = FindNextFile(hFindFile, IpFindData); 
    } while (fFound && !IsChildOir(IpFindData)); 
    return(fFound); 
    } 
    ///lill/llllllll/l/^/l/ll/lllil/l/l/lllil/l/l//l/l//l/^l/lllll//lll//ilil//l/// 
    static HANDLE FindFirstChildDir (LPTSTR szPath, WIN32_FIND_DATA 
*lpFindData) { 
    BOOL fFound; 
    HANDLE hFindFile = FindFirstFile(szPath, IpFindData); 
    if (hFindFile != INVALID_HANDLE_VALUE) { 
    fFound = IsChildDir(lpFindData); 
    if (!fFound) 
    fFound = FindNextChildDir(hFindFile, IpFindData); 
    LM. LAi'i). Llllp. 

    Рис. 14-3. Листинг программы FileChng 

    ____________________________Глава 14. Файловые системы 453 


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

    if QfFound) { 
    FindClose(hFindFile); 
    hFindFile = INVALID_HANDLE_VALUE; 
    } return(hFindFile); 
    f 
    111111111111111111111111111111111111111/lllllllilllllllllllllllllllllllllllllll 
    If чтобы сократить объем используемого стекового пространства, 
    // я создаю один экземпляр структуры DIRWALKDATA как локальную 
    // переменную, а указатель на нее передаю в DirWalkRecurse 
    // данные, используемые DirWalkRecurse 
    typedef struct { 
    HWND  hwndTreeLB;         // описатель окна списка для вывода 
    int   nDepth;             // глубина рекурсии 
    BOOL  fRecurse;           // установить в TRUE для просмотра подкаталогов 
    TCHAR szBuf[1000];        // буфер для форматирования вывода 
    int   nindent;            // число символов для сдвига вправо 
    BOOL  f0k;                // флаг управления циклом 
    BOOL  fIsDir:             // флаг управления циклом 
    WIN32_FIND_DATA FindData; // информация о файле 
    } DIRWALKDATA, «LPDIRWALKDATA; 
    /////////////////////////////////////////////////////////////////////////////// 
    
    // Просматриваем дерево каталогов и заполняем окно списка именами 
    // файлов. Если установлен pDW->fRecurse, то просматриваем и все 
    // подкаталоги - с помощью рекурсивного вызова DirWalkRecurse. 
    void DirWalkRecurse (LPDIRWALKDATA pDW) { 
    HANDLE hFind; 
    pDW->nDepth++; 
    pDW->n!ndent = 3 * pDW->nDepth; 
    _stprintf(pDW->szBuf, __TEXT("%*s"), pDW->nIndent, __TEXT("")): 
    GetCurrentDirectory(chDIMOF(pDW->szBuf) - pDW->n!ndent, 
    &pDW->szBur[pDW->nIndent]); 
    ListBox_Add8tring(pDW->hwndTreeLB, pDW->szBuf); 
    hFind = FindFirstFile(__TEXT("*.*"), &pDW->FindData); 
    pDW->fOk = (hFind '= INVALID_HANDLE_VALUE); 
    while (pDW->TOk) { 
    pDW->f!sDir = pDW->FindData.dwFileAttnbutes & FILE_ATTRIBUTE_DIRECTORY; 

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

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


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

    if (!pDW->f!sDir :: (!pDW->fRecurse && IsChildDir(&pOW->FindOata))) { 
    _stprintf(pDW->szBuf, 
    pDW->f!sDir ? __TEXT("%*s[%s]") : __TEXT("°/o*s%s"). 
    pDW->nIndent, __TEXT(""), 
    pDW->FindData.cFileName); 
    ListBdx_AddStnng(pDW->hwndTreeLB, pDW->szBuf); 
    } 
    pDW->fOk = FindNextFile(hFind, &pDW->FindData): 
    } 
    if (hFind != INVALID_HANDLE_VALUE) 
    FindClose(hFind); 
    if (pDW->fRecurse) { 
    // прлучаем первый дочерний каталог 
    hFind = FindFirstChildDir(__TEXT("*,*"). &pDW->FindData): 
    pDW->fOk = (hFind != INVALIOJANDLEJ/ALUE); 
    while (pDW->fOk) { 
    // входим в дочерний каталог 
    if (SetCurrentDirectory(pDW->FindData.cFileName)) { 
    
    // Выполняем рекурсивный обход дочернего каталога. 
    // Помните, что значения некоторых элементов pDW 
    // будут изменены этим вызовом. 
    DirWalkRecurse(pDW); 
    // возвращаемся в родительский каталог дочернего каталога 
    SetCurrentDirectory(__TEXT("..")); 
    pDW->fOk = FindNextChildDir(hFind, &pDW->FindData); 
    » ; 
    if (hFind != INVALID_HANDLE_VALUE) 
    FindClose(hFind): 
    } 
    pDW->nDepth--; 
    i 
    I / Просматриваем структуру каталогов и заполняем окно списка именами 
    // файлов. Эта функция подготавливает вызов DirWalkRecurse, которая 
    // и осуществляет основную работу. 
    void DirWalk ( 
    HWND hwndTreeLB,     // окно заполняемого списка 
    LPCTSTR pszRootPath, // начальная точка просмотра дерева 
    BOOL fRecurse) {     // входить в подкаталоги, если TRUE 

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

    _____________________________Глава 14. Файловые системы 455 


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

    TCHAR szCurrDir[_MAX_DIR]; 
    DIRWALKDATA DW: 
    // очищаем окно списка 
    ListBox_ResetContent(hwndTreeLB); 
    // сохраняем текущий каталог для последующего восстановления 
    QetCurrentDirectory(chDIMOF(szCurrOir), szCurrDir); 
    
    // устанавливаем текущим тот каталог, 
    // с которого мы хотим начать просмотр 
    SetCurrentDirectory(pszRootPath); 
    
    // nDepth используется для управления сдвигом строк в окне списка 
    // вправо. Значение -1 заставляет выводить каталоги первого уровня 
    // в крайнюю левую позицию. 
    DW.nDepth = -1; 
    DW.hwndTreeLB = hwndTreeLB; 
    DW.fRecurse = fRecurse; 
    
    // вызываем рекурсивную функцию для просмотра дерева каталогов 
    DirWalkRecurse(&DW); 
    
    // восстанавливаем текущим тот каталог, который и был таковым 
    // перед вызовом функции 
    SetCurrentDirectory(szCurrDir); 
    } 
    IIIII/I///IIHIIIIIIIIIII//III/I///I////II//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII/II 
    HANDLE g_hChange = INVALID_HANDLE_VALUE; 
    int   g_nCount = 0; 
    llllllllllll/lllll/l/llllllllllllll/l/IIIIIIHIIII/lll/l/llllllllillllllllll/ll 
    void Dlg_ErrorBox (LPCTSTR pszSource) { 
    TCHAR szBur[100]; // буфер для форматирования вывода 
    _stprintf(szBuf, __TEXT("%s reported error %lu"), pszSource, GetLastErrorO); 
    chMB(szBuf); 
    } 
    //llllll/llllllllllllllllllllll/l/lllll/llll//ll/lllllllltllll/l//t//IIIH/llll 
    DWORD Dlg_GetFilter (HWND hwnd) { 
    DWORD fdwFilter = 0; 
    if (Is"DlgButtonGhecked(hwnd, IDC_FILENAME)) 
    fdwFilter ;= FILE_NOTIFY_CHANGE_FILE_NAME; 

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

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

    РИС. 14-3  (продолжение) 
    
    if (IsDlgButtonChecked(hwnd, IDC_DIRNAME)) 
    fdwFilter ;= FILE_NOTIFY_CHANGE_DIR_NAME; 
    
    if (IsDlgButtonChecked(hwnd, IDC_ATTRIBS)) 
    fdwFilter ;= FILE_NOTIFY_CHANGE_ATTRIBUTES: 
    
    if (IsDlgButtonChecked(hwnd, IDC_SIZEFLTR)) 
    fdwFilter ;= FILE_NOTIFY_CHANGE_SIZE; 
    
    if (IsDlgBLittonCriecked(hwnd, IDC_LASTWRITE)) 
    fdwFilter ;= FILE_NOTIFY_CHANGE_LAST_WRITE: 
    
    if (IsDlgButtonChecked(hwnd, IDC_LASTACCES8)) 
    fdwFilter ;= FILE_NOTIFY_CHANGE_LAST_ACCESS; 
    
    if (IsDlgButtonChecked(hwnd, IDC_CREATION)) 
    fdwFilter ;= FILE_NOTIFY_CHANGE_CREATION; 
    
    if (IsDlgButtonChecked(hwnd, IDC_SECURITY)) 
    fdwFilter := FILE_NOTIFY_CHANGE_SECURITY; 
    return(fdwFilter); 
    } 
    llllll/tlllllillll/lllll/lt/llllllll/IIIHIIII/llllllllll/llllllllllllllll/llll 
    II проверяем элементы управления диалогового окна и формируем 
    // должный вызов FindFirstCnangeNotification: надо установить 
    // как минимум один флаг-фильтр и указать правильный путь 
    BOOL Dlg_Validate (HWND hwnd) { 
    BOOL  fValid = FALSE; 
    TCHAR szPath[_MAX_DIR]; 
    
    // проверяем, установлен ли хоть один флаг 
    if (0 != 01g_GetFilter(hwnd)) { 
    
    // удостоверимся, существует ли данный путь 
    GetDlgItemText(hwnd, IDC_PATH, szPath, chDIMOF(szPath)); 
    fValid = SetCurrentDirectory(szPath); 
    } 
    return(fValld); 
    } 
    Illllllllllllllllllllllllllllllllllllllllllllllllllllllltllllllllllllllllllllll 
    void Dlg_CloseChange (HWND hwnd) < 
    BOOL fDisableFocus = (GetFocusO == GetDlgItem(hwnd, IDC_STOP)); 

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

    _____________________________Глава 14. Файловые системы 457 

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

    EnableWindow(GetDlgItem(hwnd, IDC_STOP), FALSE); 
    if (Dlg_Validate(hwnd)) { 
    EnableWindow(GetDlgItem(hwnd, IOG_START), TRUE); 
    if (fDisableFocus) { 
    SetFocus(GetDlgItem(hwnd, IDC_START)); 
    } 
    } else { 
    fDisableFocus = fDisableFocus ;! 
    (GetFocusO == GetDlgItem(hwnd. IDC_START)); 
    EnableWindow(GetDlgItem(hwnd, IDC_START), FALSE); 
    if (fDisableFocus) { 
    SetFocus(GetDlgItem(hwnd, IDC_INCSUBDIRS)); 
    \ 
    ! 
    t 
    if (INVALID_HANDLE_VALUE != g_hChange) { 
    if (!FindCloseGhangeNotification(g_hChange)) { 
    Dlg_ErrorBox(_J"EXT("FindCloseChangeNotification")); 
    } 
    g_hChange = INVALID_HANDLE_VALUE; 
    I 
    I 
    /////////////////////////////////////////////////////////////////////////////// 
    void Dlg_OpenChange (HWND hwnd) { 
    TCHAR szPath[_MAX_DIR]; 
    BOOL fDisableFocus = (GetFocusO == GetDlgItem(hwnd, IDC_START)); 
    Dlg_CloseChange(hwnd); 
    g_nCount = 0; 
    SetDlgItemInt(hwnd, IDC_NCOUNT, g_nCount, FALSE); 
    GetDlgItemText(hwnd, IDC_PATH, szPath, chDIMOF(szPath)); 
    g_hChange = FindFirstChangeNotification(szPath, 
    IsDlgButtonChecked(hwnd, IDC_INCSUBDIRS), 
    Dlg_GetFilter(hwnd)); 
    if (INVALID_HANDLE_VALUE == g_hChange) < 
    Dlg_ErrorBox(__TEXT("FindFirstChangeNotification")); 
    g_hChange = INVALID_HANDLE_VALUE: 
    } else { 
    EnableWindow(GetDlgItem(hwnd, IDC_START), FALSE); 
    EnableWindow(GetDlgItem(hwnd, IDC_STOP), TRUE); 

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

    458 ____________________________Windows для профессионалов 

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

    if (fDisableFocus) { 
    SetFocus(GetDlgItem(hwnd, IDC_STOP)); 
    } DirWalk(GetDlgItem(hwnd, IDC_TREE), szPath, 
    IsDlgButtonChecked(hwnd, IDC_INCSUBDIRS)); 
    } 
    I 
    1111111111111111111111111111111111111111111111111111111111111111111111111111111 
    
    BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    TCHAR szPath[_MAX_DIR]; 
    
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IOI_FILECHNG, IDI_FILECHNQ); 
    
    // по умолчанию берем текущий каталог 
    GetCurrentDirectory(chDIMOF(szPath), szPath); 
    SetDlgItemText(hwnd, IDC_PATH, szPath); 
    Dlg_CloseChange(hwnd); 
    return(TRUE); 
    1 
    Illllllllllll/lltllllll/lllll/ll/l/ll/lllllllllll/llllllllllllllllllll/llllllll 
    void Dlg_OnCommand (HWND hwnd, int id, HWND hwndCtI, UINT codeNotify) { 
    switch (id) { 
    case IDC_PATH: 
    
    // если прием уведомлений начат и пользователь 
    // изменяет путь, остановить прием уведомлений 
    if (EXCHANGE == codeNotify) { 
    Dlg_CloseChange(hwnd); 
    } 
    break; 
    case IDC.JNGSUBDIRS: 
    case IDC_FILENAME: 
    case IDC_DIRNAME: 
    case IDC_ATTRIBS: 
    case IDC_SIZEFLTR: 
    case IDC_LASTWRITE: 
    case IDC_LASTACCESS: 
    case IDC_CREATION: 
    case IDC_SECURITY: 
    case IDC_STOP: 
    Dlg_CloseChange(hwnd); 
    break; 

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

    _____________________________Глава 14. Файловые системы 459 

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

    case IDC_START: 
    Dlg_OpenChange(hwnd); 
    break: 
    case IDCANCEL: 
    Dlg_CloseChange(hwnd); 
    PostQuitMessage(O); 
    break; 
    } 
    } 
    tllllllllllllllllllllllllllllllllllllllllllllllllllllll/lllll/lllli/lllllllllll 
    BOOL CALLBACK Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM 
IParam) { 
    switch (uMsg) { 
    chHANDLE_DLQMSG(hwnd, WMJNITDIALOG, Dlg_0nlnit0ialog); 
    chHANDLE_DLGMSG(hwnd, WM_COMMAND, DlgJ3nCommand); 
    } 
    return(FALSE): 
    } 
    IIIIIIIIIIIIHIIIIII/1111111111111/1//1111111111111//1111/1111111111111111/1111 
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    HWND  hwnd; 
    MSG   msg: 
    DWORD dwResult; 
    BOOL  fQuit = FALSE, fWalt4FileChanges; 
    TCHAR szPath[_MAX_DIRJ; 
    chWARNIFUNICODEUNDERWIN95(); 
    // создаем немодальное диалоговое окно вместо модального, 
    // т.к."нужен больший контроль за циклом обработки сообщений 
    hwnd = CreateDialog(hinstExe, MAKEINTRESOURCE(IDD_FILECHNG). NULL, Dlg_Proc); 
    
    // выполняем цикл до выборки WM_QUIT 
    while (!fQuit) { 
    
    // есть ли у нас действительный описатель уведомления об изменении файла? 
    fWait4FileChanges = (INVALID_HANDLE_VALUE != g_hChange); 
    
    // если есть, ждать, пока не произойдет изменения файла 
    // ИЛИ пока не придет сообщение 
    dwResult = MsgWaitForMultipleObjects( 
    (fWait4FileChanges) ? 1 : О, 
    &g_hChange, FALSE, INFINITE, QS_ALLEVENTS): 

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

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

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

    if (fWait4FileChanges && (WAIT_OBJECT_0 == dwResult)) { 
    // нас разбудило уведомление об изменении в файловой системе; 
    // обновляем окно списка 
    
    // увеличиваем счетчик полученных уведомлений 
    Set01gltemlnt(hwnd, IDC_NCOUNT, ++g_nCount, FALSE); 
    
    // получаем корневой путь и заполняем список именами 
    // файлов в этом каталоге, а также в подкаталогах, если 
    // установлен флажок Include Subdirectories 
    GetDlgItemText(hwnd, IDC_PATH, szPath, chDIMOF(szPath)); 
    
    DirWalk(Get01gItefn(hwnd, IDC_TREE), szPath, 
    IsDlgButtonChecked(hwnd, IDC_INCSUBDIRS)): 
    
    // сообщаем системе, что мы обработали уведомление 
    FindNextChangeNotification(g_hChange); 
    } else { 
    
    // нас разбудили, т.к. в очереди есть по крайней мере одно 
    // сообщение; "рассортируем" сообщения, находящиеся в очереди 
    while (PeekMessage(&msg, NULL, 0, 0. PM_REMOVE)) { 
    
    // вызываем IsDialogMessage, чтобы можно было использовать 
    // клавиатуру для перемещения по элементам управления 
    if (! IsDialogMessage(hwnd, &msg)) { 
    if (msg.message == WM_QUIT) { 
    // получив сообщение WM_QUIT, 
    // устанавливаем флаг окончания цикла 
    fQuit = TRUE; 
    } else { 
    
    // это не WM_QUIT; транслируем его 
    // и переправляем оконной процедуре 
    TranslateMessage(&msg); 
    DispatchMessage(&msg); 
    } 
    } // if (! IsDialogMessageO) 
    } // пока в очереди есть сообщения 
    } // если пришло уведомление об изменении файла ИЛИ сообщение 
    } // while (!fQuit) 
    // программа завершается; 
    // разрушаем немодальное диалоговое окно 
    DestroyWindow(hwnd); 
    return(O); 
    } 
    I I 1111/11111111/11111111/11111111 / конец файла 1111111111111111111/11111/1111111 

    _____________________________Глава 14. Файловые системы 461 


    Операции с файловыми атрибутами 

    С каждым файлом связан набор атрибутов. Многие из них инициализируются при 
создании файла, некоторые можно изменить программным путем. Но чаще всего 
атрибуты файлов не меняют, а лишь выясняют их текущие значения. Большинство 
атрибутов связано со статусом файла, его размером и временными метками. 

    Типы исполняемых файлов 

    Иногда нужно знать тип исполняемого файла. Выяснить это позволяет функция: 

    BOOL GetBinaryType(LPCTSTR IpApplicationName, LPDWORD IpBinaryType); 

    Все, что от Вас требуется, — задать полный путь к файлу на диске, и 
функция вернет тип исполняемого файла в переменной типа DWORD, на которую 
указывает параметр IpBinaryType. Возможные типы исполняемых файлов: 

    Тип исполняемого файла     Описание 

    SCS_32BIT_BINARY         Win32-пpилoжeниe 
    SCS_DOS_BINARY          Программа MS-DOS 
    SCS_OS216_BINARY         16-разрядное приложение OS/2 
    SCS_PIF_BINARY           PIF-файл для программы MS-DOS 
    SCS_POSIX_BINARY         POSIX-приложение 
    SCS_WOW_BINARY         16-разрядное приложение Windows 


    VWINDOWS/ 
    В Windows 95 функция GetBinaryType no сути не реализована и просто 
возвращает FALSE; последующий вызов GetLastError дает код ERROR_CALL_NOTJMPLEMENTED. 


    Файловые атрибуты 

    Начальные значения присваиваются атрибутам в момент создания файла 
функцией CreateFile. Эти атрибуты задает ее параметр dwAttrsAndFlags. А чтобы 
впоследствии узнать значения этих атрибутов, можно вызвать: 

    DWORD GetFileAttributes(LPTSTR IpszFileName); 

    Она возвращает атрибуты файла, указанного в ее единственном параметре. 
Значение, возвращенное функцией, можно проверить побитовой операцией AND с 
любым из следующих идентификаторов: 

    FILE_ATTRIBUTE_ARCHIVE 
    FILE_ATTRIBUTE_COMPRESSED 
    FILE_ATTRIBUTE_DIRECTORY 
    FILE_ATTRIBUTE_HIDDEN 
    FILE_ATTRIBUTE_NORMAL 
    FILE_ATTRIBUTE_OFFLINE 
    FILE_ATTRIBUTE_READONLY 
    FILE_ATTRIBUTE_SYSTEM 
    FILE_ATTRIBUTE_TEMPORARY 


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


    (Подробнее об идентификаторах файловых атрибутов см. главу 15.) 

    Кроме GetFileAttributes, Windows NT 4 предлагает новую функцию: 

    BOOL GetFileAttributesEx(LPCTSTR IpFileName, 
    GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID IpFilelnformation); 

    Она возвращает намного больше информации, чем GetFileAttributes. К тому же 
с ее помощью можно гораздо быстрее получить атрибуты файла. 
GetFileAttributesEx заменяет собой сразу три функции: GetFileAttributes, 
GetFileTimes (о ней поговорим позже), GetFileSize (и о ней — тоже потом). 
    Понятно, что параметр IpFileName задает полный путь к файлу, атрибуты 
которого нас интересуют. Параметр fInfoLevelId определяет, какая именно 
информации нам нужна. На сегодняшний день у него может быть только одно 
значение — GetFileExInfoStandard. При его использовании параметр 
IpFileInformation должен указывать на структуру WIN32_FILE_ATTRIBUTE_DATA: 

    typedef struct _WIN32_FILE_ATTRIBUTE_DATA { 
    DWORD     dwFileAttributes; 
    FILETIME ftCreationTime; 
    FILETIME ftLastAccessTime; 
    FILETIME ftLastWriteTime; 
    DWORD     nFileSizeHigh; 
    DWORD     nFileSizeLow; 
    } WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA; 

    Иногда атрибуты файла нужно изменить; для этого вызовите: 

    BOOL SetFileAttributes(LPTSTR IpFileName. DWORD dwFileAttributes);         
                         : 
    Эта функция возвращает TRUE в параметре dwFileAttributes, если атрибуты 
файла успешно изменены. Например, чтобы сбросить архивный флаг у файла CALC.EXE: 

    DWORD dwFileAttributes = GetFileAttributes("CALC.EXE"); 
    dwFileAttributes &= ~FILE_ATTRIBUTE_ARCHIVE: 
    SetFileAttributesC'CALC.EXE", dwFileAttributes); 

    SetFileAttributes накладывает определенные ограничения. Например, нельзя 
превратить файл в каталог (или наоборот), задав флаг FILE_ATTRIBUTE_DIRECTORY. 
Нельзя и сжать файл, просто установив флаг FILE_ATTRIBUTE_COMPRESSED. Сжатие 
файла требует вызова DeviceIoControl с использованием операции 
FSCTL_SET_COMPRESSION. (Кроме того, можно применить утилиту СОМРАСТ.ЕХЕ, 
поставляемую с Windows NT.) 

    Размер файла 

    Размер файла получают вызовом GetFileSize: 

    DWORD GetFileSize(HANDLE hFile, LPDWORD IpdwFileSizeHigh); 

    Вы сразу же заметите, что эта функция требует открыть файл и передать его 
описатель через параметр hFile. Описатель можно получить вызовом CreateFile 
(см. главу 15). GetFileSize возвращает младшие 32 бита числа, представляющего 
размер файла. Чтобы получить старшие 32 бита, передайте ей в IpdwFileSizeHigh 
адрес переменной типа DWORD, куда функция и запишет их. Изменить размер файла 
можно только дозаписью в него данных или вызовом SetEndOfFile. 
    В 99% случаев Вас интересует размер исходного файла, и GetFileSize 
сообщает его. Но если Вам нужен размер файла после сжатия, вызовите GetCompressedFileSize: 

    DWORD GetCompressedFileSize(LPCTSTR IpFileName, LPDWORD IpFileSizeHigh); 

    _____________________________Глава 14. Файловые системы 463 

    В отличие от GetFileSize этой функции не нужен описатель файла; Вы просто 
передаете ей полный путь к файлу. Ее параметр IpFileSizeHigh и возвращаемое 
значение имеют тот же смысл, что и в функции GetFileSize. 

    Временные метки файла 

    В MS-DOS (a точнее, в версиях FAT, существовавших до Windows 95 и Windows 
NT 3.5) с файлом была связана лишь одна временная метка — время последней 
записи в файл. В новых версиях FAT, а также в NTFS у файла 3 временные метки: 
дата и время его создания, дата и время последнего к нему обращения, дата и 
время последней модификации. Значения временных меток файла получают вызовом: 

    BOOL GetFileTime(HANDLE hFile, LPFILETIME IpftCreation, LPFILETIME 
IpftLastAccess, LPFILETIME IpftLastWrite): 

    Как и в случае GetFileSize, перед вызовом GetFileTime файл надо открыть, а 
его описатель передать через параметр hFile. Остальные 3 параметра — указатели 
на структуры FILETIME: 

    typedef struct _FILETIME { 
    DWORD dwLowDateTime; 
    DWORD dwHighDateTime; 
    } FILETIME, *PFILETIME, *LPFILETIME; 

    Если время создания файла Вас не интересует, передайте в параметре 
IpftCreation значение NULL. To же относится и к двум другим параметрам, 
связанным с временными метками. 
    Два элемента структуры FILETIME составляют одно 64-битное значение, равное 
числу 100-наносекундных интервалов, прошедших с 1 января 1601 года. Согласен, 
что пользы от этого не очень много, но в конце концов эта дата отмечает начало 
нового четырехсотлетия. Что, и это не впечатляет? По-моему, Microsoft тоже не 
думала, что это произведет на Вас особое впечатление, и поэтому ее 
разработчики создали несколько дополнительных функций, способных превратить 
эти элементы в нечто полезное. 
    Вероятно, Вам нужно узнать, какой файл более старый. Это легко: 

    LONG CompareFileTime( LPFILETIME lpft-1, LPFILETIME lpft2); 

    Функция возвращает одно из следующих значений: 

    Результат вызова CompareFileTime     Описание 
    -1                     Ipfti меньше (более позднее), чем lpft2 
    О                     lpft2 равно lpft2 
    +1                     Ipfti больше (более раннее), чем lpft2 

    С помощью CompareFileTime можно выяснить, записывались ли в файл 
какие-нибудь данные при последнем к нему обращении: 

    IResult = GompareFileTime(&ftLastAccess, &ftLastWrite); 
    if (IResult == 0) { 
    // при последнем обращении велась запись 
    } else { 
    // при последнем обращении записи не было 
    } 

    А может, Вы хотите показать пользователю одну из временных меток файла? 
Тогда придется преобразовывать структуры FILETIME в структуры SYSTEMTIME, и наоборот: 

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

    BOOL FileTimeToSystemTime(LPFILETIME Ipft, LPSYSTEMTIME Ipst); 

    и 

    BOOL SystemTimeToFileTime(LPSYSTEMTIME Ipst, LPFILETIME Ipft); 

    Эти функции преобразуют данные из структур FILETIME и SYSTEMTIME. 

    Структура SYSTEMTIME выглядит так: 

    typedef struct _SYSTEMTIME { 
    WORD wYear; 
    WORD wMonth; 
    WORD wDayOtWeek; 
    WORD wDay; 
    WORD wHour; 
    WORD wMinute; 
    WORD wSecond: 
    WORD wMilliseconds; 
    } SYSTEMTIME; 
    typedef SYSTEMTIME *PSYSTEMTIME, *LPSYSTEMTIME; 

    На основе этой информации легко сформировать нужную строку, показываемую 
пользователю. Кстати, при преобразовании из SYSTEMTIME в FILETIME элемент 
wDayOfWeek структуры SYSTEMTIME игнорируется. 
    Файловое время можно преобразовать в местное время (и обратно) с помощью: 

    BOOL FileTimeToLocalFileTime(LPFILETIME Ipft, LPFILETIME IpftLocal): 

    и 

    BOOL LdcalFileTimeToFileTinie(LPFILETIME IpftLocal, LPFILETIME Ipft); 

    Параметры обеих функций — 2 указателя на структуры FILETIME. Будьте 
внимательны: не передайте им в обоих параметрах один и тот же адрес, иначе они 
будут работать некорректно. 
    А если Вы «жуткий» апологет MS-DOS и FAT и не желаете прямо сейчас менять 
в своей программе старую обработку временных меток на новую, вот Вам две 
функции, преобразующие структуру FILETIME в формат времени, принятый в MS-DOS, 
— и наоборот: 

    BOOL FileTimeToDosDateTime(LPFILETIME Ipft, LPWORD IpwDOSDate, LPWORD IpwDOSTime); 

    и 

    BOOL DosDateTlmeToFileTime(WORD wDOSDate, WORD wDOSTime, LPFILETIME Ipft); 

    FileTimeToDosDateTime принимает адрес структуры FILETIME, содержащей 
временную метку файла, и преобразует ее в 2 значения типа WORD, используемые 
MS-DOS, — одно для даты, другое для времени. 

    /ВНИ)*АНИЕ\ 
    В Windows 95 функции FileTimeToDosDateTime и DosDateTimeToFileTime 
обрабатывают даты вплоть до 31 декабря 2099 года, а в Windows NT — вплоть до 
31 декабря 2107 года.                                                          
   : 
    Чтобы изменить временную метку файла, вызовите функцию, парную GetFileTime: 

    BOOL SetFileTime(HANDLE hFile, LPFILETIME IpftCreation, 
    LPFILETIME IpftLastAccess, LPFILETIME IpftLastWrite): 

    Если Вы не хотите модифицировать какую-либо метку, передайте в 
соответствующем параметре NULL. 
    Другой способ получить информацию об атрибутах файла — вызвать GetFileInformationByHandle". 

    _____________________________Глава 14. Файловые системы 465 


    BOOL GetFileInformationByHandle(HANDLE hFile, 
    LPBY_HANDLE_FILE_INFORMATION IpFilelnformation); 

    Она принимает в качестве параметров описатель открытого файла (hFile) и 
адрес структуры BY_HANDLE_FILE_INFORMATION, которую заполнит информацией о файле: 

    typedef struct _BY_HANDLE_FILE_INFORMATION { 
    DWORD dwFileAttributes; 
    FILETIME ftCreationTime; 
    FILETIME ftLastAccessTime; 
    FILETIME ftLastWriteTime; 
    DWORD dwVolumeSerialNumber; 
    DWORD nFileSizeHigh: 
    DWORD nFileSizeLow; 
    DWORD nNumberOfLinks; 
    DWORD nFileIndexHigh; 
    DWORD nFileIndexLow; 
    } BY_HANDLE_FILE_INFORMATION, 
    *PBY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION; 

    Функция собирает все — какие есть — сведения об атрибутах файла. Кроме 
атрибутов, помещаемых в элемент dwFileAttributes, и трех меток времени в 
элементах ftCreationTime, ftLastAccessTime и ftLastWriteTime, она возвращает в 
dwVolumeSerialNumber серийный номер тома, на котором находится файл, и размер 
файла (в элементах nFileSizeHigh и nFileSizeLow). Она также определяет число 
связей (используемых подсистемой POSIX в Windows NT) и заносит его в элемент nNumberOfLinks. 
    При каждом открытии файла система присваивает ему уникальный 
идентификатор, значение которого заносится в элементы nFileIndexHigh и 
nFileIndexLow. Если файл открыт сразу двумя приложениями, его идентификатор 
одинаков для обеих программ. Вместе с серийным номером тома этот идентификатор 
позволяет определить, не указывают ли два (или более) описателя на один и тот 
же файл. 

    Создание временных файлов 

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

    DWORD GetTempPath(DWORD nBufferLength, LPTSTR IpBuffer); 
    UINT GetTempFileName(LPCTSTR IpPathName, LPCTSTR IpPreflxString, 
    UINT uUnique, LPTSTR IpTempFileName); 

    GetTempPath записывает в буфер, на который указывает параметр IpBuffer, 
полный путь к каталогу, где операционная система хранит временные файлы. Чтобы 
узнать этот каталог, применяется такой алгоритм: 
    1. Если определена переменная окружения ТМР, то путь к каталогу задает 
именно она. 
    2. Если ТМР не определена, проверяется переменная TEMP. 
    3. Если не определены ни ТМР, ни TEMP, используется текущий каталог. 
    Функция GetTempFileName генерирует имя временного файла, гарантируя его 
уникальность. Параметр IpPathName задает каталог, где будет создан этот 
временный файл. Как правило, Вы будете сначала вызывать GetTempPath, а затем 
передавать полученный путь через IpPathName. Параметр IpPrefixString указывает 
на трехсимвольный префикс, с которого будут начинаться имена временных файлов. 

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

    Параметру uUnique функции GetTempFileName почти всегда присваивается 0. 
При этом для генерации уникального суффикса в имени файла используется 
системное время. Если временный файл с полученным именем уже есть, система 
будет генерировать другие имена, пока не подберет уникальное. После этого 
GetTempFileName создаст файл и вернет его имя в буфере, на который указывает 
ее параметр IpTempFileName. 
    Если параметр uUnique не равен 0, GetTempFileName сформирует имя файла из 
трехсимвольного префикса и номера, заданного в uUnique (в шестнадцатеричной 
форме); затем вернет это имя в буфере, на который указывает IpTempFileName. Но 
теперь, при ненулевом значении uUnique функция не станет ни проверять 
уникальность имени, ни создавать файл. Поэтому придется открывать его 
самостоятельно — вызовом CreateFile (см. главу 15). 