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




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


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

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

Visual C++. Работа с файлами с помощью WinApi

25.03.2003

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

Эта статья посвящена наиболее распространенным операциям, с которыми любой программист, так или иначе, сталкивается, а именно операциями, связанными с использованием файлов и директорий. Такие знания могут понадобиться, например, в тех случаях, когда функциональности стандартных окон, предоставляемых Window, типа File Open или File Save, недостаточно и тогда приходится писать собственный аналог, только добавляя дополнительные возможности под определенные нужды. Так как операций в WinApi с файлами довольно много, и даже если полностью посвятить весь объем статьи описанию функций, то его просто не хватит и на половину. Поэтому нами будет рассмотрен пример создания программы, позволяющей переходить из папки в папку, с диска на диск, а также возвращать имена выбранных файлов и информацию о выбранных объектах.

Замечание

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

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

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

Прежде чем преступить к подробному описанию функций для работы с файлами и директориями, которые нам понадобятся для описываемой ниже программы, нужно сначала разобраться с самим понятием файла и папки. Файл представляет собой именованную область внешней памяти персонального компьютера. Под внешней памятью в этом определении подразумевается жесткий диск, дискета, CD-диск, а также огромное множество разнообразных хранителей информации. Понятие папки или каталога связано, в первую очередь, с иерархической файловой системой, которую использует Windows.

Также файловые системы делятся на несколько типов, в каждой из которых используются одни и те же методы обращения и работы с файлами, дисками и папками. Ниже приведены некоторые типы файловых систем:

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

FAT32 - файловая система, чаще всего используемая в Windows 98, ME и 2000. Является усовершенствованной по сравнению с FAT. Имеет меньшие размеры кластеров, тем самым позволяя более удобно и эффективно расходовать пространство жесткого диска.

HPFS - (high performance file system) эта файловая система была создана специально под операционную систему OS/2 и стала попыткой решить все проблемы, которые возникали с FAT, но она так и не оправдала надежд.

NTFS - очередная попытка исправить ошибки, связанные с FAT, и, по большинству параметров, удачная. Впервые она начала использоваться с Windows NT для сетевых целей. Главной отличительной чертой ее является обеспечение надежности и безопасности. В документации по Windows XP рекомендуют использовать именно эту файловую систему.

CDFS - файловая система, специально созданная для работы с CD-ROM-дисками.

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

GetLogicalDriveString(). Сначала разберем первую:

WINBASEAPI DWORD WINAPI
GetLogicalDrive (VOID)

Как видно из описания, эта функция возвращает всего одно значение в переменную типа DWORD. Для получения имен дисков из этой переменной можно использовать следующее правило: нулевой бит соответствует диску А:, первый бит диску В:, третий бит диску С: и так далее по аналогии.

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

WINBASEAPI DWORD WINAPI GetLogicalDriveString (DWORD nBufferLength, LPSTR lpBuffer)

Здесь первый параметр nBufferLength - это длина буфера, а второй параметр LPSTR lpBuffer - это сам буфер, в который будет происходить копирование всех найденных на компьютере дисков. Функция в случае неудачи возвращает нуль, в случае же успешного выполнения - количество занесенных в буфер символов дисков. В буфер lpBuffer заносится несколько строк, каждая из которых содержит знак диска. (Надо заметить, что буфер является указателем на строку). Существует также много различных функций, разработанных для получения более подробной информации о дисках, описания которых можно найти в документации.

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

SetCurrentDirectory()и
GetCurrentDirectory().

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

WINBASEAPI DWORD WINAPI GetCurrentDirectory (DWORD nBufferLength, LPSTR lpBuffer)

По аналогии с предыдущей функцией параметры рассматриваемой сейчас функции идентичны, за исключением того, что в строковый буфер lpBuffer записываются не имена дисков, а путь к текущей папке, nBufferLength - это также длина буфера. Если путь к текущей папке не получен и произошла ошибка, то GetCurrentDirectory() возвращает нуль. Эту функцию очень удобно использовать в начале инициализации программы, тем самым копируя в строку путь папки, из которой запустили программу.

Вторая функция, служащая для назначения текущей папки, описывается так:

WINBASEAPI BOOL WINAPI SetCurrentDirectory (LPSTR lpBuffer)

Она содержит один-единственный параметр - указатель на строку lpBuffer, который должен содержать путь к папке, которую необходимо сделать текущей. Если работа функции завершилась удачно, то она возвращает 1 (TRUE), если же произошла ошибка, то 0 (FALSE).

Теперь мы знаем, как переходить из папки в папку, с диска на диск, но этого недостаточно. Для того чтобы узнать, какие файлы находятся в папках, необходимо использовать дополнительные функции FindFirstFile() и FindNextFile(). Чаще всего их используют вместе.

Функция FindFirstFile() используется для нахождения файлов в текущей папке, работа с которой подробно рассмотрена выше. Описание этой функции поможет понять смысл и приемы применения:

WINBASEAPI HANDLE WINAPI FindFirstFile (LPCSTR lpFileName, LPWIN32_FIND_DATA 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;

Основными полями, которые нам понадобятся для написания программы, являются cFileName - строка, в которой будет содержаться полный путь к файлу, dwFileAttributes - куда записываются атрибуты файла, и для любознательных: ftCreationTime - время создания файла, ftLastAccessTime и ftLastWriteTime - время последнего доступа и записи в файл. Поля dwReserved0 и dwReserved1 - зарезервированы Microsoft для будущего использования, в нашем случае на них не надо заострять внимания. (Самые новые обновления информации о функциях, структурах вы можете найти на сайте Microsoft Developer Network - MSDN.COM).

Использование функции FindNextFile() напрямую связано с FindFirstFile(). Дело в том, что последняя возвращает некоторое значение типа HANDLE, то есть идентификатор, и он, если нам необходимо найти не один файл, а группу, заданную по маске, передается в функцию FindNextFile, которая находит следующий соответствующий файл, записывая информацию о нем в структуру WIN32_FIND_DATA. Описание функции FindNextFile() выглядит так:

WINBASEAPI BOOL WINAPI FindNextFile (HANDLE hFindFile, LPWIN32_FIND_DATA lpFindFileData)

Если файлов больше не найдено, то функция возвращает FALSE, так что необходимо проверять возвращаемое значение во избежание сбоев в работе программы и целостности информации. Обязательным также является использование функции FindClose(), которая корректно завершает работу со значением типа HANDLE, возвращаемой функцией FindFirstFile(). Ниже представлено описание FindClose():

WINBASEAPI BOOL WINAPI FindClose (HANDLE hFindFile)

Мы уже описали почти все функции, которые нам понадобятся для практической части, но, допустим, мы уже сделали программу, и теперь встает вопрос о том, как сделать так, чтобы наша программа возвращала путь к выбранному файлу. Эту задачу можно будет разрешить при помощи функции GetFullPathName():

WINBASEAPI DWORD WINAPI GetFullPathName (LPCTSTR lpFileName, DWORD nBufferLength, LPTSTR lpBuffer, LPTSTR *lpFilePart)

Разберемся с аргументами этой функции: первый аргумент, lpFileName, представляет собой указатель на строку, содержащую имя файла в текущей папке, второй аргумент предназначен для указания длины строкового буфера lpBuffer, в который, в свою очередь, будет записан полный путь файла. Предназначение четвертого параметра остается неясным, так как в этот указатель на строку передается первый символ имени файла. Чаще всего при использовании GetFullPathName() в этот параметр передается NULL.

Наконец, подошли к последней, описываемой в этой статье функции:

WINBASEAPI DWORD WINAPI GetLastError ()

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

Практика

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

- Диалоговое окно и функция для обработки его событий

- На диалоговом окне должны содержаться элементы управления: ListBox, в который будут заноситься имена файлов, назовем его просто IDC_LIST1, два элемента ComboBox, под именем IDC_COMBOF и IDC_COMBO, содержащие имена дисков и фильтры для поиска файлов, кнопки OК и Cancel, соответственно, c идентификаторами IDOK и IDCANCEL, и последняя метка IDC_STATIC1, показывающая имена выбранного файла. (Диалоговое окно показано на рисунке 1.)

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

void FindListFile(HWND hdlg, char st[],char cBufferCurDir[256])
{
WIN32_FIND_DATA FileData;
HANDLE hSearch;
BOOL fFinished = FALSE;
SetCurrentDirectory(cBufferCurDir);
hSearch = FindFirstFile(st, &FileData);
if(hSearch==
INVALID_HANDLE_VALUE)
{
MessageBox (hdlg,
"Файлов с указанным именем не найдено.",st,MB_OK);
}
else
{
while (!fFinished)
{
if (!FindNextFile(hSearch,
&FileData))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
fFinished = TRUE;
}
if(!fFinished) SendDlgItemMessage(hdlg,IDC_LIST1, LB_ADDSTRING,0,(LONG)(LPSTR)
FileData.cFileName);
}
FindClose(hSearch);
}
}

Если вы внимательно читали статью, то сразу поймете, что здесь происходит, если же возникают проблемы, то поясню, что в функцию передаются: идентификатор окна, на котором находится элемент управления IDC_LIST1, далее фильтр, по которому происходит поиск. Ну а третий параметр - это путь к папке, которая становится текущей, и в ней происходит поиск файлов. Алгоритм, в принципе, понять несложно. По аналогии, нам требуется функция для получения списка дисков, написание которой я оставляю на вас, напомню только, что здесь можно проявить фантазию, используя функции GetLogicalDrive() и GetLogicalDriveString(). Допустим, она будет носить название FindListDrive().

Определим переменные, которые понадобятся для работы в самом диалоговом окне. Объявляем их в функции обработки сообщений от окна:

char st[20];
// Строка для служебных операциий
char cBuffer
CurDir[256]={"C:\\"};
// Содержит директорию по умолчанию
char cFiltersFile[5]={"*.*"};
// Содержит фильтр по умолчанию
char cFileSelName[256]={" "};
// Имя выбранного файла
char cFileFullPath[256]={" "};
// Полное имя выбранного файла
int iNumberString=0;
// Далее идут переменные, обозначающие
int iNumberFilter=0;
// положения в элементах управления
int iNumberDriver=0;

После этого в функции диалогового окна в разделе WM_INITDIALOG вписываем команды:

SetCurrentDirectory
(cBufferCurDir);
FindListFile
(hdlg,cFiltersFile,cBufferCurDir);
FindListDrive(hdlg);
SendDlgItemMessage
(hdlg,IDC_COMBO,
CB_SETCURSEL, 1,0);
SetDlgItemText
(hdlg,IDC_STATIC1,cBufferCurDir);
SendDlgItemMessage
(hdlg,IDC_COMBOF, CB_ADDSTRING, 0,(LONG)(LPSTR)"*.dat");
SendDlgItemMessage
(hdlg,IDC_COMBOF, CB_ADDSTRING, 0,(LONG)(LPSTR)"*.txt");
SendDlgItemMessage
(hdlg,IDC_COMBOF, CB_ADDSTRING, 0,(LONG)(LPSTR)"*.*");
SendDlgItemMessage
(hdlg,IDC_COMBOF, CB_SETCURSEL, iNumberFilter,0);

Теперь, после того как заполнили списки файлов, дисков и фильтров, указали текущую директорию написанными выше операторами, нам нужно обработать сообщения от элементов управления. К рассматриваемым сообщениям можно отнести такие, как LBN_SELCHANGE, CBN_SELCHANGE и LBN_DBLCLK, возникающие соответственно при выборе из списка ListBox, при выборе из выпадающего списка ComboBox и при двойном клике мышью на одной из строк списка ListBox. Ниже представлен код, обрабатывающий сообщения от IDC_LIST1, содержащего имена файлов:

case IDC_LIST1:
switch
(GET_WM_COMMAND_CMD
(wParam, lParam))
{
сase LBN_SELCHANGE:
iNumberString=
SendDlgItemMessage
(hdlg,IDC_LIST1,
LB_GETCURSEL, 0,0L);
SendDlgItemMessage
(hdlg,IDC_LIST1,LB_GETTEXT,
iNumberString,(LONG)(LPSTR)
cFileSelName);
GetFullPathName
(cFileSelName,256,cFileFullPath,NULL);
SetDlgItemText
(hdlg,IDC_STATIC1,cFileFullPath);
break;
case LBN_DBLCLK: GetSrcDir(hdlg,iNumberString,
cFileSelName,cFileFullPath,
cBufferCurDir,cFiltersFile);
break;
}
break;

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

void GetSrcDir(HWND hdlg,
int iNumberString,
char cFileSelName[256],
char cFileFullPath[256],
char cBufferCurDir[256],
char cFiltersFile[])
{
DWORD dDir=0;
iNumberString=
SendDlgItemMessage
(hdlg,IDC_LIST1, LB_GETCURSEL, 0,0L); SendDlgItemMessage
(hdlg,IDC_LIST1,LB_GETTEXT,
iNumberString,(LONG)(LPSTR)
cFileSelName);
dDir=
GetFileAttributes(cFileSelName);
if(dDir==FILE_ATTRIBUTE_DIRECTORY)
{ GetFullPathName
(cFileSelName,256,cFileFullPath,NULL);
SetDlgItemText
(hdlg,IDC_STATIC1,cFileFullPath);
strcpy
(cBufferCurDir,cFileSelName);
SendDlgItemMessage
(hdlg,IDC_LIST1,LB_RESETCONTENT,0,0);
FindListFile
(hdlg,cFiltersFile,cBufferCurDir);
}
}

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

case IDC_COMBO:
switch
(GET_WM_COMMAND_CMD
(wParam, lParam))
{
case CBN_SELCHANGE:
iNumberDriver=
SendDlgItemMessage
(hdlg,IDC_COMBO, CB_GETCURSEL, 0,0L); SendDlgItemMessage
(hdlg,IDC_COMBO,CB_GETLBTEX
T,iNumberDriver,
(LONG)(LPSTR)cBufferCurDir);
SendDlgItemMessage
(hdlg,IDC_LIST1,LB_RESETCONTENT,0,0); SendDlgItemMessage
(hdlg,IDC_COMBOF,CB_GETLBTEXT,0,
(LONG)(LPSTR)cFiltersFile);
FindListFile
(hdlg,cFiltersFile,cBufferCurDir);
SetDlgItemText
(hdlg,IDC_STATIC1,cBufferCurDir); break;
}
break;

И для IDC_COMBOF, в этом компоненте, напомню, находится список фильтров:

case IDC_COMBOF:
switch
(GET_WM_COMMAND_CMD(wParam, lParam))
{
case CBN_SELCHANGE:
iNumberFilter=
SendDlgItemMessage
(hdlg,IDC_COMBOF, CB_GETCURSEL, 0,0L); SendDlgItemMessage
(hdlg,IDC_COMBOF,CB_GETLBTEXT,
iNumberFilter,(LONG)(LPSTR)
cFiltersFile); GetDlgItemText
(hdlg,IDC_STATIC1,cBufferCurDir,256); SendDlgItemMessage
(hdlg,IDC_LIST1,LB_RESETCONTENT,0,0);
FindListFile
(hdlg,cFiltersFile,cBufferCurDir);
break;
}
break;

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

Заключение

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

Если у вас возникли какие-либо вопросы или предложения по статье или просто имеется интересная информация по WinApi и, в общем, о программировании на Visual C++, пишите на e-mail. Кстати, если не получилось написать программу, то могу выслать исходный код с комментариями, для этого также пишите по электронной почте.



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