Работа с файлами, каталогами и томами в 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 содержит неожиданно много таких ошибок, причем как в библиотеках времени выполнения, так и в самой среде.

Еще один нюанс, правда сравнительно небольшой, связан с тем, что символ “.” (точка) может встречаться не только для отделения имени от расширения, но также и в самом имени, причем неоднократно. В таких случаях под расширением подразумевается часть имени, отделенная самой правой точкой. Эта особенность сравнительно редко приводит к ошибкам, так как при разборе имени его обычно анализируют справа налево[i].

Помимо этого следует учесть, что в именах файлов могут встречаться две косые черты подряд. Так, например, открытие файла с именем “\\.\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. Однако о корректной работе функций говорить к сожалению не приходиться... Подробнее — в приводимом ниже примере.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-06-03 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: