Операционная система Microsoft Windows NT имеет очень развитые средства работы с файлами. Она способна выполнять операции над файлами, расположенными в нескольких файловых системах, таких как FAT XE "FAT" (старая добрая файловая система MS-DOS), HPFS XE "HPFS" (файловая система, созданная операционной системой IBM OS/2), NTFS XE "NTFS" (файловая система Microsoft Windows NT) и CDFS XE "CDFS" (еще одна файловая система Microsoft Windows NT, предназначенная для доступа к накопителю CD-ROM). При необходимости можно разработать собственную файловую систему и подключить ее к Microsoft Windows NT.
Файловая система FAT используется операционной системой Microsoft Windows NT для записи файлов на дискеты или на жесткий диск. Заметим, что в среде этой операционной системы вы можете отфоматировать дискеты только в формате FAT.
С помощью команды FORMAT или приложения Disk Administrator (пиктограмма которого находится в группе Administrative Tools) вы сможете отформатировать разделы жесткого диска для работы с файловыми системами FAT или NTFS.
Что же касается файловой системы HPFS, то возможность работы с ней в среде Microsoft Windows NT оставлена исключительно для совместимости с операционной системой IBM OS/2. В среде Microsoft Windows NT вы не сможете создать новый раздел HPFS. Однако если по каким-либо причинам нежелательно удалять существующий раздел в этом формате (например, вы работаете попеременно с операционными системами Microsoft Windows NT и IBM OS/2), то в среде Microsoft Windows NT вы будете иметь доступ к файлам, расположенным в разделе HPFS. Кстати, операционная система IBM OS/2 Warp версии 3.0 не предоставляет доступ к разделам NTFS.
В этой главе мы расскажем вам о преимуществах файловой системы NTFS над другими перечисленными выше файловыми системами и кратко опишем наиболее важные функции программного интерфейса операционной системы Microsoft Windows NT, предназначенные для работы с файлами. Вопросы использования файлов, отображаемых в виртуальную память, мы отложим до следующего тома “Библиотеки системного программиста”, посвященного Microsoft Windows NT. Там же вы найдете исходные тексты приложений, работающих с файлами.
Прежде чем мы перечислим достоинства файловой системы NTFS, сделаем краткий обзор недостатков других файловых систем, созданных для различных операционныъх систем персональных компьютеров.
Все вы хорошо знаете недостатки файловой системы FAT, разработанной на заре развития операционной системы MS-DOS. Однако в те времена жесткие диски персональных компьютеров имели объем 10 - 40 Мбайт, и файловая система FAT в целом неплохо подходила для работы с такими дисками и дискетами.
Один из недостатков файловой системы FAT, наиболее очевидный для пользователей, заключается в жестких ограничениях на имена файлов и каталогов. Имя должно состоять не более чем из 8 символов, плюс еще три символа расширения имени. Так как расширение имени всегда используется для обозначения типа документа (например, txt - текстовый файл, doc - файл документа Microsoft Word), пользователь был вынужден изобретать восьмисимвольные имена, отражающие содержимое файла. Если пользователь работает с десятками или сотнями документов (что совсем не редкость), ему нужно иметь незаурядную фантазию, чтобы суметь придумать для всех документов осмысленные имена.
Другой, менее очевидный недостаток, заключается в том что информация о каждом файле хранится в нескольких удаленных друг от друга местах диска. Для того чтобы прочесть файл, операционная система должна сначала найти его имя и номер первого распределенного файлу кластера в каталоге, затем ей следует обратиться к таблице размещения файлов FAT, чтобы получить список кластеров, распределенных файлу, и, наконец, прочитать кластеры, которые находятся совсем в другом месте диска.
Если с диском работает только одна программа, накладные расходы на перемещения блока магнитных головок между этими областями диска, возможно, не приведут к заметному снижению производительности (особенно при использования кэширования диска). Однако в мультизадачной среде, когда несколько программ пытаются получить доступ одновременно к нескольким разным файлам, указанные накладные расходы будут заметнее.
У файловой системы FAT есть еще одна большая проблема - увеличение фрагментации диска и файлов при интенсивной работе с файлами. Фрагментация приводит к тому, что файл как бы размазывается по диску. Для чтения такого файла нужно много времени, так как перемещение головок выполняется относительно медленно. Поэтому пользователям операционной системы очень хорошо знакомы утилиты дефрагментации, такие как Microsoft Defrag XE "Microsoft Defrag" и Norton Speedisk XE "Norton Speedisk".
Файловой системе FAT и средствам работы с диском на разных уровнях в среде операционной системы MS-DOS мы посвятили 19 том “Библиотеки системного программиста”, который называется “MS-DOS для программиста. Часть вторая”. Вы найдете в этой книге подробное изложение принципов построения файловой системы FAT.
Операционная система Microsoft Windows версии 3.1 и более ранних версий не внесла ничего нового в теорию и практику файловых систем, ограничившись использованием все той же системы FAT. Более того, для доступа к файлам процессор переключался из защищенного в реальный режим или в режим виртуального процессора 8086, а затем вызывалось прерывание MS-DOS с номером 21h.
Постоянные переключения режимов работы процессора приводили к снижению производительности. И хотя для подавляющего большинства дисковых контроллеров IDE можно было включить режим 32-разрядного доступа к диску, исключающий вызов модулей BIOS XE "BIOS" (работающих только в реальном режиме), заметного влияния на быстродействие системы этот режим не оказывал. К тому же, для большинства контроллеров SCSI XE "SCSI" 32-разрядный доступ вообще нельзя было использовать.
В программном интерфейсе операционной системы Microsoft Windows версии 3.1 были предусмотрены несколько функций для работы с файлами. Мы описали их в 113 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть третья”. Это такие функции, как OpenFile XE "OpenFile", _lopen XE "_lopen", _lclose XE "_lclose", _lcreat XE "_lcreat", _lread XE "_lread", _lwrite XE "_lwrite", _hread XE "_hread", _hwrite XE "_hwrite", _llseek XE "_llseek".
Все перечисленные выше функции, а также функции стандартной библиотеки времени выполнения танслятора Microsoft Visual C++ можно использовать в приложениях Microsoft Windows NT. Это сделано для обеспечения совместимости на уровне исходных текстов. Однако лучше работать с новыми функциями программного интерфейса Microsoft Windows NT, обладающими намного более широкими возможностями.
Известная своими средствами работы в сети операционная система Microsoft Windows for Workgroups версии 3.11 имела одно усовершенствование, заметно увеличивающее скорость работы с файлами. Эта операционная система позволяла устанавливать режим 32-разрядного доступа не только к диску, но и к файлам, полностью исключая необходимость переключения процессора из защищенного режима работы в режим виртуального процессора 8086. И хотя по-прежнему 32-разрядный драйвер диска, поставляющийся в составе Microsoft Windows for Workgroups версии 3.11, мог работать далеко не со всеми дисковыми контроллерами, многие фирмы, изготавляющие такие контроллеры, продавали 32-разрядные драйверы, позволяющие воспользоваться преимуществами доступа к диску и файлам в 32-разрядном режиме.
Однако доступ к дискам и файлам в защищенном режиме работы процессора - это все, что изменилось в файловой системе. Операционная система Microsoft Windows for Workgroups, так же как и предыдущие версии Microsoft Windows, работает с файловой системой FAT.
При разработке операционной системы Microsoft Windows 95 была создана новая модификация файловой системы, которая получила название VFAT XE "VFAT". Ее также называют файловой системой с таблицей размещения файлов защищенного режима - Protected mode FAT.
Несмотря на свое название, в файловой системе VFAT используется самый обычный формат таблицы размещения файлов. Этого, однако, не скажешь о формате дескрипторов файлов, расположенных в каталогах. В этот формат были внесены добавления, позволяющие пользователям указывать имена файлов и каталогов размером до 260 символов.
Каким образом это было достигнуто?
Для каждого файла или каталога, имеющего длинное имя, было задействовано несколько дополнительных дескрипторов, расположенных рядом. При этом имя (наряду с другими дополнительными атрибутами) было разбито на несколько частей и хранилось в нескольких дополнительных дескрипторах.
Однако самое интересное, что при создании файловой системы VFAT была обеспечена совместимость со старыми программами MS-DOS, которые могли работать только с короткими именами “в стандарте 8.3”. Для этого во всех дополнительных дескрипторах были установлены атрибуты “скрытый”, “системный”, “метка тома”, благодаря чему программы MS-DOS пропускали такие дескрипторы, не обращая на них внимания. В то же время для каждого файла или каталога с длинным именем файловая система VFAT создавала дескриптор специального вида, содержащий альтернативное (алиасное) имя. Альтернативное имя состоит из первых шести символов длинного имени, из которого убраны пробелы, символа “тильда” (~) и числа. Например, для имени The Mircosoft Network создается альтернативное имя THEMIC~1.
В результате пользователи могли работать с длинными именами как в приложениях Microsoft Windows 95, так и в программах MS-DOS (хотя, конечно, альтернативное имя в ряде случаев может мало напоминать исходное длинное имя).
Разработчики операционной системы Microsoft Windows 95 добивались максимальной совместимости с приложениями Microsoft Windows 3.1 и программами MS-DOS. Поэтому для работы с дисковыми устройствами (особенно экзотическими) в ряде случаев можно использовать драйверы реального режима, загружаемые с помощью файла config.sys. Это очень удобно, так как вы можете пользоваться устройством, для которого в составе Microsoft Windows 95 пока нет специального драйвера. Однако при использовании драйверов реального режима в процессе обращения к устройству происходит переключение процессора из защищенного режима в режим виртуального процессора 8086, что снижает производительность системы.
Высокопроизводительная файловая система HPFS (High Performance File System), использованная в операционной системе IBM OS/2, лишена большинства недостатков файловой системы FAT.
Эта файловая система оптимизирована для мультизадачной среды и ускоряет одновременную работу программ с файлами, расположенными на дисках большого объема.
Специальный алгоритм размещения файлов значительно уменьшает вредное влияние фрагментации файлов, уменьшающей общую производительность системы. При размещении файла для него подбирается подходящий непрерывный свободный участок диска и оставляется некоторый запас свободного пространства. Когда файл расширяется, для него выделяются в первую очередь секторы, относящиеся к зарезервированному для этого файла участку диска. Если же размер файла увеличился значительно, для него может быть выделен еще один или несколько свободных участков.
При использовании HPFS пользователь может указывать имена файлов размером до 254 символов, причем имя может состоять из заглавных и прописных букв, а также пробелов и некоторых других символов, например, символов “.” (в произвольном количестве).
В дополнение к таким атрибутам файлов, как “только читаемый”, “скрытый”, “системный” и “архивированный”, IBM OS/2 хранит для каждого файла набор расширенных атрибутов. Это тип файла, комментарий и ключевые слова для поиска, пиктограмма для визуального представления файла и т. д.
В распоряжении программиста имеются многочисленные функции программного интерфейса IBM OS/2, с помощью которых можно выполнять операции с файлами, в том числе и многозадачные. Например, можно запустить операцию чтения или записи фрагмента файла как отдельную задачу, которая будет выполняться автономно от запустившей ее задачи. Есть средства и для работы с расширенными атрибутами файлов, для создания, удаления и переименования файлов и каталогов, а также другие необходимые функции.
Заметим, что файловая система HPFS не содержит никаких средств разграничения доступа. Однако если она используется совместно с файл-сервером IBM Lan Server XE "IBM Lan Server", в операционную систему IBM OS/2 добавляется специальный драйвер, обеспечивающий такое разграничение. XE "HPFS"
Другое ограничение файловой системы HPFS заключается в том, что виртуальным машинам DOS, работающим под управлением IBM OS/2, недоступны каталоги и файлы с длинными именами. Причина заключается в том, что в этой операционной системе не предусмотрено никакого механизма, обеспечивающего генерацию коротких альтернативных имен, как это сделано в операционных системах Microsoft Windows 95 и Microsoft Windows NT.
Подробнее об операционной системе IBM OS/2 вы можете прочитать в 20 томе “Библиотеки системного программиста”, который называется “Операционная система IBM OS/2 Warp” и в 25 томе этой же серии с названием “Программирование для операционной системы IBM OS/2 Warp”.
Теперь, после такого краткого обзора наиболее популярных файловых систем, мы может сказать, что файловая система NTFS вобрала в себя самое лучшее из всего, что было создано к настоящему моменту в этой области. Обладая практически такой же высокой производительностью, как файловая система FAT, файловая система NTFS допускает использование длинных имен, имеет намного более высокую надежность и средства разграничения доступа, мощные средства поиска файлов в каталогах, основанные на использовании B-деревьев, и не требует выполнения периодической дефрагментации диска.
Что касается имен файлов, то в файловой системе NTFS допускается указывать имена размером до 255 символов в кодировке UNICODE XE "UNICODE", когда каждый символ представляется двумя байтами. Кодировку UNICODE мы рассмотрим подробнее в одном из следующих томов “Библиотеки системного программиста”, посвященного операционной системе Microsoft Windows NT. Сейчас мы только скажем, что она позволяет сохранить имена файлов при их копировании в системы, использующие другие национальные языки.
В именах файлов и каталогов можно использовать строчные либо прописные буквы, при этом файловая система не делает между ними различий. В итоге имена MyFile, MYFILE и myfile означают одно и то же. В режиме совместимости со стандартом POSIX, однако, операционная система Microsoft Windows NT будет считать все перечисленные выше имена разными. Рассмотрение стандарта POSIX, предназначенного для совместимости с UNIX-программами XE "UNIX-программы" (точнее говоря, приложения UNIX, отвечающие стандарту POSIX XE "POSIX", будет легче переносить на платформу Microsoft Windows NT), выходит за рамки нашей книги.
Использование B-деревьев при поиске имен в каталогах вместо обычного последовательного пребора значительно сокращает время открывания файлов, особенно если каталог содержит очень много файлов (последнее не редкость, так как современные приложения состоят из десятков, если не сотен файлов).
Стоит специально отметить, что в отличие от HPFS, файловая система NTFS содержит специальные средства, предназначенные для повышения устойчивости к различным аварийным ситуациям, таким, например, как внезапное отключение электропитания или аварийный останов операционной системы. Эти срества используют логику обработки транзакций, в результате чего операции, которые в результате аварии не успели завершиться, будут отменены с восстановлением файловой системы в исходное состояние.
Файловая система NTFS практически не имеет ограничений на макисмальный размер файла. Так, если файловые системы FAT и HPFS позовляет создавать файлы размером не более 232 байта, файловая система NTFS может работать с файлами размером до 264 байта. Максимальный размер пути, который в FAT составлял 64 байта, в NTFS не ограничен.
Для совместимости с программами MS-DOS, запущенными под управлением операционной системы Microsoft Windows NT, файловая система NTFS для всех файлов и каталогов создает короткие альтернативные имена, аналогично тому, как это делает операционная система Microsoft Windows 95. Если операционная система Microsoft Windows NT Server используется в качестве файл-сервера сети, то альтернативные имена позволяют получить доступ ко всем файлам и каталогам сервера из рабочих станций MS-DOS, неспособных работать с длинными именами напрямую.
Ко всему прочему добавим, что файловая система NTFS в операционной системе Microsoft Windows NT версии 3.51 позволяет выполнять динамическую компрессию файлов, аналогично тому как это делает драйвер DriveSpace или Stacker в операционной системе MS-DOS.
В этом разделе мы рассмотрим основные функции программного интерфейса операционной системы Microsoft Windows NT, предназначенные для работы с файлами и каталогами.
Можно было бы подумать, что функция CreateFile предназначена только для создания файлов, аналогично функции _lcreat XE "_lcreat" из программного интерфейса Microsoft Windows версии 3.1, однако это не так. Во-первых, с помощью функции CreateFile можно выполнять не только создание нового файла, но и открывание существующего файла или каталога, а также изменение длины существующего файла. Во-вторых, эта функция может выполнять операции не только над файлами, но и над каналами передачи данных, трубами XE "трубы" (pipe XE "pipe" ), дисковыми устройствами и консолями. В этом томе мы, однако, ограничимся лишь файлами.
Прототип функции CreateFile мы привели ниже:
HANDLE CreateFile( LPCTSTR lpFileName, // адрес строки имени файла DWORD dwDesiredAccess, // режим доступа DWORD dwShareMode, // режим совместного использования файла LPSECURITY_ATTRIBUTES lpSecurityAttributes, // дескриптор // защиты DWORD dwCreationDistribution, // параметры создания DWORD dwFlagsAndAttributes, // атрибуты файла HANDLE hTemplateFile); // идентификатор файла с атрибутами
Через параметр lpFileName вы должны передать этой функции адрес строки, содержащей имя файла, который вы собираетесь создать или открыть. Строка должна быть закрыта двоичным нулем.
Параметр dwDesiredAccess определяет тип доступа, который должен быть предоставлен к открываемому файлу. Здесь вы можете использовать логическую комбинацию следующих констант:
Константа |
Описание |
0 |
Доступ запрещен, однако приложение может определять атрибуты файла или устройства, открываемого при помощи функции CreateFile XE "CreateFile" |
GENERIC_READ |
Разрешен доступ на чтение |
GENERIC_WRITE |
Разрешен доступ на запись |
С помощью параметра dwShareMode задаются режимы совместного использования открываемого или создаваемого файла. Для этого параметра вы можете указать комбинацию следующих констант:
Константа |
Описание |
0 |
Совместное использование файла запрещено |
FILE_SHARE_READ |
Другие приложения могут открывать файл с помощью функции CreateFile XE "CreateFile" для чтения |
FILE_SHARE_WRITE |
Аналогично предыдущему, но на запись |
Через параметр lpSecurityAttributes необходимо передать указатель на дескриптор защиты или значение NULL, если этот дескриптор не используется. В наших приложениях мы не работаем с дескриптором защиты.
Параметр dwCreationDistribution определяет действия, выполняемые функцией CreateFile, если приложение пытается создать файл, который уже существует. Для этого параметра вы можете указать одну из следующих констант:
Константа |
Описание |
CREATE_NEW |
Если создаваемый файл уже существует, функция CreateFile возвращает код ошибки |
CREATE_ALWAYS |
Существующий файл перезаписывается, при этом содержимое старого файла теряется |
OPEN_EXISTING |
Открывается существующий файл. Если файл с указанным именем не существует, функция CreateFile XE "CreateFile" возвращает код ошибки |
OPEN_ALWAYS |
Если указанный файл существует, он открывается. Если файл не существует, он будет создан |
TRUNCATE_EXISTING |
Если файл существует, он открывается, после чего длина файла устанавливается равной нулю. Содержимое старого файла теряется. Если же файл не существует, функция CreateFile XE "CreateFile" возвращает код ошибки |
Параметр dwFlagsAndAttributes задает атрибуты и флаги для файла.
При этом можно использовать любые логические комбинации следующих атрибутов (кроме атрибута FILE_ATTRIBUTE_NORMAL, который можно использовать только отдельно):
Атрибут |
Описание |
FILE_ATTRIBUTE_ARCHIVE |
Файл был архивирован (выгружен) |
FILE_ATTRIBUTE_COMPRESSED |
Файл, имеющий этот атрибут, динамически сжимается при записи и восстанавливается при чтении. Если этот атрибут имеет каталог, то для всех расположенных в нем файлов и каталогов также выполняется динамическое сжатие данных |
FILE_ATTRIBUTE_NORMAL |
Остальные перечисленные в этом списка атрибуты не установлены |
FILE_ATTRIBUTE_HIDDEN |
Скрытый файл |
FILE_ATTRIBUTE_READONLY |
Файл можно только читать |
FILE_ATTRIBUTE_SYSTEM |
Файл является частью операционной системы |
В дополнение к перечисленным выше атрибутам, через параметр dwFlagsAndAttributes вы можете передать любую логическую комбинацию флагов, перечисленных ниже:
Флаг |
Описание |
FILE_FLAG_WRITE_THROUGH |
Отмена промежуточного кэширования данных для уменьшения вероятности потери данных при аварии |
FILE_FLAG_NO_BUFFERING |
Отмена промежуточной буферизации или кэширования. При использовании этого флага необходимо выполнять чтение и запись порциями, кратными размеру сектора (обычно 512 байт) |
FILE_FLAG_OVERLAPPED |
Выполнение чтения и записи асинхронно. Во время асинхронного чтения или записи приложение может продолжать обработку данных |
FILE_FLAG_RANDOM_ACCESS |
Указывает, что к файлу будет выполняться произвольный доступ. Флаг предназначен для оптимизации кэширования |
FILE_FLAG_SEQUENTIAL_SCAN |
Указывает, что к файлу будет выполняться последовательный доступ от начала файла к его концу. Флаг предназначен для оптимизации кэширования |
FILE_FLAG_DELETE_ON_CLOSE |
Файл будет удален сразу после того как приложение закроет его идентификтор. Этот флаг удобно использовать для временных файлов |
FILE_FLAG_BACKUP_SEMANTICS |
Файл будет использован для выполнения операции выгрузки или восстановления. При этом выполняется проверка прав доступа |
FILE_FLAG_POSIX_SEMANTICS |
Доступ к файлу будет выполняться в соответствии со спецификацией POSIX |
И, наконец, последний параметр hTemplateFile предназначен для доступа к файлу шаблона с расширенными атрибутами для создаваемого файла. Этот параметр мы рассматривать не будем для экономии места. При необходимости вы найдете всю информацию по этому вопросу в документации, поставляемой вместе с SDK.
В случае успешного завершения функция CreateFile возвращает идентификатор созданного или открытого файла (или каталога). При ошибке возвращается значение INVALID_HANDLE_VALUE XE "INVALID_HANDLE_VALUE" (а не NULL, как можно было бы предположить). Код ошибки можно определить при помощи функции GetLastError XE "GetLastError".
В том случае, если файл уже существует и были указаны константы CREATE_ALWAYS или OPEN_ALWAYS, функция CreateFile XE "CreateFile" не возвращает код ошибки. В то же время в этой ситуации функция GetLastError XE "GetLastError" возвращает значение ERROR_ALREADY_EXISTS XE "ERROR_ALREADY_EXISTS".
Функция CloseHandle позволяет закрыть файл. Она имеет единственный параметр - идентификатор закрываемого файла. Заметим, что если мы указали функции CreateFile XE "CreateFile" флаг FILE_FLAG_DELETE_ON_CLOSE, сразу после закрывания файл будет удален. Как мы уже говорили, такая методика очень удобна при работе со временными файлами.
С помощью функций ReadFile и WriteFile приложение может выполнять, соответственно, чтение из файла и запись в файл. По своему назначению эти функции аналогичны функциям _lread XE "_lread", _lwrite XE "_lwrite", _hread XE "_hread" и _hwrite XE "_hwrite" из программного интерфейса Microsoft Windows версии 3.1.
Приведем прототипы функций ReadFile и WriteFile:
BOOL ReadFile( HANDLE hFile, // идентификатор файла LPVOID lpBuffer, // адрес буфера для данных DWORD nNumberOfBytesToRead, // количество байт, которые // необходимо прочесть в буфер LPDWORD lpNumberOfBytesRead, // адрес слова, в которое // будет записано количество прочитанных байт LPOVERLAPPED lpOverlapped); // адрес структуры типа // OVERLAPPED BOOL WriteFile( HANDLE hFile, // идентификатор файла LPVOID lpBuffer, // адрес записываемого блока данных DWORD nNumberOfBytesToWrite, // количество байт, которые // необходимо записать LPDWORD lpNumberOfBytesWrite, // адрес слова, в котором // будет сохранено количество записанных байт LPOVERLAPPED lpOverlapped); // адрес структуры типа // OVERLAPPED
Через параметр hFile этим функциям необходимо передать идентификатор файла, полученный от функции CreateFile XE "CreateFile".
Параметр lpBuffer должен содержать адрес буфера, в котором будут сохранены прочитанные данные (для функции ReadFile XE "ReadFile" ), или из которого будет выполняться запись данных (для функции WriteFile XE "WriteFile" ).
Параметр nNumberOfBytesToRead используется для функции ReadFile и задает количество байт данных, которые должны быть прочитаны в буфер lpBuffer. Аналогично, параметр nNumberOfBytesToWrite задает функции WriteFile XE "WriteFile" размер блока данных, имеющего адрес lpBuffer, который должен быть записан в файл.
Так как в процессе чтения возможно возникновение ошибки или достижение конца файла, количество прочитанных или записанный байт может отличаться от значений, заданных, соответственно, параметрами nNumberOfBytesToRead и nNumberOfBytesToWrite. Функции ReadFile XE "ReadFile" и WriteFile XE "WriteFile" записывают количество действительно прочитанных или записанных байт в двойное слово с адресом, соответственно, lpNumberOfBytesRead и lpNumberOfBytesWrite.
Параметр lpOverlapped используется в функциях ReadFile и WriteFile для организации аснхронного режима чтения и записи. Если запись выполняется синхронно, в качестве этого параметра следует указать значение NULL. Способы выполнения асинхронного чтения и записи мы рассмотрим позже. Заметим только, что для использования асинхронного режима файл должен быть открыт функцией CreateFile XE "CreateFile" с использованием флага FILE_FLAG_OVERLAPPED. Если указан этот флаг, параметр lpOverlapped не может иметь значение NULL. Он обязательно должен содержать адрес подготовленной структуры типа OVERLAPPED.
Если функции ReadFile и WriteFile были выполнены успешно, они возвращают значение TRUE. При возникновении ошибки возвращается значение FALSE. В последнем случае вы можете получить код ошибки, вызвав функцию GetLastError XE "GetLastError".
В процессе чтения может быть достигнут конец файла. При этом количество действительно прочитанных байт (записывается по адресу lpNumberOfBytesRead) будет равно нулю. В случае достижения конца файла при чтении ошибка не возникает, поэтому функция ReadFile XE "ReadFile" вернет значение TRUE.
Так как ввод и вывод данных на диск в операционной системе Microsoft Windows NT буферизуется, запись данных на диск может быть отложена до тех пор, пока система не освободится от выполнения текущей работы. С помощью функции FlushFileBuffers XE "FlushFileBuffers" вы можете принудительно заставить операционную систему записать на диск все изменения для файла, идентификатор которого передается этой функции через единственный параметр:
BOOL FlushFileBuffers(HANDLE hFile);
В случае успешного завершения функция возвращает значение TRUE, при ошибке - FALSE. Код ошибки вы можете получить при помощи функции GetLastError XE "GetLastError".
Заметим, что при закрывании файла функцией CloseHandle содержимое всех буферов, связанных с этим файлом, записывается на диск автоматически. Поэтому вы должны использовать функцию FlushFileBuffers XE "FlushFileBuffers" только в том случае, если запись содержимого буферов нужно выполнить до закрывания файла.
С помощью функции SetFilePointer приложение может выполнять прямой доступ к файлу, перемещая указатель текущей позиции, связанный с файлом. Сразу после открывания файла этот указатель устанавливается в начало файла. Затем он передвигается функциями ReadFile XE "ReadFile" и WriteFile XE "WriteFile" на количество прочитанных или записанных байт, соответственно.
Функция SetFilePointer позволяет выполнить установку текущей позиции:
DWORD SetFilePointer( HANDLE hFile, // идентификатор файла LONG lDistanceToMove, // количество байт, на которое будет // передвинута текущая позиция PLONG lpDistanceToMoveHigh, // адрес старшего слова, // содержащего расстояние для перемещения позиции DWORD dwMoveMethod); // способ перемещения позиции
Через параметр hFile вы должны передать этой функции идентификатор файла, для которого выполняется изменение текущей позиции.
Параметр lDistanceToMove, определяющий дистанцию, на которую будет передвинута текущая позиция, может принимать как положительные, так и отрицательные значения. В первом случае текущая позиция переместится по направлению к концу файла, во втором - к началу файла.
Если вы работаете с файлами, размер которых не превышает 232 - 2 байта, для параметра lpDistanceToMoveHigh можно указать значение NULL. В том случае, когда ваш файл очень большой, для указания смещения может потребоваться 64-разрядное значение. Для того чтобы указать очень большое смещение, вы должны записать старшее 32-разрядное слово этого 64-разрядного значения в переменную, и передать функции SetFilePointer XE "SetFilePointer" адрес этой переменной через параметр lpDistanceToMoveHigh. Младшее слово смещения следует передавать как и раньше, через параметр lDistanceToMove.
Параметр dwMoveMethod определяет способ изменения текущей позиции и может принимать одно из перечисленных ниже значений:
Значение |
Описание |
FILE_BEGIN |
Смещение отсчитывается от начала файла, при этом значение смещения трактуется как беззнаковая величина |
FILE_CURRENT |
Смещение отсчитывается от текущей позиции в файле и может принимать как положительные, так и отрицательные значения |
FILE_END |
Смещение отсчитывается от конца файла и трактуется как отрицательная величина |
В случае успешного завершения функция SetFilePointer возвращает младшее слово новой 64-разрядной позиции в файле. Старшее слово при этом записывается по адресу, заданному параметром lpDistanceToMoveHigh.
При ошибке функция возвращает значение 0xFFFFFFFF. При этом в слово по адресу lpDistanceToMoveHigh записывается значение NULL. Код ошибки вы можете получить при помощи функции GetLastError XE "GetLastError".
При необходимости изменить длину файла (уменьшить или увеличить), вы можете воспользоваться функцией SetEndOfFile XE "SetEndOfFile". Эта функция устанавливает новую длину файла в соответствии с текущей позицией:
BOOL SetEndOfFile(HANDLE hFile);
Для изменения длины файла вам достаточно установить текущую позицию в нужное место с помощью функции SetFilePointer XE "SetFilePointer", а затем вызвать функцию SetEndOfFile XE "SetEndOfFile".
Так как операционная система Microsoft Windows NT является мультизадачной и допускает одновременную работу многих процессов, возможно возникновение ситуаций, в которых несколько задач попытаются выполнять запись или чтение для одних и тех же файлов. Например, два процесса могут попытаться изменить одни и те же записи файла базы данных, при этом третий процесс будет в то же самое время выполнять выборку этой записи.
Напомним, что если функции CreateFile указать режимы совместного использования файла FILE_SHARE_READ XE "FILE_SHARE_READ" или FILE_SHARE_WRITE XE "FILE_SHARE_WRITE", несколько процессов смогут одновременно открыть файлы и выполнять операции чтения и записи, соответственно. Если же эти режимы не указаны, совместное использование файлов будет невозможно. Первый же процесс, открывший файл, заблокирует возможность работы с этим файлом для других процессов.
Очевидно, что в ряде случаев вам все же необходимо обеспечить возможность одновременной работы нескольких процессов с одним и тем же файлом. В этом случае при необходимости процессы могут блокировать доступ к отдельным фрагментам файлов для других процессов. Например, процесс, изменяющий запись в базе данных, перед выполнением изменения может заблокировать участок файла, содержащий эту запись, и затем после выполнения записи разблокировать его. Другие процессы не смогут выполнить запись или чтение для заблокированных участков файла.
Блокировка участка файла выполняется функцией LockFile, прототип которой представлен ниже:
BOOL LockFile( HANDLE hFile, // идентификатор файла DWORD dwFileOffsetLow, // младшее слово смещения области DWORD dwFileOffsetHigh, // старшее слово смещения области DWORD nNumberOfBytesToLockLow, // младшее слово длины // области DWORD nNumberOfBytesToLockHigh); // старшее слово длины // области
Параметр hFile задает идентификатор файла, для которого выполняется блокировка области.
Смещение блокируемой области (64-разрядное) задается при помощи параметров dwFileOffsetLow (младшее слово) и dwFileOffsetHigh (старшее слово). Размер области в байтах задается параметрами nNumberOfBytesToLockLow (младшее слово) и nNumberOfBytesToLockHigh (старшее слово). Заметим, что если в файле блокируется несколько областей, они не должны перекрывать друг друга.
В случае успешного завершения функция LockFile возвращает значение TRUE, при ошибке - FALSE. Код ошибки вы можете получить при помощи функции GetLastError XE "GetLastError".
После использования заблокированной области, а также перед завершением своей работы процессы должны разблокировать все заблокированные ранее области, вызвав для этого функцию UnlockFile XE "UnlockFile" :
BOOL UnlockFile( HANDLE hFile, // идентификатор файла DWORD dwFileOffsetLow, // младшее слово смещения области DWORD dwFileOffsetHigh, // старшее слово смещения области DWORD nNumberOfBytesToUnlockLow, // младшее слово длины // области DWORD nNumberOfBytesToUnlockHigh); // старшее слово длины // области
Заметим, что в программном интерфейсе операционной системы Microsoft Windows NT есть еще две функции, предназначенные для блокирования и разблокирования областей файлов. Эти функции имеют имена, соответственно, LockFile XE "LockFile" Ex и UnlockFile XE "UnlockFile" Ex.
Главное отличие функции LockFile Ex от функции LockFile заключается в том, что она может выполнять частичную блокировку файла, например, только блокировку от записи. В этом случае другие процессы могут выполнять чтение заблокированной области.
Для экономии места мы не будем описывать эти функции в нашей книге. Всю необходимую информацию вы найдете в документации, которая поставляется вместе с SDK.
В этом разделе мы рассмотрим функции программного интерфейса Microsoft Windows NT, с помощью которых можно определить и изменить различные атрибуты файлов.
Размер файла определить очень просто - достаточно вызвать функцию GetFileSize, прототип которой приведен ниже:
DWORD GetFileSize( HANDLE hFile, // идентификатор файла LPDWORD lpFileSizeHigh); // адрес старшего слова для // размера файла
Функция GetFileSize возвращает младшее 32-разрядное слово 64-разрядного размера файла с идентификатором hFile. Старшее слово размера файла записывается в переменную типа DWORD, адрес которой передается функции через параметр lpFileSizeHigh.
Если функция завершилась без ошибок, вызванная вслед за ней функция GetLastError XE "GetLastError" возвращает значение NO_ERROR. Если же произошла ошибка, функция GetFileSize возвращает значение 0xFFFFFFFF. При этом в слово, адрес которого задается параметром lpFileSizeHigh, записывается значение NULL. Код ошибки можно определить при помощи все той же функции GetLastError.
Для изменения размера файла вы можете выполнить операцию записи в него или использовать описанные выше функции SetFilePointer XE "SetFilePointer" и SetEndOfFile XE "SetEndOfFile".
Так же как и MS-DOS, операционная система Microsoft Windows NT присваивает файлам при их создании различные флаги (атрибуты). Вы можете определить атрибуты файла при помощи функции GetFileAttributes XE "GetFileAttributes" :
DWORD GetFileAttributes(LPCTSTR lpFileName);
В качестве единственного параметра этой функции необходимо передать полный или частичный путь к файлу. Функция вернет слово, значение которого является логической комбинацией следующих атрибутов:
Атрибут |
Описание |
FILE_ATTRIBUTE_ARCHIVE |
Файл был архивирован (выгружен) |
FILE_ATTRIBUTE_COMPRESSED |
Файл, имеющий этот атрибут, динамически сжимается при записи и восстанавливается при чтении |
FILE_ATTRIBUTE_NORMAL |
Остальные перечисленные в этом списка атрибуты не установлены |
FILE_ATTRIBUTE_HIDDEN |
Скрытый файл |
FILE_ATTRIBUTE_READONLY |
Файл можно только читать |
FILE_ATTRIBUTE_SYSTEM |
Файл является частью операционной системы |
Для установки новых атрибутов вы можете воспользоваться функцией SetFileAttributes:
BOOL SetFileAttributes( LPCTSTR lpFileName, // адрес строки пути к файлу DWORD dwFileAttributes); // адрес слова с новыми // атрибутами
Если вам нужно изменить только один из битов слова атрибутов, необходимо вначале получить старое слово атрибутов при помощи функции GetFileAttributes, а затем, изменив в нем только нужные биты, установить новое значение слова атрибутов функцией SetFileAttributes.
Операционная система Microsoft Windows NT хранит для каждого файла отдельно дату и время его создания, дату и время момента последнего доступа к файлу, а также дату и время момента, когда последний раз выполнялась запись данных в файл.
Всю эту информацию вы можете получить для открытого файла при помощи функции GetFileTime XE "GetFileTime", прототип которой приведен ниже:
BOOL GetFileTime( HANDLE hFile, // идентификатор файла LPFILETIME lpCreationTime, // время создания LPFILETIME lpLastAccessTime, // время доступа LPFILETIME lpLastWriteTime); // время записи
Перед вызовом этой функции вы должны подготовить три структуры типа FILETIME и передать их адреса через параметры lpCreationTime, lpLastAccessTime и lpLastWriteTime. В эти структуры будет записана, соответственно, дата и время создания файла hFile, дата и время момента последнего доступа к файлу, а также дата и время момента, когда последний раз выполнялась запись данных в этот файл.
Структура FILETIME определена следующим образом:
typedef struct _FILETIME { DWORD dwLowDateTime; // младшее слово DWORD dwHighDateTime; // старшее слово } FILETIME;
Согласно документации, в структуре FILETIME хранится 64-разрядное значение даты и времени в виде количества интервалов размером 100 наносекунд от 1 января 1601 года.
Что делать с таким представлением даты и времени?
В программном интерфейсе Microsoft Windows NT предусмотрен набор функций, предназначенных для преобразования этого формата времени в более привычные нам форматы и обратно, а также для сравнения значений времени в формате структуры FILETIME.
С помощью функции FileTimeToSystemTime вы можете преобразовать дату и время из формата структуры FILETIME в более удобный для использования формат, определяемый структурой SYSTEMTIME XE "SYSTEMTIME" :
BOOL FileTimeToSystemTime( CONST FILETIME *lpFileTime, // указатель на структуру // FILETIME LPSYSTEMTIME lpSystemTime); // указатель на структуру // SYSTEMTIME
Структура SYSTEMTIME определена так:
typedef struct _SYSTEMTIME { WORD wYear; // год WORD wMonth; // месяц (1 - январь, 2 - февраль, и т. д.) WORD wDayOfWeek; // день недели // (0 - воскресение, 1 - понедельник, и т. д.) WORD wDay; // день месяца WORD wHour; // часы WORD wMinute; // минуты WORD wSecond; // секунды WORD wMilliseconds; // миллисекунды } SYSTEMTIME;
Обратное преобразование формата времени из формата структуры SYSTEMTIME в формат структуры FILETIME можно сделать при помощи функции SystemTimeToFileTime XE "SystemTimeToFileTime" :
BOOL SystemTimeToFileTime( CONST SYSTEMTIME *lpSystemTime, // указатель на структуру // SYSTEMTIME LPFILETIME lpFileTime); // указатель на структуру FILETIME
Для установки новых отметок времени файла необходимо воспользоваться функцией SetFileTime XE "SetFileTime" :
BOOL SetFileTime( HANDLE hFile, // идентификатор файла LPFILETIME lpCreationTime, // время создания LPFILETIME lpLastAccessTime, // время доступа LPFILETIME lpLastWriteTime); // время записи
Если вам нужно сравнить два значения времени в формате FILETIME, то проще всего это сделать при помощи функции CompareFileTime XE "CompareFileTime" :
LONG CompareFileTime( CONST FILETIME *lpTime1, // адрес первой структуры FILETIME CONST FILETIME *lpTime2); // адрес второй структуры FILETIME
Если времена и даты, записанные в обеих структурах, равны, функция CompareFileTime возвращает нулевое значение. Если первая дата и вермя больше второго, возвращается значение 1, если же меньше - возвращается отрицательное значение -1.
Для вас могут также представлять интерес еще две функции, выполняющие преобразование формата времени. Это функции FileTimeToDosDateTime XE "FileTimeToDosDateTime" и DosDateTimeToFileTime XE "DosDateTimeToFileTime", выполняющие, соответственно, преобразование даты и времени из формата структуры FILETIME в формат, принятый в операционной системы MS-DOS, и обратно. Описание этих функций при необходимости вы найдете в SDK.
С помощью функции GetFileInformationByHandle вы сможете получить разнообразную информацию об открытом файле по его идентификатору:
BOOL GetFileInformationByHandle( HANDLE hFile, // идентификатор файла LPBY_HANDLE_FILE_INFORMATION lpFileInformation ); // адрес // структуры, в которую будет записана информация о файле
Функция GetFileInformationByHandle записывает информацию о файле в структуру типа BY_HANDLE_FILE_INFORMATION XE "BY_HANDLE_FILE_INFORMATION", определенную следующим образом:
typedef struct _BY_HANDLE_FILE_INFORMATION { DWORD dwFileAttributes; // атрибуты файла FILETIME ftCreationTime; // время создания файла FILETIME ftLastAccessTime; // время доступа к файлу FILETIME ftLastWriteTime; // время записи в файл DWORD dwVolumeSerialNumber; // серийный номер тома DWORD nFileSizeHigh; // размер файла (старшее слово) DWORD nFileSizeLow; // размер файла (младшее слово) DWORD nNumberOfLinks; // количество связей файла DWORD nFileIndexHigh; // системный номер файла // (старшее слово) DWORD nFileIndexLow; // системный номер файла // (младшее слово) } BY_HANDLE_FILE_INFORMATION;
Поле nNumberOfLinks используется приложениями в стандарте POSIX.
Что же касается системного номера файла, то он отличается от идентификатора файла, полученного при помощи функции CreateFile XE "CreateFile" по смыслу и значению. Системные номера файлов являются глобальными и различаются для всех файлов, открытых в системе.
Когда вы работали с файлами в операционных системах MS-DOS или Microsoft Windows версии 3.1, то вы могли выполнять только синхронные операции с файлами. Это означает, что программа MS-DOS или 16-разрядное приложение Microsoft Windows, вызывая функции для работы с файлами, приостанавливали свою работу до тех пор, пока нужная операция (запись или чтение) не будет выполнена. Это и понятно - если приложение вызывает функцию _lread XE "_lread" или _lwrite XE "_lwrite", она не вернет управление до тех пор, пока не будет завершена, соответственно, операция чтения или записи.
Приложение, запущенное в среде мультизадачной операционной системы Microsoft Windows NT, может совместить операции ввода или вывода с выполнением другой полезной работы. Например, процессор электронных таблиц может пересчитывать одну таблицу во время загрузки другой с диска.
Пользуясь сведениями, полученными из нашей книги, вы и сами без труда сможете организовать такую обработку, например, с помощью отдельных задач, выполняющих пересчет таблиц и загрузку таблиц с диска. Задача, работающая с диском, может открыть файл таблицы (пользуясь для этого функцией CreateFile XE "CreateFile" ) и затем выполнить его синхронное чтение функцией ReadFile XE "ReadFile" или даже функцией _lread XE "_lread", если она вам больше нравится. Создавая отдельную задачу для чтения файла, вы и в самом деле сможете совместить в своем приложении выполнение файловых операций с обработкой данных.
Однако операционная система Microsoft Windows NT позволяет решить задачу совмещения файловых операций с другой работой намного проще. Для этого вы должны выполнять файловые операции асинхронно при помощи уже известных вам функций ReadFile XE "ReadFile" и WriteFile XE "WriteFile".
Как это сделать?
Прежде всего, открывая или создавая файл функцией CreateFile XE "CreateFile" вы должны указать флаг FILE_FLAG_OVERLAPPED XE "FILE_FLAG_OVERLAPPED". Далее, перед вызовом функций ReadFile XE "ReadFile" или WriteFile XE "WriteFile" вы должны подготовить структуру типа OVERLAPPED и передать ее адрес этим функциям через параметр lpOverlapped.
Структура OVERLAPPED определена следующим образом:
typedef struct _OVERLAPPED { DWORD Internal; // зарезервировано DWORD InternalHigh; // зарезервировано DWORD Offset; // младшее слово позиции в файле DWORD OffsetHigh; // старшее слово позиции в файле HANDLE hEvent; // идентификатор события, который будет // установлен в отмеченное состояние // после завершения операции } OVERLAPPED;
В этой структуре вы должны заполнить поля Offset, OffsetHigh и hEvent. Поля Internal и InternalHigh зарезервированы для использования операционной системой.
В поля Offset и OffsetHigh необходимо записать смещение в файле, относительно которого будет выполняться асинхронная операция записи или чтения. Если для представления смещения достаточно 32 разрядов, в поле OffsetHigh нужно записать значение NULL.
В поле hEvent нужно записать идентификатор созданного предварительно объекта-события, который будет использоваться для синхронизации задач. Если записать в это поле значение NULL, для синхронизации будет использован идентификатор файла. Остановимся на этом подробнее.
Когда функции ReadFile или WriteFile вызывается для выполнения синхронной операции, она возвращает управление только после того, как операция, соответственно, чтения или записи будет завершена. Если же эти функции вызываются в асинхронном режиме, они только инициируют процесс чтения или записи, сразу же возвращая управление, не дожидаясь завершения операции. Для выполнения операции в этом случае операционной системой неявно создается отдельная задача.
Такми образом, задача, вызвавшая функцию ReadFile или WriteFile в асинхронном режиме, продолжит свою работу до завершения файловой операции. Если же этой задаче (или другой задаче) требуется дождаться завершения файловой операции, необходимо использовать средства синхронизации, описанные нами в предыдущей главе.
Самый простой способ синхронизации заключается в том, что задача, вызывающая функции ReadFile или WriteFile в асинхронном режиме, записывает в поле hEvent структуры OVERLAPPED значение NULL. После вызова указанных выше функций, когда задаче нужно дождаться завершения выполнения асинхронной операции, она вызывает функцию WaitForSingleObject XE "WaitForSingleObject", передвая ей в качестве первого параметра идентификатор файла:
WaitForSingleObject(hFile, INFINITE);
Другой способ предполагает создание отдельного объекта-события, идентификатор которого записывается в поле hEvent структуры OVERLAPPED при инициализации последней. В этом случае в качестве первого параметра функции WaitForSingleObject XE "WaitForSingleObject" следует передать идентификатор этого объекта-события. Как только файловая операция будет завершена, объект-событие перейдет в отмеченное сосотояние.
Третий способ синхронизации заключается в использовании функции GetOverlappedResult XE "GetOverlappedResult". Эта функция обычно используется для проверки результата выполнения асинхронной файловой операции:
BOOL GetOverlappedResult( HANDLE hFile, // идентификатор файла LPOVERLAPPED lpOverlapped, // адрес структуры OVERLAPPED LPDWORD lpNumberOfBytesTransferred, // адрес счетчика байт BOOL bWait); // флаг ожидания
Через параметры hFile и lpOverlapped передются, соответственно, идентификатор файла, для которого выполнялась асинхронная операция, и адрес адрес структуры OVERLAPPED, подготовленной перед выполнением операции.
В переменную, адрес которой передается функции через параметр lpNumberOfBytesTransferred, записывается количество действительно прочитанных или записанных байт данных.
Параметр bWait может принимать значения TRUE или FALSE. В первом случае функция GetOverlappedResult будет дожидаться завершения выполнения операции (вот вам еще одно средство синхронизации). Если же значение параметра bWait равно FALSE, то если при вызове функции операция еще не завершилась, функция GetOverlappedResult вернет значение FALSE (признак ошибки).
При нормальном завершении (без ошибок) функция GetOverlappedResult возвращает значение TRUE.
В программном интерфейсе операционной системы Microsoft Windows NT есть еще две функции, специально предназначенные для выполнения асинхронных операций с файлами. Это функции ReadFileEx XE "ReadFileEx" и WriteFileEx XE "WriteFileEx" :
BOOL ReadFileEx( HANDLE hFile, // идентификатор файла LPVOID lpBuffer, // адрес буфера DWORD nNumberOfBytesToRead, // количество байт для чтения LPOVERLAPPED lpOverlapped, // адрес структуры OVERLAPPED LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);//адрес // функции, вызываемой после завершения операции BOOL WriteFileEx( HANDLE hFile, // идентификатор файла LPVOID lpBuffer, // адрес буфера DWORD nNumberOfBytesToWrite, // количество байт для записи LPOVERLAPPED lpOverlapped, // адрес структуры OVERLAPPED LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);//адрес // функции, вызываемой после завершения операции
Этим функциям через параметр hFile необходимо передать идентификатор файла, созданного или открытого функцией CreateFile XE "CreateFile" c bcgjkmpjdfybtv флагf FILE_FLAG_OVERLAPPED XE "FILE_FLAG_OVERLAPPED".
Через параметр lpBuffer передается адрес буфера, который будет использоваться функциями ReadFile Ex и WriteFile Ex, соответственно, для чтения и записи. При этом параметр nNumberOfBytesToRead определяет количество байт, которые будут прочитаны функцией ReadFileEx, а параметр nNumberOfBytesToWrite - количество байт, которые будут записаны функцией WriteFileEx.
Назначение параметра lpOverlapped аналогично назначению этого же параметра в функциях ReadFile и WriteFile.
Через параметр lpCompletionRoutine вы должны передать обеим функциям адрес функции завершения, которая будет вызвана после выполнения операции. Прототип такой функции приведен ниже (имя функции может быть любым):
VOID WINAPI CompletionRoutine( DWORD dwErrorCode, // код завершения DWORD dwNumberOfBytesTransfered, // количество прочитанных // или записанных байт данных LPOVERLAPPED lpOverlapped); // адрес структуры OVERLAPPED
Более подробное описание функций ReadFile Ex и WriteFile Ex вы найдете в документации, которая поставляется вместе с SDK.
Очень часто приложениям нужно выполнять такие операции, как копирование, перемещение или удаление файлов. В программном интерфейсе операционной системы Microsoft Windows NT есть удобные функции, предназначенные для выполнения этих операций.
Для копирования файла вы можете использовать функцию CopyFile:
BOOL CopyFile( LPCTSTR lpExistingFileName, // адрес пути // существующего файла LPCTSTR lpNewFileName, // адрес пути копии файла BOOL bFailIfExists); // флаг перезаписи файла
Через параметр lpExistingFileName вы должны передать путь к исходному файлу, кторый будет копироваться. Путь к файлу копии задается с помощью параметра lpNewFileName.
Параметр bFailIfExists определяет действия функции CopyFile, когда указанный в параметре lpNewFileName файл копии уже существует. Если значение параметра bFailIfExists равно TRUE, при обнаружении существующего файла копии функция CopyFile вернет признак ошибки (значение FALSE). Если же значение параметра bFailIfExists равно FALSE, существующий файл копии будет перезаписан.
С помощью функции MoveFile вы можете выполнить перемещение файла:
BOOL MoveFile( LPCTSTR lpExistingFileName, // адрес пути // существующего файла LPCTSTR lpNewFileName); // адрес пути копии файла
Параметры lpExistingFileName и lpNewFileName, определяющие, соответственно, пути к старому и новому месторасположению файла, могут указывать на разные дисковые устройства.
Немного большими возможностями обладает другая функция, предназначенная для перемещения файлов, - функция MoveFileEx XE "MoveFileEx" :
BOOL MoveFileEx( LPCTSTR lpExistingFileName, // адрес пути // существующего файла LPCTSTR lpNewFileName, // адрес пути копии файла DWORD dwFlags); // режим копирования
Дополнительный параметр dwFlags, определяющий один из режимов копирования, может принимать логическую комбинацию следующих значений:
Значение |
Описание |
MOVEFILE_REPLACE_EXISTING |
Перемещение с замещением существующего файла |
MOVEFILE_COPY_ALLOWED |
Если файл перемещается на другое устройство, для перемещения используются функции CopyFile и DeleteFile (удаление файла). Это значение не совместимо со значением MOVEFILE_DELAY_UNTIL_REBOOT |
MOVEFILE_DELAY_UNTIL_REBOOT |
Файл будет перемещен только после перезапуска операционной системы Microsoft Windows NT |
Режим MOVEFILE_DELAY_UNTIL_REBOOT удобен для создания программ автоматической установки приложений (инсталляторов).
Для удаления файла вы должны использовать функцию DeleteFile:
BOOL DeleteFile(LPCTSTR lpFileName);
Параметр lpFileName задает путь к удаляемому файлу.
В случае успеха функция возвращает значение TRUE. При ошибке (например, при попытке удаления файла, открытого этим же или другим процессом), возвращается значение FALSE.
Кратко перечислим несколько функций, предусмотренных в программном интерфейсе Microsoft Windows NT для работы с каталогами.
С помощью функции GetCurrentDirectory приложение может определить текущий каталог:
DWORD GetCurrentDirectory( DWORD nBufferLength, // размер буфера LPTSTR lpBuffer); // адрес буфера
Перед вызовом этой функции вы должны подготовить буфер. Через параметр lpBuffer функции GetCurrentDirectory следует передать адрес буфера, а через параметр nBufferLength - размер буфера в байтах.
В случае успеха функция возвращает размер заполенной части буфера, при ошибке - нулевое значение.
При необходимости с помощью функции GetSystemDirectory вы можете определить путь к системному каталогу Microsoft Windows NT (например, C:\WINNT\SYSTEM32, если Microsoft Windows NT установлена на диск C:):
UINT GetSystemDirectory( LPTSTR lpBuffer, // адрес буфера UINT uSize); // размер буфера в байтах
Функция GetWindowsDirectory позволяет узнать путь к каталогу, в который установлена операционная система Windows (например, C:\WINNT):
UINT GetWindowsDirectory( LPTSTR lpBuffer, // адрес буфера UINT uSize); // размер буфера в байтах
Для изменения текущего каталога используйте функцию SetCurrentDtirecory:
BOOL SetCurrentDirectory(LPCTSTR lpszCurDir);
Через параметр lpszCurDir вы должны передать функции SetCurrentDtirecory путь к новому каталогу.
Вы можете создать новый каталог при помощи функции CreateDirectory XE "CreateDirectory" :
BOOL CreateDirectory( LPCTSTR lpPathName, // путь к создаваемому каталогу LPSECURITY_ATTRIBUTES lpSecurityAttributes); // дескриптор // защиты
Если вам не нужно определять специальные права доступа к создаваемому каталогу (например, запретить некоторым пользователям удалять каталог), вы можете указать для параметра lpSecurityAttributes значение NULL.
Для удаления каталога следует использовать функцию RemoveDirectory:
BOOL RemoveDirectory(LPCTSTR lpszDir);
Через единственный параметр этой функции следует передать путь к удаляемому каталогу.
В программном интерфейсе Microsoft Windows NT нет специальной функции, предназначенной для изменения имени каталогов, однако вы можете выполнить эту операцию при помощи функций MoveFile XE "MoveFile" или MoveFileEx XE "MovefileEx", предназначенных для перемещения файлов. Разумеется, при этом новый и старый каталоги должны располагаться на одном и том же диске.
Для просмотра содержимого каталогов в программном интерфейсе Microsoft Windows NT предусмотрены функции FindFirstFile XE "FindFirstFile", FindNextFile XE "FindNextFile" и FindClose XE "FindClose". Просмотр с помощью этих функций выполняется в цикле.
Перед началом цикла вызовается функция FindFirstFile:
HANDLE FindFirstFile( LPCTSTR lpFileName, // адрес пути для поиска LPWIN32_FIND_DATA lpFindFileData); // адрес структуры // LPWIN32_FIND_DATA, куда будет записана // информация о файлах
Через параметр lpFileName вы должны передать функции адрес строки, содержащей путь к каталогу и шаблон для поиска. В шаблоне можно использовать символы “?” и “*”.
Через параметр lpFindFileData следует передать адрес структуры типа WIN32_FIND_DATA, в которую будет записана информация о найденных файлах. Эта структура определена следующим образом:
typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; // атрибуты файла FILETIME ftCreationTime; // время создания файла FILETIME ftLastAccessTime; // время доступа FILETIME ftLastWriteTime; // время записи DWORD nFileSizeHigh; // размер файла (старшее слово) DWORD nFileSizeLow; // размер файла (младшее слово) DWORD dwReserved0; // зарезервировано DWORD dwReserved1; // зарезервировано TCHAR cFileName[MAX_PATH]; // имя файла TCHAR cAlternateFileName[14]; // альтернативное имя файла } WIN32_FIND_DATA;
Если поиск завершился успешно, функция FindFirstFile возвращает идентификатор поиска, который будет затем использован в цикле при вызове функции FindNextFile. При ошибке возвращается значение INVALID_HANDLE_VALUE XE "INVALID_HANDLE_VALUE".
Заметим, что поля cFileName и cAlternateFileName структуры WIN32_FIND_DATA содержат, соответственно, длинное имя файла и короткое, альтернативное имя файла “в формате 8.3”.
После вызова функции FindFirstFile вы должны выполнять в цикле вызов функции FindNextFile:
BOOL FindNextFile( HANDLE hFindFile, // идентификатор поиска LPWIN32_FIND_DATA lpFindFileData); // адрес структуры // WIN32_FIND_DATA
Через параметр hFindFile этой функции следует передать идентификатор поиска, полученный от функции FindFirstFile. Что же касается параметра lpFindFileData, то через него вы должны передать адрес той же самой структуры типа WIN32_FIND_DATA, что была использована при вызове функции FindFirstFile.
Если функция FindNextFile завершилась успешно, она возвращает значение TRUE. При ошибке возвращается значение FALSE. Код ошибки вы можете получить от функции GetLastError XE "GetLastError". В том случае, когда были просмотрены все файлы в каталоге, эта функция возвращает значение ERROR_NO_MORE_FILES XE "ERROR_NO_MORE_FILES". Вы должны использовать такую ситуацию для завершения цикла просмотра содержимого каталога.
После завершения цикла просмотра необходимо закрыть идентификатор поиска, вызвав для этого функцию FindClose XE "FindClose" :
BOOL FindClose(HANDLE hFindFile);
В качестве единственного параметра этой функции передается идентификатор поиска, полученный от функции FindFirstFile.
Приложение Microsoft Windows NT может динамически следить за содержимым выбранного каталога или даже дерева каталогов, получая от файловой системы извещения при изменении содержимого каталога. Механизм таких извещений основан на использования объектов-событий и функций FindFirstChangeNotification XE "FindFirstChangeNotification", FindNextChangeNotification XE "FindNextChangeNotification", FindCloseChangeNotification XE "FindCloseChangeNotification".
Прежде всего приложение, которое хочет получать извещения от файловой системы, должно вызвать функцию FindFirstChangeNotification, сообщив ей путь к каталогу и события, при возникновении которых необходимо присылать извещения. Прототип функции FindFirstChangeNotification представлен ниже:
HANDLE FindFirstChangeNotification( LPCTSTR lpPathName, // адрес пути к каталогу BOOL bWatchSubtree, // флаг управления каталогом или деревом DWORD dwNotifyFilter); // флаги событий
Через параметр lpPathName вы должны передать функции адрес строки, содержащей путь к каталогу, за которым будет следить файловая система и при изменении в котором ваше приложение получит извещение.
Если при вызове функции флаг bWatchSubtree будет равен TRUE, будут отслеживаться изменения не только в каталоге, указанном в параметре lpPathName, но и во всех его подкаталогах. Если же значение этого флага будет равно FALSE, при изменении содержимого подкаталогов ваше приложение не получит извещение.
При помощи параметра dwNotifyFilter вы можете указать события, при возникновении которых в указанном каталоге или дереве каталогов ваше приложение получит извещение. Здесь вы можете указать комбинацию следующих констант:
Константа |
Описание изменений |
FILE_NOTIFY_CHANGE_FILE_NAME |
Изменение имен файлов, расположенных в указанном каталоге и его подкаталогах, создание и удаление файлов |
FILE_NOTIFY_CHANGE_DIR_NAME |
Изменение имен каталогов, создание и удаление каталогов |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
Изменение атрибутов |
FILE_NOTIFY_CHANGE_SIZE |
Изменение размеров файлов (после записи содержимого внутренних буферов на диск) |
FILE_NOTIFY_CHANGE_LAST_WRITE |
Изменение времени записи для файлов (после записи содержимого внутренних буферов на диск) |
FILE_NOTIFY_CHANGE_SECURITY |
Изменение дескриптора защиты |
Функция FindFirstChangeNotification создает объект-событие и возвращает его идентификатор. При ошибке возвращается значение INVALID_HANDLE_VALUE XE "INVALID_HANDLE_VALUE".
Полученный идентификатор может быть использован в операциях ожидания, выполняемых при помощи функций WaitForSingleObject XE "WaitForSingleObject" и WaitForMultipleObjects XE "WaitForMultipleObjects". Когда произойдет одно из событий, указанных функции FindFirstChangeNotification в параметре dwNotifyFilter, этот объект-событие перейдет в отмеченное значение.
Таким образом, приложение может создать задачу, которая вызывает функцию FindFirstChangeNotification и затем выполняет ожидание для созданного ей объекта синхронизации. При возникновении события эта задача перейдет в активное состояние. При этом она, например, может выполнить сканирование каталога, указанного в параметре lpPathName, для обнаружения возникших изменений.
Заметим, что несмотря на свое название, функция FindFirstChangeNotification не обнаруживает изменения. Она только создает объект синхронизации, который переходит в отмеченное состояние при возникновении таких изменений.
После того как ваше приложение выполнило обнаружение и обработку изменений, оно должно вызвать функцию FindNextChangeNotification:
BOOL FindNextChangeNotification(HANDLE hChangeHandle);В качестве параметра этой функции передается идентификатор объекта синхронизации, созданного функцией FindFirstChangeNotification. Функция FindNextChangeNotification переводит объект синхронизации в неотмеченное состояние таким образом, что его можно использовать повторно для обнаружения последующих изменений.
Если ваше приложение больше не собирается получать извещения от файловой системы, оно должно закрыть идентификатор соответствующего объекта синхронизации. Для этого следует вызвать функцию FindCloseChangeNotification, передав ей через единственный параметр идентфикатор, полученный от функции FindFirstChangeNotification:
BOOL FindCloseChangeNotification(HANDLE hChangeHandle);
В этом разделе мы кратко опишем функции программного интерфейса Microsoft Windows NT, предназначенные для получения информации о дисковых устройствах и состоянии файловой системы.
Для того чтобы определить список установленных в системе логических дисковых устройств, вы можете вызвать функцию GetLogicalDrives XE "GetLogicalDrives" :
DWORD GetLogicalDrives(VOID);
Эта функция не имеет параметров и возвращает 32-разрядное значение, каждый бит которого отвечает за свое логическое устройство. Самый младший, нулевой бит соответствует устройству с идентификатором A:, бит с номером 1 - устройству с идентификатором A:, и так далее. Если бит установлен, устройство присутствует в системе, если нет - отсутствует.
Более развернутую информацию о составе логических дисковых устройств в системе можно получить при помощи функции GetLogicalDriveStrings XE "GetLogicalDriveStrings" :
DWORD GetLogicalDriveStrings( DWORD nBufferLength, // размер буфера LPTSTR lpBuffer); // адрес буфера для записи // сведений об устойствах
Если вызвать эту функцию с параметрами nBufferLength и lpBuffer равными, соответственно, 0 и NULL, она вернет размер буфера, необходимый для записи информации о всех логических дисковых устройствах, присутствующих в системе. После этого вы можете вызвать функцию GetLogicalDriveStrings еще раз, заказав предварительно буфер нужного размера и указав функции правильный размер буфера.
Функция GetLogicalDriveStrings заполнит буфер текстовыми строками вида:
a:\ b:\ c:\
Каждая такая строка закрыта двоичным нулем. Последняя строка будет закрыта двумя двоичными нулями.
С помощью функции GetDriveType вы можете определить тип дискового устройства:
UINT GetDriveType(LPCTSTR lpRootPathName);
В качестве параметра функции GetDriveType нужно передать текстовую строку имени устройства, например, полученную при помощи функции GetLogicalDriveStrings.
В зависимости от типа указанного устройства функция GetDriveType может вернуть одно из следующих значений:
Значение |
Описание |
0 |
Тип устройства не удалось определить |
1 |
Указанный корневой каталог не существует |
DRIVE_REMOVABLE |
Устройство со сменным носителем данных |
DRIVE_FIXED |
Устройство с несменным носителем данных |
DRIVE_REMOTE |
Удаленное (сетевое) устройство |
DRIVE_CDROM |
Устройство чтения CD-ROM |
DRIVE_RAMDISK |
Электронный диск (RAM-диск) |
Одним из наиболее интересных параметров логического устройства является размер свободного пространства на нем. Этот параметр вместе с некоторыми другими вы можете определить при помощи функции GetDiskFreeSpace XE "GetDiskFreeSpace" :
BOOL GetDiskFreeSpace( LPCTSTR lpRootPathName, // адрес пути к корневому каталогу LPDWORD lpSectorsPerCluster,// количество секторов в кластере LPDWORD lpBytesPerSector, // количество байт в секторе LPDWORD lpNumberOfFreeClusters, // количество свободных // кластеров LPDWORD lpTotalNumberOfClusters); // общее количество // кластеров
Перед вызовом этой функции вы должны подготовить несколько переменных типа DWORD и передать функции их адреса. Функция GetDiskFreeSpace запишет в эти переменные параметры логического диска, перечисленные в комментариях к прототипу функции.
Для того чтобы определить размер свободного пространства на диске в байтах, вы должны умножить значение количества свободных кластеров (записанное по адресу lpNumberOfFreeClusters) на количество секторов в кластере (записанное по адресу lpSectorsPerCluster) и на количество байт в одном секторе (которое будет записано по адресу lpBytesPerSector). Более подробно о делении диска на кластеры и секторы вы можете узнать из 19 тома “Библиотеки системного программиста”.
В программном интерфейсе Microsoft Windows NT есть еще одна функция, с помощью которой вы можете определить параметры дискового устройства. Это функция GetVolumeInformation XE "GetVolumeInformation" :
BOOL GetVolumeInformation( LPCTSTR lpRootPathName, // адрес пути к корневому каталогу LPTSTR lpVolumeNameBuffer, // буфер для имени тома DWORD nVolumeNameSize, // размер буфера lpVolumeNameBuffer LPDWORD lpVolumeSerialNumber, // буфер для серийного номера // тома LPDWORD lpMaximumComponentLength, // буфер для максимальной // длины имени файла, допустимой для данного тома LPDWORD lpFileSystemFlags, // буфер для системных флагов LPTSTR lpFileSystemNameBuffer, // буфер для имени // файловой системы DWORD nFileSystemNameSize); // размер буфера // lpFileSystemNameBuffer
Перед использованием этой функции вы должны подготовить несколько буферов и передать функции их адреса. Функция заполнит буферы параметрами устройства, корневой каталог которого задан параметром lpRootPathName.
В буфере системных флагов, адрес которого передается функции через параметр lpFileSystemFlags, могут быть установлены следующие флаги:
Флаг |
Описание |
FS_CASE_IS_PRESERVED |
Система делает различия между заглавными и прописными буквами в именах файлов при записи этих имен на диск |
FS_CASE_SENSITIVE |
Система делает различия между заглавными и прописными буквами |
FS_UNICODE_STORED_ON_DISK |
Система может работать с кодировкой Unicode в именах файлов |
FS_PERSISTENT_ACLS |
Система способна работать со списком контроля доступа к файлам ACL (access-control list). Такая возможность есть в файловой системе NTFS, но отсутствует в файловых системах HPFS и FAT |
FS_FILE_COMPRESSION |
Файловая система способна сжимать (компрессовать) отдельные файлы |
FS_VOL_IS_COMPRESSED |
Для тома используется автоматическая компрессия данных |
При необходимости вы можете легко изменить метку тома, вызвав для этого функцию SetVolumeLabel XE "SetVolumeLabel" :
BOOL SetVolumeLabel( LPCTSTR lpRootPathName, // адрес пути // к корневому каталогу тома LPCTSTR lpVolumeName); // новая метка тома
Если параметр lpVolumeName указать как NULL, метка тома будет удалена.
Несмотря на то что набор функций, предназначенных для работы с дисками и файлами в среде Microsoft Windows NT, достаточно мощный и удобный, иногда его возможностей может оказаться недостаточно. Например, если вы разрабатываете систему защиты от несанкционированного копирования дискет, вам могут потребоваться средства нестандартного форматирования дискет. Вам также может потребоваться выполнять из приложения извлечение сменного носителя данных или блокировку последнего в накопителе, проверку замены сменного носителя данных или другие “необычные” операции.
Создавая аналогичную програму MS-DOS, вы можете выполнить нестандартное форматиование, например, с помощью функций BIOS XE "BIOS" или обращаясь непосредственно к портам дискового контроллера. В среде Microsoft Windows NT оба эти способа непригодны, так как функции BIOS и порты контроллера диска недоступны для обычных приложений, работающих в защищенном режиме.
Выход, однако, есть.
Он заключается в прямом обращении к драйверу дискового устройства с помощью функции управления вводом/выводом, которая называется DeviceIoControl XE "DeviceIoControl". По своим возможностям он напоминает способ, описанный нами в 19 томе “Библиотеки системного программиста” и связанный с использованием функций GENERIC IOCTL XE "GENERIC IOCTL".
Прототип функции DeviceIoControl мы привели ниже:
BOOL DeviceIoControl( HANDLE hDevice, // идентификатор устройства DWORD dwIoControlCode, // код выполняемой операции LPVOID lpInBuffer, // буфер для входных данных DWORD nInBufferSize, // размер буфера lpInBuffer LPVOID lpOutBuffer, // буфер для выходных данных DWORD nOutBufferSize, // размер буфера lpOutBuffer LPDWORD lpBytesReturned, // указатель на счетчик // выведенных байт LPOVERLAPPED lpOverlapped); // указатель на // структуру OVERLAPPED
Через параметр hDevice вы должны передать идентификатор устройства, полученный от функции CreateFile. Для того чтобы воспользоваться этой функцией для открывания устройства, вы должны указать имя устройства следующим образом (пример приведен для диска C:):
hDevice = CreateFile("\\\\.\\C:", 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
С помощью параметра dwIoControlCode можно задать один из следующих кодов операции:
Код операции |
Описание |
FSCTL_DISMOUNT_VOLUME |
Размонтирование тома |
FSCTL_GET_COMPRESSION |
Определение состояния компрессии для каталога или файла |
FSCTL_LOCK_VOLUME |
Блокирование тома |
FSCTL_SET_COMPRESSION |
Установка состояния компрессии для каталога или файла |
FSCTL_UNLOCK_VOLUME |
Разблокирование тома |
IOCTL_DISK_CHECK_VERIFY |
Проверка замены носителя данных для устройства со сменным носителем |
IOCTL_DISK_EJECT_MEDIA |
Извлечение носителя данных из устройства с интерфейсом SCSI |
IOCTL_DISK_FORMAT_TRACKS |
Форматирование нескольких дорожек диска |
IOCTL_DISK_GET_DRIVE_GEOMETRY |
Получение информации о физической геометрии диска |
IOCTL_DISK_GET_DRIVE_LAYOUT |
Получение информации о всех разделах диска |
IOCTL_DISK_GET_MEDIA_TYPES |
Получение информации о среде, которую можно использовать для хранения данных в устройстве |
IOCTL_DISK_GET_PARTITION_INFO |
Полечение информации о разделе диска |
IOCTL_DISK_LOAD_MEDIA |
Загрузка носителя данных в устройство |
IOCTL_DISK_MEDIA_REMOVAL |
Включение или отключение механизма извлечения носителя данных |
IOCTL_DISK_PERFORMANCE |
Получение информации о производительности устройства |
IOCTL_DISK_REASSIGN_BLOCKS |
Перевод блоков диска в область резервных блоков |
IOCTL_DISK_SET_DRIVE_LAYOUT |
Создание разделов на диске |
IOCTL_DISK_SET_PARTITION_INFO |
Установка типа разделов диска |
IOCTL_DISK_VERIFY |
Выполнение логического форматирования |
IOCTL_SERIAL_LSRMST_INSERT |
Разрешение или запрещение добавления информации о состоянии линии и модема в поток передаваемых данных |
Через параметр lpInBuffer вы должны передать функции DeviceIoControl адрес управляющего блока, необходимого для выполнения опрации. Формат этого блока зависит от кода выполняемой операции. В документации SDK приведены форматы управляющих блоков для всех перечисленных выше кодов операций.
В буфер, адрес которого передается через параметр lpOutBuffer, будет записан результат выполнения операции. Формат этого буфера также зависит от кода операции.
При необходимости с помощью функции DeviceIoControl вы можете выполнять асинхронные операции, подготовив структуру типа OVERLAPPED XE "OVERLAPPED" и передав ее адрес через параметр lpOverlapped. Не забудьте также при открывании устройства указать функции CreateFile XE "CreateFile" флаг FILE_FLAG_OVERLAPPED XE "FILE_FLAG_OVERLAPPED".