Для работы с файлами в Windows содержится свой набор функций, позволяющих выполнять основные операции над файлами. В первых 16ти разрядных версиях Windows (по 3.x включительно) эти функции основывались на применении функций MS–DOS. Непосредственно в Windows описан только минимальный набор базовых функций, для выполнения других операций всегда можно воспользоваться прерыванием 0x21. В отличие от них, 32х разрядные версии Windows содержат уже полный набор функций для работы с файлами, при этом использование функций MS–DOS не допускается. Функции Win32 API для работы с файлами — это расширенный набор функций по сравнению с Windows API.
Работа с файлами, каталогами и томами в Windows API
В Windows 3.x применяется файловая система MS–DOS. В первых версиях Windows вообще вся работа с дисками просто переадресовывалась к функциям MS–DOS, в поздних версиях, например Windows for Workgroup 3.11, в частных случаях может использовать собственные средства, реализующие доступ к диску и к файлам в 32х разрядном защищенном режиме. Это несколько увеличивает производительность системы, работающей в защищенном режиме, так как при обращениям к данным на диске исключаются переключения процессора из защищенного режима в реальный и обратно. При этом система проверяет, возможно ли с помощью имеющихся драйверов обращаться к данному диску, или нет. Если это невозможно (например, используются нестандартные DOS–драйверы), то для доступа к диску и файлам применяются средства MS–DOS, как в предыдущих версиях Windows.
Файлы (file) группируются в каталоги (directory или folder, папки). В одном каталоге может находиться большое количество файлов или других вложенных каталогов. Каталоги образуют строго древовидную структуру — данный каталог может содержать сколько угодно вложенных, но сам он может быть вложен только в один родительский каталог (каталог верхнего уровня). На каждом томе (volume) существует один каталог самого верхнего уровня — корневой каталог (root directory). Для обозначения конкретного тома используются буквы английского алфавита, начиная с “a”.
Имена файлов (file name) и каталогов (directory name, folder name) в Windows API, как и в MS–DOS, состоят из имени, длиной до 8 символов и расширения, длиной до 3 символов, отделяемого от имени одной точкой. Таким образом максимальная длина имени составляет 12 символов, включая разделяющую точку. Для точного задания файла необходимо указать не только его имя, но также и каталог, в котором он находится. Причем, так как каталоги могут быть вложенные, то необходимо задавать список каталогов, которые надо пройти на пути от текущего каталога, либо от корневого каталога тома до каталога, содержащего нужный файл.
Имя тома, путь до файла и имя файла составляют так называемое полное имя файла (path name). Полное имя начинается с названия тома, за которым следует двоеточие, обратная косая черта и список каталогов на пути к файлу, разделяемых обратными косыми чертами, затем следует имя файла и его расширение, отделяемое точкой. Например: E:\Mka\Book\Samples\1a.cpp
Указывает на файл 1a.cpp, находящийся к каталоге Samples, вложенном в подкаталог Book и, вместе с ним, в подкаталог Mka, хранящийся на томе E.
Для каждого тома существует понятие текущего каталога (current directory). Кроме того существует понятие текущего тома (диска) (current drive). Для задания файла, находящегося в текущем каталоге текущего тома, достаточно просто указать имя этого файла. Вы можете легко сделать текущий диск другим и перейти в нем в какой–либо иной каталог. В этом случае система запомнит тот каталог, в котором вы были перед переходом на другой диск, и будет считать его текущим каталогом для прежнего диска.
Так, например:
1a.cpp — задает файл 1a.cpp в текущем каталоге текущего диска
E:1a.cpp — задает файл 1a.cpp в текущем каталоге тома E
E:\1a.cpp — задает файл 1a.cpp в корневом каталоге тома E
\1a.cpp — задает файл 1a.cpp в корневом каталоге текущего диска
Система допускает использование специальных имен “.” (точка) и “..” (две точки) для задания текущего каталога и для задания каталога верхнего уровня. Например, пусть текущий каталог E:\Mka\Book\Samples, тогда:
.\1a.doc — задает файл E:\Mka\Book\Samples\1a.cpp
..\Chapter2.doc — задает файл E:\Mka\Book\Chapter2.doc
В системе запрещается использовать имена файлов и каталогов, совпадающие с именами стандартных устройств, например CON, COM2, LPT1, PRN и так далее.
Система ограничивает максимальную длину полного имени файла 144 символами. Так как заранее известно, что полное имя не превысит этой длины, то часто для работы с именами файлов используются статически выделяемые области, размер которых не изменяется.
Собственно в Windows 3.x содержится небольшой набор функций, повторяющих основные операции над файлами — создание, открытие, позиционирование файлового указателя, чтение, запись и закрытие; помимо этого предусмотрено несколько дополнительных функций. Функций для разделения доступа, переименования или копирования файлов, создания и смены каталогов и прочего не предусматривается. При необходимости можно воспользоваться прерыванием 0x21, либо функциями стандартной C–библиотеки времени выполнения.
HFILE _lcreat(lpszFilename, fnAttribute);
HFILE _lopen(lpszFilename, fnOpenMode);
LONG _llseek(hf, lOffset, nOrigin);
UINT _lread(hf, hpvBuffer, cbBuffer);
UINT _lwrite(hf, hpvBuffer, cbBuffer);
long _hread(hf, hpvBuffer, cbBuffer);
long _hwrite(hf, hpvBuffer, cbBuffer);
HFILE _lclose(hf);
Назначение этих функций понятно из названия. Среди них интересны только две процедуры — _hread и _hwrite — которые предназначены для чтения и записи больших фрагментов файлов за одну операцию. Под большими в данном случае понимаются фрагменты, превышающие 65535 байт (64K), то есть превышающие размер одного 16ти разрядного сегмента. Для 16ти разрядной платформы Windows 3.x это очень удобно.
Неудобным в функциях Windows API для работы с файлами является то, что для флагов этих функций предусмотрены только числовые значения, а не символические. Так, например, для функции _lcreat параметр fnAttribute равный 0, соответствует созданию обычного файла, а 2 — скрытого. Или для функции _llseek параметр nOrigin равный 1 указывает на задание нового положения относительно текущего, а не относительно начала файла. Все эти константы так и надо указывать в виде числа, предварительно проверив их значение по руководству.
Используемые в Windows хендлы файлов являются хендлами файлов MS–DOS. Это позволяет легко применять обычные средства для работы с файлами. С другой стороны это приводит к типичной ошибке: в Windows принято, что значение 0 для хендла является недопустимым; то есть обычная проверка при получение хендла какого–либо объекта выглядит так:
HANDLE hObject;
hObject = Get...(...); // получаем хендл объекта
if (hObject) {// проверяем его...
// все в порядке
} else {
// ошибка!}
Однако при работе с файлами такой способ ошибочен — хендл файла, равный 0, в MS–DOS соответствует стандартному устройству для вывода (stdout), а не недопустимому. Для обозначения ошибки применяется значение хендла файла не 0, а -1. Для удобства в windows.h определен специальный символ HFILE_ERROR, равный -1. Предыдущий фрагмент при работе с файлами должен выглядеть так:
HFILE hFile;
hFile = _lcreat(“c:\\myfile.dat”, 0);
if (hFile != HFILE_ERROR) {
// все в порядке
...
_lclose(hFile);
} else {
// ошибка!}
Помимо уже рассмотренных в Windows предусмотрена специальная функция, предназначенная для открытия, поиска, удаления файлов и выполнения некоторых других операций:
HFILE OpenFile(lpszFileName, lpOpenBuff, fuMode);
Параметр lpOpenBuf является указателем на структуру OFSTRUCT. В этой структуре сохраняется информация об открытом файле, что позволяет использовать ее для повторного открытия (или удаления) того–же файла позже. Параметр fuMode обычно указывает режим доступа к открытому файлу (флаги OF_READ, OF_WRITE, OF_READWRITE), выполняемую операцию — открыть или создать файл (OF_CREATE), ограничение доступа к файлу других приложений (OF_SHARE_...).
Помимо открытия файла данная функция способна выполнять некоторые специфические операции (задаваемые тем–же параметром fuMode):
удаление файла OF_DELETE,
поиск файла OF_SEARCH,
проверка существования файла OF_EXIST,
повторное открытие файла по информации, сохраненной в структуре OFSTRUCT OF_REOPEN,
инициализацию структуры OFSTRUCT OF_PARSE,
сравнение даты и времени создания файла с данными в структуре OFSTRUCT OF_VERIFY,
а также сообщать о невозможности открыть файл (при этом функция OF_PROMPT и не позволяет ни выбрать другой файл, ни указать новое имя файла) OF_CANCEL
Еще несколько функций Windows носят вспомогательный характер. Так, например, функции GetWindowsDirectory и GetSystemDirectory возвращают информацию о каталоге, содержащем win.com — каталоге Windows и системном каталоге (обычно windows\system).
UINT GetWindowsDirectory(lpszSysPath, cbSysPath)
UINT GetSystemDirectory(lpszSysPath, cbSysPath);
Интересно, что Windows не предоставляет функции для получения текущего каталога — для этого надо использовать либо функции стандартной библиотеки, либо можно узнать полный путь к выполняемому файлу, а из него выделить путь (в некоторых случаях даже важнее знать именно каталог, в котором находится исполняемый модуль, чем текущий):
Int GetModuleFileName(hInstance, lpszFileName, cbFileName);
Еще пара функций помогает создавать временные файлы. Функция GetTempDrive возвращает букву, обозначающую диск, используемый для хранения временных файлов (при этом параметр функции chDriveLetter не используется). При этом предполагается, что для хранения временных файлов используется первый жесткий диск (обычно C), а если он не существует, то текущий диск. Вторая функция — GetTempFileName помогает построить уникальное имя временного файла, при этом она использует либо переменную окружения TEMP, либо, если она не определена, то корневой каталог указанного тома, либо, если он не задан, корневой каталог первого жесткого диска (обычно C).
BYTE GetTempDrive(chDriveLetter);
Int GetTempFileName(chDriveLetter, lpszPrefixString, uUnique, lpszTempFileName);
Однако эти функции нужны только для формирования уникального имени для временного файла. Собственно временным этот файл не будет — для его удаления после закрытия надо использовать соответствующую функцию, система за вас этого не сделает.
Функция SetHandleCount позволяет изменить максимально допустимое число одновременно открываемых файлов одним приложением. По умолчанию одно приложение может открыть до 20 файлов сразу.
UINT SetHandleCount(cHandles);
Последняя функция возвращает информацию о типе дискового устройства. К сожалению эта информация крайне скудна — Windows распознает только три вида дисковых устройств: фиксированные (DRIVE_FIXED), сменные (DRIVE_REMOVABLE) и удаленные (DRIVE_REMOTE):
UINT GetDriveType(nDriveNumber);
Параметр nDriveNumber задает номер диска: устройство “A” имеет номер 0, “C” — 2 и так далее. Возвращаемое функцией значение тип диска (DRIVE_FIXED, DRIVE_REMOVABLE, DRIVE_REMOTE), либо, в случае ошибки, 0.
Работа с файлами, каталогами и томами в Win32 API
Средства для работы с файлами в Win32 API существенно отличаются от средств в Windows API. Одна из самых существенных, с точки зрения разработчика, особенностей — использование длинных имен файлов. Так для имени файла и расширения система отводит 255 символов. Это значит, что использование каких–либо буферов фиксированного размера для хранения полных имен файлов недопустимо! Так как полное имя содержит путь, часто насчитывающий добрый десяток вложенных каталогов, а их имена могут быть длиной до 255 символов каждый, да еще само имя файла, то определить достаточный размер такого буфера просто не представляется возможным.
Конечно, теоретически можно резервировать по 5–10 килобайт под каждое имя, надеясь, что использовать столь длинные пути со столь длинными именами никто не будет. Но это дело чисто вероятностное — пока имена файлов и каталогов приходилось хотя–бы иногда набирать руками, очень длинных путей никто и не делал, но в современных системах интерфейс разработан таким образом, что набирать имя файла или каталога приходится обычно лишь однажды — при его создании и при этом набирается только имя, а не полный путь. Это до определенной степени провоцирует на применение очень длинных имен.
Для корректной работы с длинными именами файлов надо сначала определить размер буфера, узнав длину имени, затем динамически выделить пространство, и только потом получить нужное имя. При этом надо учесть, что приложения Win32 могут быть скомпилированы для использования UNICODE, тогда каждый символ будет занимать не один байт, а два, что автоматически удваивает требуемый размер буфера.
Иногда возможно использование так называемых коротких имен вместо длинных. Дело в том, что в 32х разрядной системе возможен запуск 16ти разрядных приложений Windows и задач MS–DOS, которые, естественно, не могут работать с длинными именами. Для того, что бы старые приложения могли иметь доступ ко всем файлам, система автоматически генерирует так называемые короткие имена, подчиняющиеся соглашениям MS–DOS. Все 16ти разрядные приложения Windows и все задачи MS–DOS имеют дело с этими короткими именами. В Win32 API предусмотрены специальные функции (GetShortPathName и GetFullPathName) для получения короткого имени из длинного и наоборот. Однако надо иметь в виду, что этот механизм все–таки не гарантирует корректную работу старых приложений — полная длина пути (даже составленного из коротких имен) может превысить 144 символа, так как число вложенных подкаталогов может оказаться значительным. При этом 32х разрядная система (Windows–95 или Windows NT) будет работать с такими глубоко запрятанными файлами совершенно спокойно, но при попытке получить доступ к этим файлам средствами MS–DOS или использовать их старыми 16ти разрядными приложениями Windows возможно возникновение ошибок. Возможно, но не обязательно — смотря по тому, какие пути используются: абсолютные (длина может превышать 144 символа), или относительные (как правило их длина существенно меньше).
DWORD GetFullPathName(lpszFile, cchPath, lpszLongPath, plpszFileNamePart);
DWORD GetShortPathName(lpszLongPath, lpszShortPath, cchBuffer);
Функция GetFullPathName возвращает полное, включая путь, длинное имя файла, заданного параметром lpszFile. Если указанное имя не содержит пути, то предполагается, что файл находится в текущем каталоге. Функция GetShortPathName выполняет обратную задачу — она сообщает короткое имя файла, заданного его длинным именем.
Другая проблема — использование пробелов в именах файлов. Часто при анализе командных строк предполагается, что пробел отделяет один компонент командной строки от другого. Теперь пробел может находиться и внутри имени файла, содержащегося в этой командной строке. В таких случаях принято заключать имя файла в двойные кавычки (этот символ запрещено использовать в самих именах). А раз так, то во все средства разбора командной строки надо включать специальный анализ на выделение имен файлов, заключенных в кавычки (либо не заключенных, если в имени нет пробелов). Больше всего ошибок возникает при переносе старых приложений в Win32. Многие солидные программисты просто не замечали, что какая–то хорошо отлаженная библиотечная функция, служившая много лет им верой и правдой, вдруг даст ошибку, наткнувшись на пробел в имени файла. А поймать все такие ошибки в процессе отладки очень трудно. Такие ошибки проскакивали незамеченными во время отладки в хорошие, коммерческие продукты. Так, например, очень хороший и надежный компилятор Watcom C/C++ 10.5 содержит неожиданно много таких ошибок, причем как в библиотеках времени выполнения, так и в самой среде.
Еще один нюанс, правда сравнительно небольшой, связан с тем, что символ “.” (точка) может встречаться не только для отделения имени от расширения, но также и в самом имени, причем неоднократно. В таких случаях под расширением подразумевается часть имени, отделенная самой правой точкой. Эта особенность сравнительно редко приводит к ошибкам, так как при разборе имени его обычно анализируют справа налево1.
Помимо этого следует учесть, что в именах файлов могут встречаться две косые черты подряд. Так, например, открытие файла с именем “\\.\A:” соответствует получению доступа к тому “A” (заметьте, что в программе на C это имя будет записано как “\\\\.\\A:”).
Все рассмотренные функции Windows API реализованы и в Win32 API, однако помимо них добавлено множество других, очень полезных функций. Правда реализация прежних функций несколько изменилась; так функция SetHandleCount при работе в Windows NT просто потеряла смысл — для описания файлов используется динамически выделяемое пространство, функции _lread и _lwrite полностью совпадают с функциями _hread и _hwrite соответственно. Многие из старых функций получили аналоги в Win32 API, обладающие несколько большими функциональными возможностями, например функции _lread и _lwrite имеют более мощные аналоги ReadFile, ReadFileEx, WriteFile и WriteFileEx.
Работа с томами
Win32 API предоставляет полный набор средств для работы с файлами и томами, в отличие от прежних версий Windows, которые часто использовали функции MS–DOS. Большинство функций Win32 API для идентификации тома используют не одну букву, и не номер тома, как это было в MS–DOS, а путь к корневому каталогу тома. Обычно это строка вида X:\, однако, в сравнительно редких случаях, возможно задание конкретного каталога.
Например, при работе с Windows 3.x и Win32s возможна работа 32х разрядного приложения в 16ти разрядной операционной системе, когда пользователь может обеспечить доступ к тому как к отдельному каталогу другого тома (используя команду join). В этом случае характеристики тома в целом и отдельного каталога этого тома могут существенно различаться — скажем, том является разделом жесткого диска, а один из его каталогов соответствует CD–ROM или RAM диску.
Итак, коротко рассмотрим основные функции Win32 API для работы с томами.
DWORD GetLogicalDrives(void);
DWORD GetLogicalDriveStrings(nBufferSize, lpszBuffer);
Возвращают информацию о присутствующих в системе томах. Функция GetLogicalDrives возвращает двойное слово, установленные в 1 биты которого соответствуют имеющимся томам. Более интересная функция GetLogicalDriveStrings возвращает список из имен корневых каталогов томов. Имена разделяются между собой символом ‘\0’, а весь список завершается двумя символами ‘\0’.
В документации можно встретить утверждения, что эта функция реализована только в Windows NT, однако это не так — в Windows–95 (по крайней мере в локализованной русской версии 4.0.950a) она тоже определена. Точно ее нет только в Win32s. Сравнительно легко можно написать универсальную функцию, которая будет использовать GetLogicalDriveStrings, либо, при ее отсутствии, эмулировать ее с помощью функции GetLogicalDrives (нечто подобное сделано в приводимом ниже примере 2B).
Другой интересный нюанс этой функции связан с тем, что для получения от нее результата, необходимо использовать буфер неизвестной заранее длины (как и для многих других функций Win32 API, работающих с файлами). Можно, конечно, зарезервировать буфер с большим запасом и надеяться, что он почти никогда не будет заполнен полностью. Лучше, однако, сначала узнать требуемое пространство, выделить его, и только затем получить данные:
DWORD dwSize = GetLogicalDriveStrings(0, NULL); // узнать длину
LPTSTR lpszStrings = new TCHAR [ dwSize+1 ]; // +1 = для символа ‘\0’
GetLogicalDriveStrings(dwSize, lpszStrings);
for (LPTSTR p = lpszStrings; *p; p += lstrlen(p) + 1) {
// ‘p’ указывает на название конкретного тома}
delete lpszStrings;
Следующая рассматриваемая функция — GetDriveType — возвращает информацию о типе тома. В прежнем Windows API существовал аналог этой функции, который получал вместо имени корневого каталога тома номер логического диска и распознавал несколько меньшее количество типов томов — CD–ROMы и RAM диски отдельно не опознавались.
UINT GetDriveType(lpszRoot);
Возвращаемое число указывает тип тома, либо информирует о том, что том задан неверно или его тип не может быть определен. В приводимом ниже примере применяется эта и большинство других, рассматриваемых в данном разделе функций. При желании можете обратиться к примеру или технической документации, что бы получить дополнительную информацию.
Функция GetVolumeInformation возвращает более подробную информацию о томе. С ее помощью можно узнать метку тома (в Windows API для этих целей часто искали файл типа “метка тома” в корневом каталоге этого тома), серийный номер, задаваемый при форматировании тома, тип файловой системы (NTFS, HPFS, CDFS, FAT), а также максимальную длину имени файла, поддерживаемую томом и некоторые другие сведения.
BOOL GetVolumeInformation(
lpszRoot,
lpszVolume, nVolumeSize,
lpdwSerialNumber, lpdwMaxNameLength, lpdwFlags,
lpszFileSystemName, nFileSystemName);
На практике приходилось видеть, когда функция ошибалась с определением файловой системы для удаленных дисков. Такую ошибку трудно четко повторить, так как возможно большое количество комбинаций из систем, установленных на компьютер с запущенным приложением (Windows 3.x + Win32s, Windows–95, Windows–98, Windows NT) и на компьютер, предоставляющий свои диски в общее пользование (список еще больше — включая системы типа OS/2, Macintosh, Unix и прочее).
Достаточно часто может возникнуть необходимость в проверке свободного пространства и полного размера какого–либо тома. Сделать это можно с помощью функции GetDiskFreeSpace:
BOOL GetDiskFreeSpace(
lpszRoot,
lpdwSectorsPerCluster, lpdwBytesPerSector,
lpdwFreeClusters, lpdwTotalClusters);
Эта функция возвращает информацию о размере кластера данных, размере тома в кластерах и о количестве свободных кластеров. Кластер — минимальный объем пространства используемый при выделении места для хранения данных. Кластера обычно жестко не связаны с физической организацией тома, они представляют собой некоторое логическое объединение одной или нескольких физически выделяемых единиц информации на томе. Так, все пространство тома обычно разбивается на физические сектора (обычно по 512 байт для жестких и гибких дисков и 2048–2192 байт для CD–ROM)
С помощью функции SetVolumeLabel вы можете изменить название тома:
BOOL SetVolumeLabel (lpszRoot, lpszVolume); // не реализована в Win32s!
Существует специальная функция, осуществляющая взаимодействие непосредственно с драйверами устройств. В некоторой степени применение этой функции может рассматриваться аналогично функциям MS–DOS 0x44?? (device I/O control, IOCTL), однако возможности данной функции гораздо шире. В числе интересных — возможность узнать, заменялся–ли том в устройстве со сменными носителями (например, гибкий диск), узнать производительность устройства, отформатировать том (дорожки диска), получить информацию о разбиении диска на разделы и даже разбить диск на разделы по–новому, а также многое другое. Функция требует, что бы ей был передан хендл, описывающий данное устройство. Для того, что бы получить этот хендл, можно воспользоваться функцией CreateFile (см. ниже), передав ей условное имя файла, в виде \\.\A: — для доступа, например, к тому A (буква, естественно, обозначает тот диск, к которому нужен доступ), или \\.\PhysicalDrive0 — для доступа к физическому жесткому диску 0 (цифра — номер жесткого диска, как он подключен к контроллеру, обычно 0 или 1). Обратите внимание, что в тексте программы символы “обратная косая черта” должны быть повторены дважды, например “\\\\.\\A:”.
Внимание! для доступа к дискам в Windows NT необходимо иметь права доступа администратора системы!
BOOL DeviceIoControl(// не реализована в Win32s!
hDevice, dwIoControlCode,
lpvInBuffer, cbInBuffer,
lpvOutBuffer, cbOutBuffer,
lpcbBytesReturned,
lpoOverlapped);
В этом разделе дополнительно будут обзорно рассмотрены еще две функции, предназначенные для работы с устройствами (это не обязательно тома). Эти функции реализованы только в Windows NT, так что применяются крайне редко — обычно стараются разрабатывать приложения, переносимые между разными реализациями Win32.
BOOL DefineDosDevice(dwFlags, lpszDeviceName, lpTargetPath);
DWORD QueryDosDevice(lpszDeviceName, lpBuffer, cbMaxSize);
Обе функции жестко привязаны к тому, как в Windows NT осуществляется доступ к ее устройствам: в системе существуют свой собственный способ определения всех устройств. Например, имя \Device\Parallel0 определяет первый параллельный порт. Однако, обычно для того–же самого используются имена типа LPT1, вошедшие в обиход со времен, более древних, чем первые IBM PC XT. Для удобства в Windows NT определена специальная таблица, устанавливающая соответствия между именами устройств “в стиле MS–DOS” и именами в системе. Эта таблица глобальная, все работающие приложения (не только приложения MS–DOS) осуществляют доступ к устройствам посредством этой таблицы.
Функция DefineDosDevice позволяет задать самому такое соответствие, а функция QueryDosDevice узнать либо соответствие конкретного имени устройства, либо получить список всех определенных имен. Подробнее смотри в документации, либо в приводимом ниже примере.
Имеется одна несколько странная особенность этих функций — обычно функции Win32 API, не реализованные на той или иной платформе возвращают код ошибки ERROR_CALL_NOT_IMPLEMENTED, который можно получить посредством функции GetLastError. Выглядит такая проверка примерно так:
DefineDosDevice(0, "Z:", buffer);
if (GetLastError() != ERROR_SUCCESS) {
// возникла ошибка - может быть функция не поддерживается
} else {
// все в порядке, работаем как обычно}
Несколько неожиданно, что в Windows 98 эти функции не устанавливают код ошибки, функция GetLastError возвращает код ERROR_SUCCESS. Однако о корректной работе функций говорить к сожалению не приходиться... Подробнее — в приводимом ниже примере.
Пример 2B — работа с томами
Данный пример иллюстрирует применение некоторых функций для работы с томами и файлами. Это приложение построено на основе примера 1B, использующего распаковщики сообщений. В окне, созданным данным приложением, просто будут перечисляться имена томов и некоторая информация о них, а также о назначении устройств DOS.
Интересно рассмотреть, каким образом осуществляется вывод информации о томах в окно приложения. Это можно было–бы реализовать разными способами:
Хуже некуда. Изменить в примере 1B.CPP обработку сообщения WM_PAINT (функция Cls_OnPaint), так, что бы при обработке сообщения опрашивать все устройства и выводить необходимую информацию. Очень плохо в этом то, что такой опрос может занимать значительное время, и приводить к обращениям к устройствам при каждой перерисовке окна. Еще хуже то, что при попытке чтения информации с устройства возможно возникновение ошибок, о которых система будет сообщать в отдельном всплывающем окошке, например “Cannot read from drive A:”. Это окошко окажется, с большой вероятностью, поверх окна приложения, следовательно, при закрытии окна сообщения, наше приложение снова получит сообщение WM_PAINT, снова попробует обратиться к тому–же устройству ... и так далее (что бы этого избежать можно воспользоваться функцией SetErrorMode, отключив вывод сообщений об ошибках).
Чуть лучше. Считывать информацию об устройствах при создании окна, формировать где–то в памяти весь текст, затем отображать его при обработке WM_PAINT. Этот способ качественно лучше тем, что получение данных и их отображение осуществляется в разных местах программы и в разное время. Однако и у него есть минусы — список устройств может изменяться во время работы приложения, а оно этого не отразит, и, кроме того, список может оказаться достаточно большим — больше размеров окна. Первую проблему мы решать сейчас даже не будем — этого легко добиться введя меню с командой Обновить (Refresh) или выполняя такое обновление через определенный интервал времени. Вторая проблема приведет к добавлению собственного интерфейса — обработке сообщений клавиатуры и мыши, что потребует написания значительного кода и продолжительной отладки.
Еще лучше. Чуть разовьем второй способ — получать информацию будем при создании окна, а отображение и работу с мышью и клавиатурой переложим на Windows. Windows предоставляет разработчикам несколько стандартных классов окон, реализующих самые распространенные элементы управления — кнопки, флажки, списки, простейшее окно–редактор и другие. Вот окном–редактором мы и воспользуемся, причем специально укажем, что текст является неизменяемым, то есть редактор будет работать как окно просмотра. В этом случае при создании главного окна приложения, мы должны создать дочернее окно–редактор, занимающее всю внутреннюю область главного окна2. Затем, сформировав весь необходимый текст, передать его редактору. Окно редактирования будет снабжено полосами прокрутки, поддерживать работу с клавиатурой и мышью, осуществлять передачу текста в буфер обмена — и все само, без разработки дополнительного кода. Мы должны доделать совсем немного — обрабатывать сообщения, связанные с изменением размера главного окна (соответственно менять размер дочернего окна), при уничтожении главного окна не забыть уничтожить окно–редактор, а сообщение WM_PAINT мы можем вообще не обрабатывать.
Приложение рассчитано на работу в Win32, однако это связано только лишь с применением функций Win32 API для получения информации о томах. При создании приложения для Windows 3.x вместо функций, не декларированных в Windows API, используются функции–эмуляторы, включаемые в это приложение при компиляции 16ти разрядного приложения. Это обеспечивает возможность нормальной компиляции и работы приложения на обеих платформах, но с несколько ограниченными возможностями в случае применения Windows API (строго говоря, функции–эмуляторы можно было бы сделать и более мощными, опираясь на функции и структуры данных MS–DOS — просто это выходит за рамки данной книги).
Функции DefineDosDevice и QueryDosDevice, применяемые в числе прочих в этом приложении, работают только в виде 32х разрядного приложения и только под Windows NT, так как они имеют смысл исключительно для реализации Win32 в Windows NT.
Файл 2B.CPP
#define STRICT
#include <windows.h>
#include <windowsx.h>
#define UNUSED_ARG(arg) (arg)=(arg)
#ifndef __NT__
// определим необходимые функции при компиляции для Windows API
#include "2b16.cpp"
// текст файла 2b16.cpp приведен ниже
#endif
static char szWndClass[]= "test volume functions";
static HINSTANCE hInstance;
BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{UNUSED_ARG(lpCreateStruct);
char *temp, *p, *s;
DWORD n, i, sernum, complen, flags, spc, bps, fc, tc;
char file_system[ 128 ], buffer[ 1024 ];
UINT count, errmode;
HWND hwndView;
RECT rc;
HFILE hf;
static OFSTRUCT ofs;
static char tempfile[] = “c:\\test.txt”;
static char devZ[] = “Z:”;
// создадим окно-редактор, занимающее всю внутреннюю область окна
GetClientRect(hwnd, &rc);
// теоретически в CREATESTRUCT указаны размеры окна, но к сожалению они
// могут быть указаны как нулевые, хотя это не так. Функция GetClientRect
// в этом случае все равно возвращает корректные данные
hwndView = CreateWindow(
"EDIT", "",
WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|ES_MULTILINE|ES_READONLY,
0, 0, rc.right, rc.bottom, hwnd, (HMENU)1, hInstance, NULL);
if (!IsWindow(hwndView)) {
// если окно просмотра создать не удалось, то завершаем приложение
// сообщив об ошибке
MessageBox(NULL, "Cannot create viewer window!", NULL, MB_OK);
return FALSE;}
// для красоты используем моноширинный шрифт (подробнее см. GDI)
SetWindowFont(hwndView, GetStockObject(ANSI_FIXED_FONT), FALSE);
// функция SetWindowFont в документации не описана, это макрос, определяемый
// в windowsx.h Подробнее — см. исходный текст этого файла.
// запоминаем хендл окна просмотра в структуре описания главного окна
// (при регистрации класса надо зарезервировать 4 байта — что бы и в
// Windows API и в Win32 API использовать одинаковые значения и функции)
SetWindowLong(hwnd, 0, (LONG)hwndView);
// 1. мы не знаем заранее, как много места понадобиться для описания всех
// томов и устройств DOS. Для простоты создадим временный файл с заранее
// заданным именем c:\test.txt (строго говоря, надо было бы проверить наличие
// переменной TEMP или TMP, убедиться, что она указывает на корректный
// каталог - в жизни часто она указывает на несуществующий каталог, диск
// или даже на защищенный диск - всякое бывает - и создать файл там — но это
// все слишком громоздко для примера).
// 2. в этом файле мы соберем нужный текст, а затем разом передадим его окну
// просмотра, после чего файл удалим.
// 3. для работы с файлами используем функции Windows API, что бы сохранить
// переносимость между разными платформами
hf = _lcreat(tempfile, 0);
if (hf == HFILE_ERROR) {
// при ошибке - сообщаем и заканчиваем (окно просмотра будет уничтожено
// позже, при обработке WM_DESTROY
MessageBox(NULL, "Cannot create temporary file!", NULL, MB_OK);
return FALSE;}
// пишем первый заголовок
_lwrite(hf, "******** GET VOLUME INFORMATION ********\r\n\r\n", 44);
SetLastError(ERROR_SUCCESS);
n = GetLogicalDriveStrings(0, NULL);
if (GetLastError() != ERROR_SUCCESS) {
// Функция GetLogicalDriveStrings не реализована в Win32s и (в книгах
// часто утверждают) в Windows-95, возможно, иногда это и так, но у меня
// в Windows-95 версии 4.00.950a, локализованной для России, работает.
// если все же нет, то эмулируем ее работу посредством GetLogicalDrives
// которая точно есть во всех реализациях Win32
n = GetLogicalDrives();
// узнаем, сколько всего устройств
count = 0;
for (i = 1; i; i <<= 1) if (n & i) count++;
// формируем строку с именами устройств
temp = new char [ count * 4 + 1 ];
if (temp) {
p = temp;
for (i = 0; i < 32; i ++) if (n & (1L << i)) {
*p++ = (char)('A' + i); *p++ = ':'; *p++ = '\\'; *p++ = '\0';}
*p++ = '\0';}
} else {
// если функция GetLogicalDriveStrings работает, то используем ее
temp = new char [ n + 1 ];
if (temp) GetLogicalDriveStrings(n, temp);
}
if (temp) {
// исключаем обработку сообщений о критической ошибке
errmode = SetErrorMode(SEM_FAILCRITICALERRORS);
// если строка получена, то разбираем ее по частям
for (p = temp; *p; p += lstrlen(p) + 1) {
wsprintf(buffer, "Root: %s", p);
switch (GetDriveType(p)) {
case 0: s = "???"; break;
case 1: s = "invalid"; break;
case DRIVE_REMOVABLE: s = "REMOVABLE"; break;
case DRIVE_FIXED: s = "FIXED"; break;
case DRIVE_REMOTE: s = "REMOTE"; break;
case DRIVE_CDROM: s = "CD-ROM"; break;
case DRIVE_RAMDISK: s = "RAM DISK"; break;
default: s = "what?!"; break;}
// укажем явное преобразование указателя на строку ‘s’ к типу
// LPSTR — так как в Windows API для моделей памяти с одним сегментом
// данных (tiny,small,medium) указатели по умолчанию 16-ти разрядные
// а для функций Windows обязательно нужны 32-х разрядные
wsprintf(buffer+lstrlen(buffer), " type=%s volume='", (LPSTR)s);
sernum = complen = flags = 0; file_system[0] = '\0';
if (
GetVolumeInformation(
p, buffer+lstrlen(buffer), 64,
&sernum, &complen, &flags,
file_system, sizeof(file_system)))
{// если информация о томе прочитана -> получаем и выводим более
// подробные сведения
wsprintf(buffer+lstrlen(buffer), "' serial=%08lX\r\n", sernum);
_lwrite(hf, buffer, lstrlen(buffer));
spc = bps = fc = tc = 0L;
GetDiskFreeSpace(p, &spc, &bps, &fc, &tc);
bps *= spc;
wsprintf(
buffer,
" comp. length=%lu cluster=%lu total=%luK free=%luK\r\n",
complen, bps, bps*tc/1024, bps*fc/1024);
_lwrite(hf, buffer, lstrlen(buffer));
wsprintf(buffer, " file system ='%s' flags=", file_system);
s = buffer + lstrlen(buffer);
if (flags & FS_CASE_IS_PRESERVED) {
lstrcpy(s, "CASE_PRESERVED "); s+= lstrlen(s);}
if (flags & FS_CASE_SENSITIVE) {
lstrcpy(s, "CASE_SENSITIVE "); s+= lstrlen(s);}
if (flags & FS_UNICODE_STORED_ON_DISK) {
lstrcpy(s, "UNICODE "); s+= lstrlen(s);}
if (flags & FS_PERSISTENT_ACLS) {
lstrcpy(s, "ACL "); s+= lstrlen(s);}
if (flags & FS_FILE_COMPRESSION) {
lstrcpy(s, "MAY_COMPRESS "); s+= lstrlen(s);}
if (flags & FS_VOL_IS_COMPRESSED) {
lstrcpy(s, "COMPRESSED "); s+= lstrlen(s);}
lstrcpy(s, "\r\n");
_lwrite(hf, buffer, lstrlen(buffer));
} else {
// если информация о томе не получена, то просто сообщаем
lstrcpy(
buffer+lstrlen(buffer),
"' ***** NO VOLUME INFORMATION!\r\n");
_lwrite(hf, buffer, lstrlen(buffer));}}
delete temp;
// восстанавливаем режим обработки критических ошибок
SetErrorMode(errmode);} else {
// если возникла ошибка (не хватило памяти для получения списка устройств)
// то просто выводим текст с сообщением, но не заканчиваем, что бы не
// заботиться об уничтожении временного файла досрочно
_lwrite(hf, "NOT ENOUGHT MEMORY TO GET INFORMATION!\r\n", 40);}
// пишем второй заголовок
_lwrite(hf, "\r\n******** READ DOS DEVICES MAP ********\r\n\r\n", 44);
// просто для примера назначаем Z: как каталог, содержащий win.com
GetWindowsDirectory(buffer, sizeof(buffer));
SetLastError(ERROR_SUCCESS);
DefineDosDevice(0, devZ, buffer);
if (GetLastError() != ERROR_SUCCESS) {
// функция DefineDosDevice не реализована - это не Windows NT!
_lwrite(hf, "NOTE: DefineDosDevice() is not implemented!\r\n", 45);
} else {
// если назначить удалось - это NT, получаем полный список и выводим его
// хотя может быть и Windows 98
buffer[ QueryDosDevice(NULL, buffer, 1024) ] = ‘\0’;
// функция QueryDosDevice возвращает число символов, скопированных в буфер
// и в случае Windows 98 мы получим 0, хотя сам буфер может содержать мусор.
for (p = buffer; *p; p += lstrlen(p) + 1) {
_lwrite(hf, " ", 15 - lstrlen(p));
_lwrite(hf, p, lstrlen(p));
_lwrite(hf, " = ", 3);
// используем массив file_system в качестве временной строки
QueryDosDevice(p, file_system, sizeof(file_system));
_lwrite(hf, file_system, lstrlen(file_system));
_lwrite(hf, "\r\n", 2);}
GetWindowsDirectory(buffer, sizeof(buffer));
// удаляем назначенное нами устройство
DefineDosDevice(
DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, "Z:", buffer);}
// узнаем длину файла с текстом и выделяем необходимый блок памяти
n = _llseek(hf, 0L, 1);
temp = new char [ (int)n + 1 ];
if (temp) {
// загружаем его в память и добавляем оканчивающий символ ‘\0’
_llseek(hf, 0L, 0);
temp[ _lread(hf, temp, (int)n) ] = '\0';
// и передаем окну просмотра
SetWindowText(hwndView, temp);
// наш временный буфер больше не нужен
delete temp;
} else {
// если памяти не хватило – сообщаем
SetWindowText(hwndView, "*** Cannot load text ***");}
// закрываем временный файл и удаляем его - система сама этого не сделает
_lclose(hf);
OpenFile(tempfile, &ofs, OF_DELETE);
return TRUE;}
void Cls_OnSize(HWND hwnd, UINT state, int cx, int cy)
{UNUSED_ARG(state);
// при изменении размеров главного окна меняем размеры окна просмотра
HWND hwndView = (HWND)GetWindowLong(hwnd, 0); if (IsWindow(hwndView)) MoveWindow(hwndView, 0,0, cx, cy, TRUE);}
void Cls_OnDestroy(HWND hwnd)
{// при закрытии главного окна закрываем его дочернее (лучше это сделать
// самим, хотя в крайнем случае система это сделает за вас)
HWND hwndView = (HWND)GetWindowLong(hwnd, 0);
if (IsWindow(hwndView)) DestroyWindow(hwndView);
PostQuitMessage(0);}
void Cls_OnSetFocus(HWND hwnd, HWND hwndOldFocus)
{UNUSED_ARG(hwndOldFocus);
// при получении фокуса главным окном - активируем окно просмотра, что бы
// работала клавиатура
HWND hwndView = (HWND)GetWindowLong(hwnd, 0);
if (IsWindow(hwndView)) SetFocus(hwndView);}
LONG WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{switch (uMsg) {
HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);
HANDLE_MSG(hWnd, WM_SIZE, Cls_OnSize);
HANDLE_MSG(hWnd, WM_SETFOCUS, Cls_OnSetFocus);
default: break;}
return DefWindowProc(hWnd, uMsg, wParam, lParam);}
static BOOL init_instance(HINSTANCE hInstance)
{WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 4; // для хендла окна просмотра
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
return RegisterClass(&wc) == NULL ? FALSE : TRUE;}
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{UNUSED_ARG(lpszCmdLine);
MSG msg;
HWND hWnd;
if (!hPrevInst) {
if (!init_instance(hInst)) return 1;}
hWnd= CreateWindow(
szWndClass, // class name
"window header", // window name
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT,CW_USEDEFAULT, // window position
CW_USEDEFAULT,CW_USEDEFAULT, // window size
NULL, // parent window
NULL, // menu
hInst, // current instance
NULL // user-defined parameters);
if (!hWnd) return 1;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);}
return msg.wParam;}
Файл 2B16.CPP
// включаем описания необходимых функций библиотеки времени выполнения
#include <dos.h>
#include <io.h>
// функции GetLastError и SetLastError не описаны в Windows API, эмулируем их
#define ERROR_SUCCESS 0
static int error_code = ERROR_SUCCESS;
int GetLastError(void)
{return error_code;}
int SetLastError(int err)
{int prev = error_code; return error_code = err;}
// функция GetLogicalDriveStrings не реализована в Windows API, причем в некоторых // реализациях Win32 она представлена заглушкой, которую мы и эмулируем
int GetLogicalDriveStrings(DWORD size, char* buf)
{UNUSED_ARG(size);
UNUSED_ARG(buf);
SetLastError(1);
return 0;}
// функция GetLogicalDrives не реализована в Windows API, но нам она нужна
// для нормальной работы приложения - эмулируем ее
DWORD GetLogicalDrives(void)
{DWORD dwDevices = 0L;
unsigned n, uTotal, uCurrent, uTest;
// запоминаем текущее устройство
_dos_getdrive(&uCurrent);
_dos_setdrive(uCurrent, &uTotal);
for (n = 1; n <= uTotal; n++) {
_dos_setdrive(n, &uTotal);
_dos_getdrive(&uTest);
// если устройство удалось сделать текущим - оно описано в системе
if (uTest == n) dwDevices |= 1L << (n -1);}
// восстанавливаем прежнее устройство
_dos_setdrive(uCurrent, &uTotal);
return dwDevices;}
// функция GetDriveType в Win32 API реализована несколько иначе, чем в Windows;
// переопределим функцию Win32 API UINT GetDriveType(LPSTR) через функцию
// Windows API UINT GetDriveType(UINT), заодно определим пару недостающих // символов
#ifndef DRIVE_CDROM
#define DRIVE_CDROM 5
#endif
#ifndef DRIVE_RAMDISK
#define DRIVE_RAMDISK 6
#endif
UINT GetDriveType(char* pRoot)
{char c;
if (pRoot) {
c = (char)AnsiUpper((LPSTR)(DWORD)(UINT)(unsigned char)(pRoot[ 0 ]));
if (c >= 'A' && c <= 'Z') return GetDriveType(c - 'A');}
return 0;}
// функция GetVolumeInformation не определена в Windows API. Эмулируем ее // в сильно упрощенном виде, заодно определяем необходимые символы.
#define FS_CASE_IS_PRESERVED 1
#define FS_CASE_SENSITIVE 2
#define FS_UNICODE_STORED_ON_DISK 4
#define FS_PERSISTENT_ACLS 16
#define FS_FILE_COMPRESSION 32
#define FS_VOL_IS_COMPRESSED 64
BOOL GetVolumeInformation(
char *pRoot, char* pVolume, int cbVolume,
DWORD *sernum, DWORD *complen, DWORD *flags,
char *pFS, int cbFS)
{BOOL fReturnCode = FALSE;
char c;
unsigned uDrive, uCurrent, n;
UNUSED_ARG(cbVolume);
UNUSED_ARG(cbFS);
if (pRoot) {
c= (char)AnsiUpper((LPSTR)(DWORD)(UINT)(unsigned char)(pRoot[ 0 ]));
if (c >= 'A' && c <= 'Z') {
uDrive = (unsigned)(c - 'A' + 1);
_dos_getdrive(&uCurrent);
_dos_setdrive(uDrive, &n);
_dos_getdrive(&n);
if (uDrive == n) {// убедимся, что устройство определено
// строго говоря все это стоит определить точнее:
pVolume[0] = '\0';
*sernum = 0L;
*complen = 12;
*flags = (long)FS_CASE_IS_PRESERVED;
pFS = '\0';
// проверим, есть ли доступ к устройству - если мы вернем
// TRUE, то позже нами будет предпринята попытка узнать
// размер тома
if (!access(pRoot, F_OK)) fReturnCode = TRUE;}
// восстановим текущее устройство
_dos_setdrive(uCurrent, &n);}}
return fReturnCode;}
// функция GetDiskFree не определена в Windows API, эмулируем ее посредством
// функции _dos_getdiskfree стандартной библиотеки времени выполнения.
void GetDiskFreeSpace(char* pRoot, DWORD* spc, DWORD* bps, DWORD* fc, DWORD* tc)
{char c;
diskfree_t df;
if (pRoot) {
c = (char)AnsiUpper((LPSTR)(DWORD)(UINT)(unsigned char)(pRoot[ 0 ]));
if (c >= 'A' && c <= 'Z') {
if (!_dos_getdiskfree(c - 'A' + 1, &df)) {
*spc = df.sectors_per_cluster;
*bps = df.bytes_per_sector;
*fc = df.avail_clusters;
*tc = df.total_clusters;}}}}
// функции DefineDosDevice и QueryDosDevice не реализованы в Windows API.
// используем вместо них заглушки, так как они имеют смысл только для NT
#define DDD_REMOVE_DEFINITION 1
#define DDD_EXACT_MATCH_ON_REMOVE 2
BOOL DefineDosDevice(DWORD dwFlags, LPSTR lpDosDevice, LPSTR lpPath)
{UNUSED_ARG(dwFlags);
UNUSED_ARG(lpDosDevice);
UNUSED_ARG(lpPath);
SetLastError(1);
return FALSE;}
DWORD QueryDosDevice(LPSTR lpDosDevice, LPSTR lpPath, DWORD ucchMax)
{UNUSED_ARG(lpDosDevice);
UNUSED_ARG(lpPath);
UNUSED_ARG(ucchMax);
SetLastError(1);
return 0L;}
Резюме
Любопытно, что в существующем виде приложение по–разному работает на разных платформах — для получения информации о томах и доступном пространстве предпринимается попытка чтения с устройства. Однако том в устройстве может отсутствовать вовсе — например, в дисководе может не быть дискеты. Разные платформы реагируют на такое событие разным образом — Windows–95, убедившись что тома нет, просто возвращает нули, а Windows NT или Windows 3.x выдают сообщение о системной ошибке.
В приложении специально применяется функция SetErrorMode, которая позволяет отключить вывод сообщений о невозможности чтения с диска. Любопытно, что в документации часто указано, что эта функция для Win32 API реализована только для RISC процессоров — странно, но на обычных Intel Pentium она тоже сработала. В пояснениях к результатам теста реакция системы на критические ошибки приводится так, как будто эта функция не применяется. На самом деле в приведенном приложении (с применяемой функцией SetErrorMode) сообщений об ошибках не будет.
Приложение 2B было протестировано на 2х компьютерах:
А) компьютер с Windows–953 и Windows 3.11 + Win32s
диск A — 3.5”, дискета не вставлена
диск C — EIDE HDD, том “Bootable”
диск D — SCSI HDD, под Windows 3.11 доступен через ASPI драйвер, том “SCSI_VOL”
диск E — IDE CD–ROM, диск не вставлен
Б) компьютер с Windows NT
диск A — 3.5”, дискета не вставлена
диск C — SCSI HDD,
диск D — IDE CD–ROM, диск не вставлен
Всего было выполнено 6 тестов — запускались 16ти и 32х разрядные версии приложения в среде Windows 3.11 + Win32s, Windows–95 и Windows NT Server 4.0. Результаты тестов следующие:
Среда Windows 3.11+Win32s, 16ти разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=16384 total=822272K free=71712K
file system ='' flags=CASE_PRESERVED
Root: D:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=32768 total=1065792K free=45408K
file system ='' flags=CASE_PRESERVED
Root: E:\ type=REMOTE volume='' ***** NO VOLUME INFORMATION!
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
16ти разрядное приложение пытается обратиться к дискам в функции access (в эмуляции GetVolumeInformation), что для дисков A (гибкий диск) и E (CD–ROM) приводит к системному сообщению об ошибке “Cannot read from drive ...”;
Локальный CD–ROM распознается как сетевое устройство.
Среда Windows 3.11+Win32s, 32х разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='BOOTABLE' serial=00000000
maximal component length=12 cluster=16384 total=822272K free=71728K
file system ='FAT' flags=
Root: D:\ type=RAM DISK volume='SCSI_VOL' serial=00000000
maximal component length=12 cluster=32768 total=1065792K free=45408K
file system ='FAT' flags=
Root: E:\ type=CD-ROM volume='SCSI_VOL' serial=00000000
maximal component length=12 cluster=0 total=0K free=0K
file system ='FAT' flags=
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Реакция приложения совершенно другая (используются функции, входящие в реализацию Win32 API, а не их эмуляция нашим приложением).
При обращении к диску A генерируется сообщение об ошибке,
При обращении к диску E (CD–ROM) функция GetVolumeInformation (причем именно ее реализация в Win32s, а не ее эмуляция у нас) дает сообщение об ошибке и сообщает, что информация о томе успешно (?!) получена, после чего следует попытка узнать свободное пространство на отсутствующем диске E — с еще одним сообщением об ошибке (вместо сообщения “no volume information”).
Характерно, что тип диска E — CD–ROM — определен корректно, файловая система — CDFS (файловая система CD дисков) почему–то распознана как FAT, а метку тома позаимствовали у предыдущего диска4.
Кроме того, SCSI диск, доступный через ASPI, был распознан как RAM DISK.
Среда Windows 95, 16ти разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=16384 total=822272K free=70208K
file system ='' flags=CASE_PRESERVED
Root: D:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=32768 total=1065792K free=45408K
file system ='' flags=CASE_PRESERVED
Root: E:\ type=REMOTE volume='' ***** NO VOLUME INFORMATION!
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Уже неплохо — результаты совпадают с тем, что было получено для 16ти разрядного приложения в среде Windows 3.11, реакция на получение информации об отсутствующем диске такая–же — сообщение об ошибке.
Среда Windows 95, 32х разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: a:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: c:\ type=FIXED volume='BOOTABLE' serial=0E3219D9
maximal component length=255 cluster=16384 total=822272K free=69696K
file system ='FAT' flags=CASE_PRESERVED UNICODE
Root: d:\ type=FIXED volume='SCSI_VOL' serial=025511DA
maximal component length=255 cluster=32768 total=1065792K free=45408K
file system ='FAT' flags=CASE_PRESERVED UNICODE
Root: e:\ type=CD-ROM volume='' ***** NO VOLUME INFORMATION!
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Пожалуй, самый приличный результат. Все срабатывает корректно и без сообщений о критических ошибках, даже если не использовать функцию SetErrorMode. Типы дисков и файловые системы определяются корректно.
Среда Windows NT, 16ти разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00000000
maximal component length=12 cluster=32768 total=999968K free=615136K
file system ='' flags=CASE_PRESERVED
Root: D:\ type=REMOTE volume='' serial=00000000
maximal component length=12 cluster=16384 total=601632K free=0K
file system ='' flags=CASE_PRESERVED
******** READ DOS DEVICES MAP ********
NOTE: DefineDosDevice() is not implemented!
Поведение приложения вполне соответствует обычному 16ти разрядному приложению в среде Windows 3.x, но вот полный размер диска, превышающий 1Г определяется с ошибкой — диск C на 1.33Г был определен как диск размером 999М.
Среда Windows NT, 32х разрядное приложение:
******** GET VOLUME INFORMATION ********
Root: A:\ type=REMOVABLE volume='' ***** NO VOLUME INFORMATION!
Root: C:\ type=FIXED volume='' serial=00AE5141
maximal component length=255 cluster=512 total=1405655K free=615143K
file system ='NTFS' flags=CASE_PRESERVED CASE_SENSITIVE UNICODE ACL MAY_COMPRESS
Root: D:\ type=CD-ROM volume='ASART3' serial=E2F025BC
maximal component length=221 cluster=2048 total=601644K free=0K
file system ='CDFS' flags=CASE_SENSITIVE
******** READ DOS DEVICES MAP ********
DISPLAY1 = \Device\Video0
NDIS = \Device\Ndis
DISPLAY2 = \Device\Video1
Z: = \??\C:\WINNT
D: = \Device\CdRom0
$VDMLPT1 = \Device\ParallelVdm0
COM1 = \Device\Serial0
COM2 = \Device\Serial10000
PIPE = \Device\NamedPipe
UNC = \Device\Mup
PhysicalDrive0 = \Device\Harddisk0\Partition0
PRN = \DosDevices\LPT1
A: = \Device\Floppy0
Scsi0: = \Device\ScsiPort0
DC21X41 = \Device\DC21X41
LPT1 = \Device\Parallel0
Scsi1: = \Device\ScsiPort1
C: = \Device\Harddisk1\Partition1
AUX = \DosDevices\COM1
MAILSLOT = \Device\MailSlot
NUL = \Device\Null
Единственное, что кажется несколько странным, так это сообщение о критической ошибке при попытке вызова GetVolumeInformation для отсутствующего диска, хотя под Windows–95 эта функция работает молча — она же и так возвращает результат “не удалось”, так зачем же еще давать сообщение? Сообщение об ошибке отключается с помощью SetErrorMode, хотя это и не согласуется с формальным описанием функции.
Итого:
Если ваше приложение может работать в Windows 3.x с Win32s, то необходимо особенно тщательное тестирование всех операций с файлами и томами, так как реакция системы может существенно отличаться от той, которая будет в случае Windows–95 и Windows NT. Кроме того особое внимание стоит уделить функциям, возвращающим информацию о томе (типа GetVolumeInformation) — они производят попытку реального обращения к томам, что может привести либо к возникновению сообщений об ошибках либо даже к «зависанию» всего приложения, если работа с томом сопровождается какой–либо ошибкой. Так, например, отдельной проверки и отладки потребуют все случаи работы со сменными дисками (гибкими, компакт–дисками, магнито–оптическими дисками и т.д.) и с сетевыми томами (особенно случаи, когда удаленный компьютер, предоставляющий свои тома в общее пользование, зависает, отключается или происходят какие–либо неполадки в работе сети — в такой ситуации возможно даже зависание компьютера, с которого производится вызов функции GetVolumeInformation).
Работа с каталогами и файлами
Win32 API содержит все необходимые функции для работы с каталогами и файлами. При этом набор функций, реализованных в Windows полностью перекрывает обычный набор функций библиотеки времени выполнения, так что необходимость в применении функций последней отпадает. Рассматриваемые здесь функции служат следующим целям:
работа с каталогами (создание, удаление, поиск и получение списка файлов);
получение и изменение атрибутов файлов;
работа с файлами (длинными и короткими именами файлов, копирование, перемещение и удаление файлов);
В таком порядке мы и познакомимся с функциями Win32 API.
Работа с каталогами
Аналогично обычному Windows API в Win32 API используется понятие текущего каталога, в котором должны размещаться файлы, если только в их имени не содержится путь к этим файлам. Иногда возможно задание относительного пути (подробнее см. Error: Reference source not found, стр. Error: Reference source not found). Вы можете как узнать текущий каталог с помощью функции GetCurrentDirectory, так и сделать текущим какой–либо иной каталог, воспользовавшись функцией SetCurrentDirectory.
DWORD GetCurrentDirectory(cchDir, lpszDir);
BOOL SetCurrentDirectory(lpszDir);
Параметры функций: lpszDir — буфер, содержащий или получающий имя текущего каталога, cchDir — максимальное число символов в имени текущего каталога, которые могут уместиться в отведенном буфере. Не забывайте, что длина имени (это полный путь к текущему каталогу) может быть очень большой, поэтому удобно сначала узнать необходимый размер буфера, выделить его и затем получить имя:
DWORD nSize;
char *pszCurDir;
dwSize = GetCurrentDirectory(0, NULL); // возвращается требуемая длина,
pszCurDir = new char [ dwSize ]; // включая завершающий ‘\0’
GetCurrentDirectory(dwSize, pszCurDir);
При необходимости вы можете создавать или уничтожать каталоги с помощью функций CreateDirectory и RemoveDirectory. Как и в случае функций библиотеки времени выполнения, функция RemoveDirectory может удалить только пустой каталог; в противном случае функция сообщит об ошибке.
BOOL CreateDirectory(lpszDir, lpsaSA);
BOOL RemoveDirectory(lpszDir);
Параметр lpSA является указателем на структуру SECURITY_ATTRIBUTES, с помощью которого вы можете управлять доступом к файлам.
DWORD GetTempPath(cchBuffer, lpszTempPath);
Эта функция дополняет прежние GetTempDrive и GetTempFileName. Она возвращает путь к каталогу, предназначенному для хранения временных файлов.
Несколько других функций предназначены для поиска файла или для получения списка файлов в каталоге. Функция SearchPath ищет определенный файл и возвращает полный путь к файлу, если только этот файл найден.
DWORD SearchPath(lpszPath, lpszFile, lpszExt, cchBuffer, lpszBuffer, plpszFileNamePart);
Параметр lpszPath указывает на каталог, в котором надо искать файл. Если этот параметр равен NULL, то Windows будет искать файл в том каталоге, откуда приложение запущено, в текущем каталоге, в системных каталогах Windows, и в самом каталоге Windows, а также во всех каталогах, перечисленных на пути поиска, заданном параметром окружения PATH. Параметр lpszFile задает имя файла для поиска, lpszExt — расширение имени искомого файла (используется, если указанное имя не содержит расширения). Параметры cchBuffer, lpszBuffer задают характеристики (размер и адрес) буфера, принимающего имя файла, а указатель на указатель plpszFileNamePart определит позицию, начиная с которой в буфере находится имя файла (то есть первый символ после последнего ‘\’ в пути).
Три функции FindFirstFile, FindNextFile и FindClose примерно соответствуют функциям _dos_findfirst, _dos_findnext и _dos_findclose библиотеки времени выполнения. Функция FindFirstFile требует задания шаблона имен файлов и указателя на специальную структуру WIN32_FIND_DATA, которая будет заполняться информацией о текущем файле, соответствующем шаблону. Функция создает специальный объект, используемый в последующих вызовах функции FindNextFile для продолжения перебора файлов в каталоге. После завершения перебора созданный объект необходимо удалить с помощью функции FindClose.
HANDLE FindFirstFile(lpszFileName, lpFindFileData);
BOOL FindNextFile(hFindFile, lpFindFileData);
BOOL FindClose(hFindFile);
struct WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwSizeHigh;
DWORD dwSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];};
Пример:
WIN32_FIND_DATA fd;
HANDLE hFD;
hFD = FindFirstFile(“c:\\*.*”, &fd);
if (hFD) {
do {
// анализ структуры fd и выполнение необходимых действий над файлами.
} while (FindNextFile(hFD, &fd));
FindClose(hFD);}
Последние рассматриваемые нами функции позволяют получать информацию об изменении каталога файлов. Система создает специальный объект, который функция FindFirstChangeNotification первоначально устанавливает в занятое состояние, и который автоматически переводится в свободное состояние, как только в каталоге (или в подкаталогах) происходит указанное изменение. Ожидаемое изменение задается параметром fdwFilter при вызове функции FindFirstChangeNotification.
HANDLE FindFirstChangeNotification(lpszPath, fWatchSubTree, fdwFilter);
HANDLE FindNextChangeNotification(hChange);
BOOL FindCloseChangeNotifictaion(hChange);5
Функции FindFirstChangeNotification и FindNextChangeNotification возвращают управление сразу после установки созданного объекта в занятое состояние. Вы можете использовать функции WaitForSingleObject или WaitForMultipleObjects для ожидания изменений в каталоге.
Функция FindCloseChangeNotifictaion удаляет созданный объект.
Внимание! Эти функции не реализованы для Windows 3.x с Win32s. Однако, помимо этого ограничения, существует еще несколько особенностей в применении этих функций. Так, при работе в Windows–95 переименование одного файла в каталоге может приводить к двукратному сообщению об изменении содержимого каталога. Еще серьезнее ограничение на использование удаленных каталогов (каталогов других компьютеров, предоставленных в общий доступ) — система вообще не отслеживает изменения в них, даже если эти изменения осуществлены с вашего компьютера. Это справедливо и для Windows–95 и для Windows NT.
Получение и изменение атрибутов файлов
В данном разделе мы рассмотрим основные функции Win32 API для получения и изменения атрибутов файлов. Для каждого файла поддерживается несколько атрибутов, универсальных для разных файловых систем. К ним относятся атрибуты архивный (archive), скрытый (hidden), системный (system), только для чтения (read–only), временный (temporary) и каталог (directory), а также дата и время создания, дата и время последнего изменения и дата и время последнего обращения к файлу.
Некоторые из этих атрибутов поддерживаются ограниченно. Так, например, атрибут временный (temporary) используется только для оптимизации доступа к файлу (система по возможности сохраняет все данные в оперативной памяти, не записывая их на диск), сам же файл при закрытии не удаляется.
Разные файловые системы, которые могут использоваться локальными или удаленными дисками, могут предоставлять даты и времена с разной точностью и в разном комплекте. Так, тома с обычной FAT под управлением MS–DOS хранят только дату и время последнего изменения файла с точностью до 2х секунд, тома с FAT, поддерживаемые системами типа Windows–95, Windows NT или OS/2 сохраняют дату и время создания и последнего изменения с точностью до 2х секунд и дату (без времени) последнего доступа к файлу. Как правило файловые утилиты показывают только время последнего изменения файла, так как эта информация, во–первых, предоставлена всеми используемыми файловыми системами, а во–вторых, потому что именно эта дата обычно интересует пользователя.
Для получения и изменения атрибутов предназначены две функции:
DWORD GetFileAttributes(lpFileName);
DWORD SetFileAttributes(lpFileName, dwFileAttributes);
Обе функции требуют имя файла, атрибуты которого возвращаются (изменяются), а также они упаковывают информацию об атрибутах в виде двойного слова, отдельные биты которого информируют о наличии тех или иных атрибутов. Вы можете использовать следующие символы, определяемые вместо конкретных значений атрибутов:
FILE_ATTRIBUTE_ARCHIVE
FILE_ATTRIBUTE_DIRECTORY
FILE_ATTRIBUTE_HIDDEN
FILE_ATTRIBUTE_NORMAL
FILE_ATTRIBUTE_READONLY
FILE_ATTRIBUTE_SYSTEM
FILE_ATTRIBUTE_TEMPORARY
Помимо двух рассмотренных функций, атрибуты файлов возвращаются еще некоторыми другими, как, например, уже рассмотренная нами функции FindFirstFile и FindNextFile, размещающие эту информацию в структуре WIN32_FIND_DATA, или функция GetFileInformationByHandle, возвращающая их в структуре BY_HANDLE_FILE_INFORMATION. Функция GetFileInformationByHandle не работает в системе Windows 3.x с Win32s.
BOOL GetFileInformationByHandle(hFile, lpFileInformation);
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;};
Перед рассмотрением функций, работающих с датой и временем, надо сделать несколько оговорок, касающихся способов представления даты и времени.
Во–первых, дата и время могут задаваться как всеобщее (UTC, coordinated universal time, время по Гринвичу) так и как локальное время. Разница сводится к учету часового пояса и перехода на летнее время. Подробнее — см. описание функций SetTimeZoneInformation и GetTimeZoneInformation.
Во–вторых, и дата и время может быть записана разными способами и в разных форматах. Иногда дата и время объединяются в одну структуру, иногда они разделяются по разным структурам. Способы упаковки данных в структуры различаются как в зависимости от операционных систем, так и в зависимости от тех функций, которые применяют информацию о дате и времени.
При работе с файлами в Win32 API дата и время записаны как одно квадрослово (реально представлено в виде структуры из двух двойных слов) FILETIME.
struct FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;};
Это квадрослово показывает число интервалов по 0.000 000 1 секунде (100 наносекунд), которые прошли, считая от 4 часов утра 1 января 1601 года. Такая, на первый взгляд странная, начальная дата объясняется просто: по правилам григорианского календаря високосными считаются года, номер которых нацело делится на 4, кроме тех, которые делятся нацело на 100, но включая те, номер которых нацело делится на 400. То есть год 1904 был високосным, 1908 тоже, а 1900 — нет. Однако 2000 год, равно как и 1600 год был таковым. Таким образом за начальную точку отсчета времени был взят первый день последнего 400–летнего цикла.
Для каждого файла определено 3 момента, информация о которых заносится в систему: дата и время создания файла, дата и время последнего изменения файла и дата и время последнего обращения к файлу.
Помимо уже рассмотренных функций FindFirstFile, FindNextFile и GetFileInformationByHandle, вы можете узнать или назначить для конкретного файла эти времена с помощью следующих функций:
BOOL GetFileTime(hFile, lpftCreation, lpftLastAccess, lpftLastWrite);
BOOL SetFileTime(hFile, lpftCreation, lpftLastAccess, lpftLastWrite);
Где hFile является хендлом открытого файла (см. функцию CreateFile), lpftCreation, lpftLastAccess и lpftLastWrite — указателями на три структуры типа FILETIME. Система использует информацию о дате и времени всегда в виде всеобщего времени.
При необходимости вы можете узнать, какое событие произошло раньше с помощью функции
LONG CompareFileTime(lpft1, lpft2);
А также вы можете преобразовать дату и время из всеобщего в локальное (с учетом часового пояса) и наоборот с помощью функций:
BOOL FileTimeToLocalFileTime(lpftUTC, lpftLocal);
BOOL LocalFileTimeToFileTime(lpftLocal, lpftUTC);
Обе эти функции используют структуры FILETIME для получения времени и возвращения результата.
Если вы переносите разработанную ранее программу под Win32 API, то может быть проще не переписывать все функции, анализирующие дату и время, а просто преобразовать полученные данные в формат MS–DOS. Для этого предназначены функции:
BOOL FileTimeToDosDateTime(lpft, lpwDOSDate, lpwDOSTime);
BOOL DosDateTimeToFileTime(wDOSDate, wDOSTime, lpft);
которые преобразуют дату и время из FILETIME в формат MS–DOS — два слова, в одном упаковывается дата, а в другом — время. Однако в иных случаях применять эти функции не рекомендуется. Проще преобразовать FILETIME в SYSTEMTIME, которая содержит всю необходимую информацию в отдельных полях структуры, что позволяет ее легко анализировать.
BOOL FileTimeToSystemTime(lpft, lpst);
BOOL SystemTimeToFileTime(lpst, lpft);
struct SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMillisecond;};
Параметры lpft и lpst являются указателями на структуры FILETIME и SYSTEMTIME соответственно; поля структуры SYSTEMTIME определяют год, месяц (январь считается 1), номер дня недели (0 — воскресенье), день месяца (начиная с 1), час, минуту, секунду и миллисекунду. Применение этих функций можно увидеть в примере 3B.
Работа с файлами
Как уже говорилось, в Win32 API есть все необходимое для работы с файлами. Приложение не нуждается в разработке дополнительного кода для выполнения типовых операций над файлами. Для удобства разработчиков в Win32 API определены специальные функции, осуществляющие копирование, перемещение, удаление и переименование файлов. Для выполнения этих операций предназначены 4 функции, декларированные в Win32 API:
BOOL CopyFile(lpszFileName, lpszNewFileName, fFailIfExists);
BOOL DeleteFile(lpszFileName);
BOOL MoveFile(lpszFileName, lpszNewFileName);
BOOL MoveFileEx(lpszFileName, lpszNewFileName, fdwFlags);
Функция CopyFile осуществляет копирование файла, заданного параметром lpszFileName в файл, заданный параметром lpszNewFileName. Дополнительный параметр функции fFailIfExist определяет реакцию системы в том случае, когда новый файл уже существует. Если fFailIfExist равно TRUE, то существующий файл сохраняется, а если этот параметр равен FALSE, то файл будет перезаписан.
Функция DeleteFile удаляет указанный файл.
Функции MoveFile и MoveFileEx осуществляют перемещение или копирование указанного файла. Однако области применения этих функций существенно различаются. Согласно описанию, функция MoveFile может переименовывать или перемещать файлы или каталоги, причем каталог перемещается целиком, со всеми входящими в него файлами и подкаталогами. Функция возвращает код ошибки в том случае, когда итоговый файл или каталог уже существует. Если перемещение осуществляется в пределах одного тома, то процесс перемещения сводится только к исправлению записей в каталогах, а реального перемещения данных не происходит. А если перемещение осуществляется на другой том, то функция MoveFile просто копирует файл и удаляет исходный, но при этом перемещение каталога на другой том не осуществляется — каталог можно перемещать только в пределах своего тома.
Функция MoveFileEx (не реализована в Windows–95 и в Win32s!) может переименовывать, перемещать или удалять только файлы, как в пределах одного тома, так и с тома на том. С помощью дополнительного параметра fdwFlags можно уточнить, каким образом будет осуществляться перемещение файла. Флаг MOVEFILE_REPLACE_EXISTING определяет, что файл будет перемещен или переименован, даже если итоговый файл уже существует. Если файл должен быть перемещен на другой диск, то аналогично функции MoveFile перемещение осуществляется как копирование и удаление исходного файла; если не указать флаг MOVEFILE_COPY_ALLOWED, то такое перемещение будет запрещено. Еще один флаг MOVEFILE_DELAY_UNTIL_REBOOT очень удобен при разработке инсталляционных программ. Этот флаг говорит системе, что файл надо перемещать не сейчас, при последующей загрузке системы. Таким образом появляется возможность замены файлов, являющихся компонентами системы или используемыми системой в процессе работы. Частный случай использования этой функции — задание параметра lpszNewFileName равным NULL совместно с флагом MOVEFILE_DELAY_UNTIL_REBOOT (согласно описанию допустимо только такое сочетание) — приводит к удалению указанного файла при перезагрузке системы.
Пример 3B — работа с каталогами
Данный пример реализован исключительно для платформы Win32, так как в нем демонстрируется применение некоторых функций, специфичных именно для этой платформы. В главном окне приложения отображается содержимое текущего каталога; дополнительно устанавливается механизм извещения приложения об обновлении каталога, так что при любом изменении в каталоге — создании нового файла, удалении существующего, переименовании и пр. — содержимое окна обновляется.
В данном примере информирование приложения о смене содержимого каталога осуществляется с помощью функций FindFirstChangeNotification и FindNextChangeNotification, специфичных для Win32 API. Схожего эффекта можно было–бы добиться при использовании средств File Manager Extension (заголовочный файл fmext.h) в рамках Windows API. Схожего, но не такого же. Например, использование расширений диспетчера файлов в Windows API для получения информации об обновлении содержимого каталога ограничено только одним приложением в системе — два запущенных приложения уже не могут получать такие уведомления.
Функции FindFirstChangeNotification и FindNextChangeNotification используют специальный объект, который переводится в занятое состояние до ближайшего изменения каталога. Напрашивается простейший способ — вызвать функцию FindFirstChangeNotification и затем сразу функцию WaitForSingleObject, так что приложение остановится до тех пор, пока оно не получит уведомление об изменении каталога. Способ надежный, но остановленное приложение не сможет обновлять своего содержимого в окне (если его окно на время перекроют другие окна), вы даже не сможете завершить ваше приложение, так как оно ждет уведомления об обновлении каталога.
Из этой ситуации можно предложить два выхода — один, исключительно в стиле Win32 — создать отдельный поток, который будет ждать обновлений. Наверное, это самый эффективный способ, но о создание многопотоковых приложений будет обсуждено существенно позже. Другой путь — скрестить средства Windows API и Win32 API — вместо ожидания освобождения объекта, проверять его состояние по таймеру. В этом приеме практически не требуется дополнительного программирования, достаточно добавить инициализацию таймера и обработку сообщений WM_TIMER.
Собственно, именно таким путем и решена данная задача. Она построена аналогично примеру 2B — оглавление каталога, сформированное в виде текста, передается специальному окну–редактору, которое закрывает всю внутреннюю область главного окна приложения. В оконную процедуру главного окна приложения добавляется обработка сообщения WM_TIMER. Сам таймер инициализируется и освобождается в функции WinMain. Обработчик сообщений таймера — Cls_OnTimer — проверяет состояние синхронизирующего объекта (созданного при создании окна функцией FindFirstChangeNotification) и, если он освободился, принимает меры для обновления окна и снова переводит объект его в занятое состояние с помощью функции FindNextChangeNotification. Конечно, вместо таймера эффективнее было бы применить отдельный поток; однако приведенный вариант сравнительно легко адаптируется под 16ти разрядную платформу, в которой многопотоковые приложения не реализованы.
Функция, получающая оглавление каталога и формирующая отображаемый список вынесена в обработчик специального сообщения MY_REFRESH. Номер этому сообщению мы присваиваем сами, так, что бы он не конфликтовал со стандартными сообщениями окна. Для этого можно воспользоваться символом WM_USER, соответствующим самому младшему номеру сообщения, не являющего стандартным. Все номера сообщений равные или превышающие WM_USER стандартным окном6 не используются. Это же позволит продемонстрировать определение распаковщиков для собственного сообщения.
Файл 3B.CPP
#define STRICT
#include <windows.h>
#include <windowsx.h>
#define UNUSED_ARG(arg) (arg)=(arg)
// определяем собственное сообщение и его распаковщик
#define MY_REFRESH WM_USER+1
/* void Cls_OnMyRefresh(HWND hwnd); */
#define HANDLE_MY_REFRESH(hwnd,wParam,lParam,fn) ((fn)(hwnd),0L)
#define FORWARD_MY_REFRESH(hwnd,fn) (void)(fn)((hwnd),MY_REFRESH,0,0L)
// резервируем глобальные переменные
static char szWndClass[]= "File & folder operations";
static char *pszCurDir;
static HINSTANCE hInstance;
// разрабатываем функцию, обновляющую содержимое окна просмотра
static void Cls_OnMyRefresh(HWND hwnd)
{int nSize;
char *pszList, szTemp[ 1024 ];
char szCreate[ 20 ], szRead[ 20 ], szWrite[ 20 ], szAttr[ 6 ];
WIN32_FIND_DATA fd;
FILETIME ftLocal;
SYSTEMTIME stTime;
HANDLE hFD;
static char szFileMask[] = "*.*";
static char szTimeFmt[] = "%02u-%02u-%04u %02u:%02u:%02u";
hFD = FindFirstFile(szFileMask, &fd);
if (hFD) {
// так как список файлов может быть длинным, то определяем его дину
nSize = lstrlen(pszCurDir) + 82; // длина заголовка списка
do {
nSize += lstrlen(fd.cFileName) + 76; // длина записи о каждом файле
} while (FindNextFile(hFD, &fd));
FindClose(hFD);
// резервируем необходимое пространство, включая последний ‘\0’ символ
pszList = new char [ nSize + 1 ];
if (pszList) {
// готовимся к получению списка файлов
hFD = FindFirstFile(szFileMask, &fd);
if (hFD) {
// формируем заголовок
pszList[0] = '[';
lstrcpy(pszList + 1, pszCurDir);
nSize = lstrlen(pszCurDir) + 1;
lstrcpy(
pszList + nSize,
"]\r\n creation | last write"
" | last access | attrs | name");
nSize += 81;
do {
// перебираем все файлы по одному
// учитываем переход от Гринвича к локальному времени
FileTimeToLocalFileTime(&(fd.ftCreationTime), &ftLocal);
// преобразуем к приемлемому для чтения формату
FileTimeToSystemTime(&ftLocal, &stTime);
// записываем дату и время
wsprintf(
szCreate, szTimeFmt, stTime.wDay, stTime.wMonth,
stTime.wYear, stTime.wHour, stTime.wMinute, stTime.wSecond);
// аналогично записываем время изменения
FileTimeToLocalFileTime(&(fd.ftLastWriteTime), &ftLocal);
FileTimeToSystemTime(&ftLocal, &stTime);
wsprintf(
szWrite, szTimeFmt, stTime.wDay, stTime.wMonth,
stTime.wYear, stTime.wHour, stTime.wMinute, stTime.wSecond);
// и время последнего доступа
FileTimeToLocalFileTime(&(fd.ftLastAccessTime), &ftLocal);
FileTimeToSystemTime(&ftLocal, &stTime);
wsprintf(
szRead, szTimeFmt, stTime.wDay, stTime.wMonth,
stTime.wYear, stTime.wHour, stTime.wMinute, stTime.wSecond);
// расшифровываем атрибуты файла
szAttr[0] = fd.dwFileAttributes&FILE_ATTRIBUTE_ARCHIVE ? 'A':'-';
szAttr[1] = fd.dwFileAttributes&FILE_ATTRIBUTE_HIDDEN ? 'H':'-';
szAttr[2] = fd.dwFileAttributes&FILE_ATTRIBUTE_SYSTEM ? 'S':'-';
szAttr[3] = fd.dwFileAttributes&FILE_ATTRIBUTE_READONLY ? 'R':'-';
szAttr[4] = fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY?'D':'-';
szAttr[5] = '\0';
// и формируем строку для вывода и добавляем ее в список
wsprintf(
pszList + nSize,
"\r\n%19.19s | %19.19s | %19.19s | %5.5s | ",
szCreate, szWrite, szRead, szAttr);
nSize += 76;
lstrcpy(pszList + nSize, fd.cFileName);
nSize += lstrlen(fd.cFileName);
} while (FindNextFile(hFD, &fd));
FindClose(hFD);
SetWindowText((HWND)GetWindowLong(hwnd, 0), pszList);
// после передачи текста окну просмотра
// выделенный для него буфер больше не нужен.
delete pszList;
return;
// дальше мы можем оказаться только в случае ошибки}
delete pszList;}}
SetWindowText((HWND)GetWindowLong(hwnd, 0), "*** Found errors! ***");}
// а это обработчик сообщения WM_CREATE. Создаем дочернее окно просмотра и // синхронизирующий объект, для получения уведомлений об изменении каталога
static BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{UNUSED_ARG(lpCreateStruct);
HWND hwndView;
RECT rc;
HANDLE hWait;
// создаем окно просмотра
GetClientRect(hwnd, &rc);
hwndView = CreateWindow(
"EDIT", "",
WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|ES_MULTILINE|ES_READONLY,
0, 0, rc.right, rc.bottom, hwnd, (HMENU)1, hInstance, NULL);
if (!IsWindow(hwndView)) {
MessageBox(NULL, "Cannot create viewer window!", NULL, MB_OK);
return FALSE;}
SetWindowFont(hwndView, GetStockObject(ANSI_FIXED_FONT), FALSE);
SetWindowLong(hwnd, 0, (LONG)hwndView);
// инициализируем синхронизирующий объект
hWait = FindFirstChangeNotification(
pszCurDir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
SetWindowLong(hwnd, 4, (LONG)hWait);
// первоначально заполняем окно просмотра
FORWARD_MY_REFRESH(hwnd, PostMessage);
return TRUE;}
// при изменении размеров главного окна надо изменить размеры дочернего окна просмотра
static void Cls_OnSize(HWND hwnd, UINT state, int cx, int cy)
{UNUSED_ARG(state);
HWND hwndView = (HWND)GetWindowLong(hwnd, 0);
if (IsWindow(hwndView)) MoveWindow(hwndView, 0,0, cx, cy, TRUE);}
// когда главное окно закрывается уничтожаем окно просмотра и синхронизирующий объект
static void Cls_OnDestroy(HWND hwnd)
{HWND hwndView = (HWND)GetWindowLong(hwnd, 0);
if (IsWindow(hwndView)) DestroyWindow(hwndView);
HANDLE hWait = (HANDLE)GetWindowLong(hwnd, 4);
if (hWait) FindCloseChangeNotification(hWait);
PostQuitMessage(0);}
// при получении главным окном фокуса ввода, передаем его окну просмотра
static void Cls_OnSetFocus(HWND hwnd, HWND hwndOldFocus)
{UNUSED_ARG(hwndOldFocus);
HWND hwndView = (HWND)GetWindowLong(hwnd, 0);
if (IsWindow(hwndView)) SetFocus(hwndView);}
// по таймеру (у нас - каждые 2 секунды) проверяем состояние синхронизирующего объекта
void Cls_OnTimer(HWND hwnd, UINT id)
{UNUSED_ARG(id);
HANDLE hWait;
hWait = (HANDLE)GetWindowLong(hwnd, 4);
if (hWait) {
if (WaitForSingleObject(hWait, 0) == WAIT_OBJECT_0) {
// объект освобожден - каталог изменился
MessageBeep(MB_OK); // звуковой сигнал для привлечения внимания
FORWARD_MY_REFRESH(hwnd, PostMessage); // снова загрузить список
FindNextChangeNotification(hWait); // и ждать дальше ...}}}
LONG WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{switch (uMsg) {
HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);
HANDLE_MSG(hWnd, WM_SIZE, Cls_OnSize);
HANDLE_MSG(hWnd, WM_SETFOCUS, Cls_OnSetFocus);
HANDLE_MSG(hWnd, MY_REFRESH, Cls_OnMyRefresh);
HANDLE_MSG(hWnd, WM_TIMER, Cls_OnTimer);
default: break;}
return DefWindowProc(hWnd, uMsg, wParam, lParam);}
static BOOL init_instance(HINSTANCE hInst)
{WNDCLASS wc;
hInstance = hInst;
wc.style = 0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 8;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
return RegisterClass(&wc) == NULL ? FALSE : TRUE;}
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{UNUSED_ARG(lpszCmdLine);
MSG msg;
HWND hWnd;
int nSize;
nSize = GetCurrentDirectory(0, NULL);
pszCurDir = new char [ nSize ];
GetCurrentDirectory(nSize, pszCurDir);
if (!hPrevInst) {
if (!init_instance(hInst)) return 1;}
hWnd= CreateWindow(
szWndClass, // class name
szWndClass, // window name
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT,CW_USEDEFAULT, // window position
CW_USEDEFAULT,CW_USEDEFAULT, // window size
NULL, // parent window
NULL, // menu
hInst, // current instance
NULL // user-defined parameters);
if (!hWnd) return 1;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// инициализируем таймер так, что бы сообщения передавались каждые 2 секунды
SetTimer(hWnd, 0, 2000, (TIMERPROC)NULL);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);}
if (pszCurDir) delete pszCurDir;
return msg.wParam;}
Чтение и запись файлов
HANDLE CreateFile(lpszName, fdwAccess, fdwShareMode, lpszSA, fdwCreate, fdwAttrsAndFlags, hTemplateFile);
INVALID_HANDLE_VALUE = (HANDLE)-1 !!!!!!!!!!
BOOL CloseHandle(hFile);
BOOL ReadFile(hFile, lpvBuffer, nBytes, lpdwReaded, lpOverlapped);
BOOL WriteFile(hFile, lpvBuffer, nBytes, lpdwWritten, lpOverlapped);
DWORD SetFilePointer(hFile, lDistanceLow, lDistanceHigh, dwMethod);
BOOL SetEndOfFile(hFile);
BOOL FlushFileBuffers(hFile);
BOOL LockFile(hFile, dwOffsetLow, dwOffsetHigh, cbLockLow, cbLockHigh);
BOOL UnlockFile(hFile, dwOffsetLow, dwOffsetHigh, cbLockLow, cbLockHigh);
BOOL LockFileEx(hFile, dwFlags, dwUnused, cbLockLow, cbLockHigh, lpsOverlapped); // not in 95!
BOOL UnlockFileEx(hFile, dwFlags, dwUnused, cbLockLow, cbLockHigh, lpOverlapped); // not in 95!
BOOL GetOverlappedResult(hFile, lpOverlapped, lpcbTransfer, fWait);
DWORD WaitForSingleObject(hObject, dwTimeOut);
BOOL ReadFileEx(hFile, lpvBuffer, nBytes, lpOverlapped, lpfnCompletionFunc);
BOOL WriteFileEx(hFile, lpvBuffer, nBytes, lpOverlapped, lpfnCompletionFunc);
1 В большинстве случаев используются полные имена файлов, содержащие имена каталогов и, возможно, точки, входящие в имя каталога, а не только разделяющие имя файла и его расширение.
2 Подробнее о работе с окнами смотри в соответствующих разделах.
3 Реально тестирование проводилось еще и на третьем компьютере с системой Windows–98, однако ее поведение ничем не отличалось от Windows–95, за исключением уже отмеченного нюанса в реализации функций DefineDosDevice и QueryDosDevice.
4 Такое поведение типично для MSCDEX.EXE, который обеспечивает доступ к дискам с CDFS в среде MS–DOS; при отсутствии диска MSCDEX часто использует данные, оставшиеся в кэше от предыдущего диска, может быть даже от того, который был раньше вставлен, либо вообще от другого. Это, кстати, может приводить к ошибке — при попытке проверить наличие какого–либо конкретного файла на томе с CDFS вы можете получить положительный ответ, даже если том вообще отсутствует в приводе! При проверке наличия файла на CD–ROM лучше не просто проверять его наличие, а открывать и выполнять чтение небольшого фрагмента, что бы убедиться в действительном присутствии файла.
5 Аналогичная группа функций существует и для получения информации о принтерах (FindFirstPrinterChangeNotification, FindNextPrinterChangeNotification и FindClosePrinterChangeNotification), только она реализована исключительно для Windows NT. Согласно описанию для Windows–95 или Windows 3.x с Win32s эти функции не доступны.
6 То есть окном, чья процедура построена на функции DefWindowProc. Другие окна, как, скажем, редакторы, кнопки, списки, диалоги и прочее используют сообщения с номерами, большими WM_USER. Однако такие сообщения они только получают сами, но главное окно приложения их не получит.