В лекции рассматривается использование функций библиотеки (C++): CreateFile(), CopyFile(), MoveFile(), DeleteFile(), ReadFile(), WriteFile(), UnlockFile(), LockFile() и CloseHandle().
Материалы по данной теме имеются так же в лекции про файлы.
Создание файлов Для создания нового или открытия существующего файла используется функция CreateFile. При использовании функции CreateFile необходимо указать, предполагается чтение или запись в файл или и то и другое. Также необходимо указать, необходимые действия в случае наличия файла на диске или его отсутствия (например, перезаписывать файл, если он существует и создавать новый, если – нет). Также функция CreateFile позволяет указать возможность разделения файла с другими приложениями (одновременного чтения/записи нескольких приложений в файл). Если некоторое приложение монополизировало доступ к файлу на чтение и/или запись, то никакое другое приложение не сможет читать и/или писать в файл, пока первое его не закроет.
dwCreationDisposition: [in] Определяет то, какие действия необходимо предпринять в случаях, если файл существует и если файл не существует. Этот параметр должен иметь одно из следующих заначений: Значение Пояснение CREATE_NEW Создаёт файл. Вызов заканчивается неудачей, если файл существует. CREATE_ALWAYS Создаёт новый файл. Если файл существует, то его содержимое и атрибуты будут стёрты. OPEN_EXISTING Открытие файла. Если файл не существует, то вызов закончится неудачей. OPEN_ALWAYS Открывает файл. Если файл не существует, то он будет создан. TRUNCATE_EXISTING Открывает файл, размер которого обнуляется. Файл должен открываться как минимум с режимом доступа GENERIC_WRITE. Если файл не существует, то вызов будет неудачен.
dwFlagsAndAttributes: [in] Позволяет задавать файловые атрибуты (только для чтения, скрытый, системный и пр.). Также позволяет сообщать операционной системе желаемое поведение при работе с файлами. Например, запись в файл без буферизации (FILE_FLAG_NO_BUFFERING и FILE_FLAG_WRITE_THROUGH); оптимизация для неупорядоченного доступа (FILE_FLAG_RANDOM_ACCESS); открытие для асинхронного ввода/вывода (FILE_FLAG_OVERLAPPED).
Возвращаемое значение: Если вызов успешен, возвращается дескриптор открытого файла. Если вызов неудачен, возвращается константа INVALID_HANDLE_VALUE. Код ошибки можно получить вызовом функции GetLastError. Подробную информацию об ошибке (по её коду) можно получить вызовом функции FormatMessage.
Для копирования файлов используется функция CopyFile:
В случае успеха возвращается ненулевое значение.
Для переименования файлов и директорий используется функция MoveFile:
В случае успеха возвращается ненулевое значение.
Для удаления существующих файлов используется функция DeleteFile:
Чтение/запись в файл: Каждый открытый файл имеет файловый указатель (file pointer), который указывает позицию следующего файла, который будет записан/прочтен. При открытии файла его файловый указатель перемещается на начало файла. После прочтения/записи очередного файла система перемещает файловый указатель. Файловый указатель можно перемещать, используя функцию SetFilePointer. Для чтения/записи в файл используются функции ReadFile и WriteFile, при этом необходимо, чтобы файл был открыт на чтение и на запись соответственно.
Функция ReadFile читает из файла указанное количество символов, начиная с позиции, обозначенной файловым указателем. При синхронном (в противоположность асинхронному) чтении файловый указатель сдвигается на фактически прочитанное количество байт.
Возвращаемое значение: Если выполнение функции произошло успешно, то возвращается ненулевое значение. Если возвращено ненулевое значение, но прочитано 0 байт, значит файловый указатель стоял на конце файла перед операцией чтения.
Функция WriteFile записывает в файл данные, начиная с позиции, обозначенной файловым указателем. При синхронной (в противоположность асинхронному) записи файловый указатель сдвигается на фактически записанное количество байт.
Возвращаемое значение: Если выполнение функции произошло успешно, то возвращается ненулевое значение. Блокирование предоставляет процессу монопольный доступ к отрезку файла. Файловые блокировки не наследуются. Остальные процессы не могут ни читать, ни писать в заблокированную часть файла.
Функция UnlockFile позволяет разблокировать участок файла, ранее заблокированный функцией LockFile .
Возвращаемое значение: Если выполнение функции произошло успешно, то возвращается ненулевое значение. Отрезок файла, который разблокируется функцией UnlockFile должен в точности соответствовать отрезку, заблокированному функцией LockFile. Например, две соседних части файла не могут быть заблокированы по отдельности, а разблокированы как единое целое. Процесс не должен завершать выполнение, имея заблокированные части файлов. Файловый дескриптор, для которого есть заблокированные отрезки, не должен закрываться.
Для оптимального использования ресурсов операционной системы приложение должно закрывать ненужные файлы с помощью функции CloseHandle. Файлы, открытые на момент завершения приложения, закрываются автоматически.
Возвращаемое значение: Если выполнение функции произошло успешно, то возвращается ненулевое значение.
Записки программиста
Учимся работать с файлами через Windows API
Из предыдущих постов, посвященных WinAPI, мы научились настраивать Visual Studio и узнали, как в нем писать простые консольные приложения. Следующим маленьким шажком в изучении WinAPI будет освоение работы с файлами.
Для этого нелегкого дела нам понадобятся следующие процедуры:
В Windows для того, чтобы открыть или создать файл, нужно вызвать процедуру, имеющую целых семь аргументов. К счастью, большинство из них приходится использовать крайне редко. Аргумент szName задает имя файла, а dwAccess — желаемый доступ к файлу, обычно это GENERIC_READ, GENERIC_WRITE или оба значения, объединенные логическим или. Параметр dwShareMode определяет, что могут делать с файлом другие процессы, пока мы с ним работаем. Возможные значения — FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE и их комбинации, однако часто этот параметр просто устанавливают в ноль. Параметр dwCreationDisposition определяет, как именно мы хотим открыть файл, может быть, например, CREATE_NEW, CREATE_ALWAYS, OPEN_EXISTING, OPEN_ALWAYS. О семантике этого хозяйства нетрудно догадаться самостоятельно. С помощью dwFlags можно указать дополнительные свойства файла, например, хранить ли его в зашифрованном или сжатом виде, или сказать, что файл является скрытым, временным или системным. Обычно сюда передают FILE_ATTRIBUTE_NORMAL. Наконец, про lpSecurityAttributes и hTemplateFile сейчас знать не нужно, сюда можно смело передавать NULL.
В случае успешного создания или открытия файла, процедура CreateFile возвращает его хэндл. В случае ошибки возвращается специальное значение INVALID_HANDLE_VALUE. Узнать подробности об ошибке можно с помощью GetLastError.
Чтение из файла в буфер lpBuff размером dwBuffSize. В переменную dwCount записывается реальное количество прочитанных байт. Последний опциональный аргумент называется lpOverlapped и о нем сейчас знать не нужно.
Аргументы и семантика процедуры WriteFile полностью аналогичны ReadFile.
Файловые дескрипторы закрываются с помощью CloseHandle. На самом деле, эта процедура используется не только для работы с файлами, так что мы еще не единожды с нею встретимся.
Посмотрим теперь на все это хозяйство в действии. Следующая программа пишет в файл counter.dat количество собственных запусков. Первые пять запусков ничем не примечательны. На шестой и последующие запуски программа сообщает, что у нее истек триал и просит приобрести полную версию.
#define MAX_TRIAL_RUNS 5
const TCHAR szCounterFileName [ ] = L «counter.dat» ; const TCHAR szMsgTmpl [ ] = L «Вы запустили программу в %d-й раз. %s.» ; const TCHAR szCheckOk [ ] = L «Все в порядке, продолжайте работу» ; const TCHAR szCheckFailed [ ] = L «Триал истек, купите полную версию» ;
Как обычно, программа также успешно компилируется при помощи MinGW и запускается под Wine.
В качестве домашнего задания можете попробовать модифицировать программу так, чтобы она выводила время, когда производились все ее запуски. Для этого вам понадобятся процедуры GetLocalTime, SetFilePointer и GetFileSizeEx. Если это задание покажется вам слишком простым, попробуйте найти информацию о том, как при помощи процедур, упомянутых в этой заметке, (1) написать консольное приложение и (2) открыть диск C: на чтение, словно он является обычным файлом.
Если у вас есть дополнения или возникли вопросы, смелее пишите комментарии, не стесняйтесь!
Visual C++. Работа с файлами в WinApi. Создание, чтение и запись
Необходимость в использовании файлов в собственных приложениях возникает, когда требуется постоянно хранить те или иные данные, полученные в процессе работы приложения и иметь к ним доступ уже после завершения работы программы, при повторном ее запуске.
Как вы, наверное, заметили, подход хранения информации в файлах на разнообразнейших носителях, типов которых в данное время появляется все больше, является очень удобным и в некоторых случаях незаменимым. Характерным и одним из распространенных случаев использования файлов для простых программ, как, в общем-то, и для более сложных приложений, являются случаи хранения в файлах конфигурации программы. То есть хранение данных, отвечающих за то, по каким параметрам будет осуществлено построение внешнего вида окна, например, какие пункты должны присутствовать в меню или в каком-либо элементе управления. В этой статье будет рассмотрен пример загрузки из файла пунктов в элемент управления типа ComboBox.
Теоретические сведения
В Windows предусмотрено два типа работы с файлами: синхронный и асинхронный. Для изучения мы возьмем первый, так как асинхронный применяется редко (удобен для работы с внешними портами компьютеров COM, LPT и т.д.) и в некоторых версиях операционных систем не поддерживается. Так что более удачным будет выбор в пользу использования в разрабатываемом приложении синхронного типа работы с файлами.
Если разбирать сами понятия синхронного и асинхронного типов, то можно сказать, что операции, производимые с файлами, при синхронном режиме будут работать довольно медленно, то есть при начале работы с файлом компьютер будет ждать до того, как работа будет прекращена, и только потом будет продолжена работа приложения. В случае с асинхронным режимом можно работать, параллельно выполняя работу с файлом и совершая необходимые действия, не относящиеся к файлу.
Описание функций
Прежде всего, нужно сказать, что все действия с файлом происходят после использования функции CreateFile(). Эта функция используется для того, чтобы указать, какие права на использование файла необходимо предоставить, а также для создания файлов и работы с внешними портами (как вы, наверное, заметили, функция довольно универсальна и является одной из основных). Все это многообразие действий можно осуществить, передавая в функцию те или иные многочисленные параметры, выбор которых уже зависит от реализации частной задачи. Итак, функция описывается следующим образом:
Аргументов у функции действительно много, и их подробный разбор поможет более ясно понять принципы использования самой функции. Первый из них — lpFileName, содержит имя файла, над которым намереваемся совершить действия, второй — dwDesiredAccess отвечает за назначение прав на использование, которые следует предоставить при открытии или создании. Существует всего два значения, которые мы можем передать, — это GENERIC_WRITE для записи и GENERIC_READ для чтения соответственно. Нужно помнить, что эти значения можно комбинировать. Далее идет аргумент dwShareMode, который контролирует открытие файла другими приложениями (пользователями). Опять, как и в предыдущем случае, значений два — FILE_SHARE_READ и FILE_SHARE_WRITE, другие приложения могут только читать из файла в первом случае, когда он используется вашим приложением, и во втором только записывать в файл. Если же не требуется ни того, ни другого, передается нуль. Возможна комбинация из значений. Следующий параметр — lpSecurityAttributes, является указателем на структуру SECURITY_ATTRIBUTES, в которой содержится дополнительная информация о защищенности создаваемого файла.
Чаще всего этот параметр ставится в NULL, без каких-либо потерь в использовании файла. Но если вам все-таки требуется «поставить защиту» на файл, то понадобится и заполнить структуру SECURITY_DESCRIPTOR, указатель на которую содержит SECURITY_ATTRIBUTES. Подробнее об этих структурах рассказывать не имеет смысла из-за того, что их редко используют. После этого идет dwCreationDistribution, обозначающий действия, применяемые к файлу при его открытии или создании. В этот параметр можно передать одно из многих значений: CREATE_NEW — создается новый файл; CREATE_ALWAYS — создается файл с указанным именем, или если такой файл уже создан, то он удаляется и создается заново; OPEN_EXISTING — открывает уже созданный файл; OPEN_ALWAYS — открывает файл с указанным именем (параметр lpFileName), если его нет, то он создается; TRANCATE_EXISTING — открывает файл, после чего происходит удаление его содержимого до нуля байт. Предпоследний аргумент — dwFlagsAndAttributes, указывает, какие атрибуты применять при создании файла. Значений очень много, и они знакомы почти каждому пользователю, для примера приведем чаще всего используемые: FILE_ATRRIBUTE_NORMAL — если вас интересует создание обычного и простого файла, советую использовать этот атрибут;
FILE_ATTRIBUTE_READONLY — только чтение, FILE_ATTRIBUTE_ARCHIVE — архивный файл; FILR_ATTRIBUTE_HIDDEN — скрытый файл. Последние три значения можно комбинировать. Ну и, наконец, последний параметр следует выставить в NULL, он очень редко используется и для учебных целей не представляет интереса. При каждом вызове функции CreateFile() необходимо вызывать проверку на возникновении ошибки, если ошибка возникает, функция возвращает INVALID_HANDLE_VALUE. Если работа функции завершилась удачно, она возвращает указатель на файл (хендл — HANDLE), через который с ним осуществляется дальнейшая работа.
Ясно, что простого открытия или создания файла не достаточно, возникает проблема что-то в него записать или прочитать уже находящуюся в нем информацию. Решение проблемы заключает в себе использование функций ReadFile() — для чтения информации и WriteFile() — для записи информации. Рассмотрим сначала последнюю функцию, хотя, в общем, по синтаксису они похожи, это будет ясно по описанию. Итак, WriteFile() имеет следующий вид:
Первый параметр hFile — это хендл, возвращаемый функцией CreateFile() в случае удачного завершения работы.
Второй lpBuffer — это буфер, содержащий информацию, которая будет записана в файл. Что касается третьего nNumberOfBytesToWrite — это число байт, которое мы намереваемся записать, то есть размер буфера lpBuffer в байтах.
Отличие четвертого аргумента lpNumberOfBytesWritten от третьего заключается в том, что в него передается переменная, в которую будет записано фактически, сколько байт данных было записано.
Последний параметр используется при асинхронном способе работы с файлами, использование которого не разбирается в этой статье. Если функция завершилась без ошибок, она возвращает 1, иначе 0.
Функция чтения из файла ReadFile() описывается так:
Нетрудно заметить, что синтаксис практически полностью идентичен с функцией WriteFile(). Главное отличие заключается именно в самом использовании рассматриваемой функции, а именно, что данные не записываются в файл, а считываются из него. А так, буфер, размер в байтах буфера и число считанных байт используются аналогично.
Допустим, у нас имеется файл, в котором информация записана не подряд одной строкой, а через определенные промежутки. Считывать информацию в этом случае придется, используя такую возможность, как позиционирование в файле, то есть можно указать, с какого места файла будут считываться данные из файла. Осуществить вышеописанные действия позволяет функция SetFilePointer(). Она описана следующим образом:
DWORD SetFilePointer (HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod)
Сперва в эту функцию передается хендл уже открытого файла. Во второй параметр lDistanceToMove нужно ввести число, отвечающее, на сколько будет совершено перемещение позиции в файле, с которой впоследствии будет производиться чтение данных. Третий аргумент lpDistanceToMoveHigh напрямую связан со вторым. Обратите внимание на их типы: в первом случае это LONG, а во втором — PLONG, то есть указатель.
Исходя из этого, можно сказать, что при помощи второго параметра можно передвинуть позицию в файле только на 2^32 (4 байта занимает переменная типа LONG), то есть получается, что это максимальный размер файла, с которым возможно работать.
Но в некоторых случая требуется работать с файлами большего размера, для этого-то и предназначен третий параметр, с помощью которого можно изменить позицию в файле на 2^64. Необходимость работать со столь большими файлами возникает очень редко, и поэтому подробно разбирать примеры использования функции SetFilePointer с этим параметром является нерациональным. При вызове функции следует просто передать в этот параметр NULL.
Последний аргумент dwMoveMethod указывает, откуда будет начат отсчет, принимает значения: FILE_BEGIN — начало файла, FILE_CURRENT — с текущего значения, FILE_END — с конца файла. Нужно учесть, что число, на которое следует сдвинуть позицию в файле, может быть отрицательным, это удобно, если отсчет ведется от конца файла. В случае удачного завершения работы функция возвращает новую позицию в файле.
Иногда бывает полезно удалить созданный файл, заканчивая работу программы, например, храня в этом файле промежуточные данные, полученные в процессе работы программы, но совершенно ненужные для постоянного хранения. В этом случае удобно пользоваться функцией:
BOOL DeleteFile (LPCTSTR lpFileName)
В параметр lpFileName просто передается имя файла, который следует удалить. Как и несколько предыдущих функций, при возникновении ошибки функция возвращает 0, иначе 1.
Последней функцией, которую нужно взять на рассмотрение, является функция SetEndOfFile(), позволяющая указать, где следует установить конец файла. Прототип этой функции выглядит так:
BOOL SetEndOfFile (HANDLE hFile)
Нетрудно видеть, что эта функция содержит всего один параметр, а именно хендл открытого файла. Функция возвращает истину, если завершилась удачно, или ложь в случае возникновении ошибки. Концом файла становится то место, в котором находится текущая позиция, установленная функцией SetFilePointer ().
Практика
Как уже упоминалось выше, в практической части этой статьи будет разобран пример написания программы, использующий все разобранные здесь функции. Примерный вид заготовки показан на рисунке 1.
Сразу становится ясно, что код приложения должен содержать оконную функцию, описание класса этого окна, в основном окне должны размещаться элемент управления ComboBox и составленное меню, содержащее пункт Файл и его подпункты «Создать», «Прочитать» и «Удалить».
Всю эту черновую работу по созданию приложения вы должны сделать сами, ведь все перечисленные действия абсолютно не касаются темы, взятой за основу статьи (для облегчения работы можете воспользоваться инструментом App Wizard Win32).
Итак, когда основа приложения уже сделана, перейдем к написанию обработчиков событий для пунктов меню. В функции, обрабатывающей сообщения основного окна, вписываем следующий код:
HANDLE hFile; // Хендл окна char cBufferText [10]; // Буфер для содержимого текста int j, i; // Переменные для цикла HWND hWnd; // Идентификатор окна (описывается глобально!) HWND hCombo; // Идентификатор списка (описывается глобально!)
В обработчике сообщения, возникающего при нажатии на подпункте «Создать», пишем: