16+
ComputerPrice
НА ГЛАВНУЮ СТАТЬИ НОВОСТИ О НАС




Яндекс цитирования


Версия для печати

Модуль поиска не установлен.

Visual C++. Работа с файлами в WinApi. Создание, чтение и запись

22.04.2003

Михаил Новиков <vertexdev@pisem.net>

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

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

Как вы, наверное, заметили, подход хранения информации в файлах на разнообразнейших носителях, типов которых в данное время появляется все больше, является очень удобным и в некоторых случаях незаменимым. Характерным и одним из распространенных случаев использования файлов для простых программ, как, в общем-то, и для более сложных приложений, являются случаи хранения в файлах конфигурации программы. То есть хранение данных, отвечающих за то, по каким параметрам будет осуществлено построение внешнего вида окна, например, какие пункты должны присутствовать в меню или в каком-либо элементе управления. В этой статье будет рассмотрен пример загрузки из файла пунктов в элемент управления типа ComboBox.

Теоретические сведения

В Windows предусмотрено два типа работы с файлами: синхронный и асинхронный. Для изучения мы возьмем первый, так как асинхронный применяется редко (удобен для работы с внешними портами компьютеров COM, LPT и т.д.) и в некоторых версиях операционных систем не поддерживается. Так что более удачным будет выбор в пользу использования в разрабатываемом приложении синхронного типа работы с файлами.

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

Описание функций

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

HANDLE CreateFile (LPCTSTR lpFileName, DWORD dwDesiredAccess,
DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);

Аргументов у функции действительно много, и их подробный разбор поможет более ясно понять принципы использования самой функции. Первый из них - 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() имеет следующий вид:

BOOL WriteFile
(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)

Первый параметр hFile - это хендл, возвращаемый функцией CreateFile() в случае удачного завершения работы.

Второй lpBuffer - это буфер, содержащий информацию, которая будет записана в файл. Что касается третьего nNumberOfBytesToWrite - это число байт, которое мы намереваемся записать, то есть размер буфера lpBuffer в байтах.

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

Последний параметр используется при асинхронном способе работы с файлами, использование которого не разбирается в этой статье. Если функция завершилась без ошибок, она возвращает 1, иначе 0.

Функция чтения из файла ReadFile() описывается так:

BOOL ReadFile
(HANDLE hFile, LCVOID lpBuffer,
DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)

Нетрудно заметить, что синтаксис практически полностью идентичен с функцией 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;
// Идентификатор списка (описывается глобально!)

В обработчике сообщения, возникающего при нажатии на подпункте "Создать", пишем:

case ID_CREATEFILE:
hFile=CreateFile("proba.zzz", GENERIC_WRITE,
FILE_SHARE_READ,
NULL, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE ==
hFile)
{
MessageBox (hWnd,"Ошибка при записи файла","Возникла ошибка"MB_OK);
return 0;
}
for(i=0;i<5;i++)
{
for(j=0;j<=10;j++)
cBufferText[j]=NULL;
SetFilePointer
(hFile, 5*i, 0,FILE_BEGIN);
sprintf (cBufferText,"%d",i);
WriteFile (hFile,
cBufferText, sizeof
(cBufferText),
&dwByte, NULL);
}
SetEndOfFile (hFile);
CloseHandle (hFile);
break;

После того как файл создан, по умолчанию в папке, где был запущен исполняемый файл приложения, нужно прочитать информацию из него и занести ее в элемент управления ComboBox, идентификатор которого должен быть описан глобально. Сам элемент, напомню, создается функцией CreateFile(), как и основное окно.

В обработчике сообщения, которое возникает при нажатии на подпункте "Прочитать", вписываем следующий код:

case ID_READFILE:
hFile = CreateFile
("proba.zzz", GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if(INVALID_HANDLE_VALUE == hFile)
{
MessageBox (hWnd,"Ошибка при открытии файла","Возникла ошибка", MB_OK);
return 0;
}
for(i=0;i<5;i++)
{
SetFilePointer
hFile,5*i,0,FILE_BEGIN);
ReadFile (hFile, cBufferText, sizeof(cBufferText), &dwByte, NULL); SendMessage (hCombo, CB_ADDSTRING,0,(LONG)cBufferText);
}
CloseHandle(hFile); break;

Ну и, наконец, в обработчике подпункта "Удалить" пишем следующие строчки:

case ID_DELETEFILE:
DeleteFile ("proba.zzz");
break;

Вот и вся программа, работу которой вы можете увидеть на рисунке 2. Если открыть созданный в процессе работы приложения файл (после нажатия на подпункт "Создать"), можно увидеть примерно то, что изображено на рис. 3, если, конечно, вы сделали все правильно.

Выводы

В итоге, можно сказать, что доступ к файлам и работа с ними в Windows при синхронном режиме осуществлена довольно просто.

Все сводится к открытию или созданию файла с определенными флагами, осуществлению с ним различных операций и только после этого корректному закрытию, что немаловажно. Если у вас возникнут какие-либо ошибки или вопросы при выполнении практической части и описания теоретической - прошу присылать их на E-mail.



статьи
статьи
 / 
новости
новости
 / 
контакты
контакты