5. Работа с файлами

Операционная система 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

Прежде чем мы перечислим достоинства файловой системы NTFS, сделаем краткий обзор недостатков других файловых систем, созданных для различных операционныъх систем персональных компьютеров.

Операционная система MS-DOS и файловая система FAT

Все вы хорошо знаете недостатки файловой системы 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

Операционная система 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

Известная своими средствами работы в сети операционная система 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

При разработке операционной системы 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

Высокопроизводительная файловая система 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

Теперь, после такого краткого обзора наиболее популярных файловых систем, мы может сказать, что файловая система 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

Можно было бы подумать, что функция 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

Функция CloseHandle позволяет закрыть файл. Она имеет единственный параметр - идентификатор закрываемого файла. Заметим, что если мы указали функции CreateFile XE "CreateFile" флаг FILE_FLAG_DELETE_ON_CLOSE, сразу после закрывания файл будет удален. Как мы уже говорили, такая методика очень удобна при работе со временными файлами.

Функции ReadFile и WriteFile

С помощью функций 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.

Функция FlushFileBuffers

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


BOOL FlushFileBuffers(HANDLE hFile);

В случае успешного завершения функция возвращает значение TRUE, при ошибке - FALSE. Код ошибки вы можете получить при помощи функции GetLastError XE "GetLastError".

Заметим, что при закрывании файла функцией CloseHandle содержимое всех буферов, связанных с этим файлом, записывается на диск автоматически. Поэтому вы должны использовать функцию FlushFileBuffers XE "FlushFileBuffers" только в том случае, если запись содержимого буферов нужно выполнить до закрывания файла.

Функция SetFilePointer

С помощью функции 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

При необходимости изменить длину файла (уменьшить или увеличить), вы можете воспользоваться функцией SetEndOfFile XE "SetEndOfFile". Эта функция устанавливает новую длину файла в соответствии с текущей позицией:


BOOL SetEndOfFile(HANDLE hFile);

Для изменения длины файла вам достаточно установить текущую позицию в нужное место с помощью функции SetFilePointer XE "SetFilePointer", а затем вызвать функцию SetEndOfFile XE "SetEndOfFile".

Функции LockFile и UnlockFile

Так как операционная система 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);   // размер буфера в байтах 

Определение каталога Microsoft Windows NT

Функция 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".