Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 13, М.: Диалог-МИФИ, 1993, 284 стр. 4.2. Функции Windows для работы с файламиС помощью функций, входящих в состав программного интерфейса операционной системы Windows вы можете выполнять над файлами те же операции, что и в среде MS-DOS. Это открытие, закрытие, чтение, запись, позиционирование, удаление и т. п. Открытие файловДля открытия файлов вы можете воспользоваться универсальной функцией OpenFile или более простой (но и более ограниченной) функцией _lopen. Приложения Windows могут воспользоваться функцией OpenFile , которая предназначена для создания, открытия, повторного открытия и удаления файлов. Приведем прототип этой функции: HFILE WINAPI OpenFile( LPCSTR lpszFileName, // путь к файлу OFSTRUCT FAR* lpOpenStruct, // адрес структуры OFSTRUCT UINT fuMode); // режим работы и атрибуты Функция возвращает идентификатор файла, который можно (и нужно) использовать во всех последующих операциях с файлом или -1 при ошибке. Параметр lpszFileName является указателем на текстовую строку в кодировке ANSI, содержащую путь к файлу и закрытую двоичным нулем. В имени файла не допускается указывать символы шаблона, такие как "*" и "?". Через параметр lpOpenStruct передается адрес структуры OFSTRUCT, которая заполняется информацией при первом открытии файла. Параметр fuMode используется для определения действий, выполняемых функцией OpenFile, а также атрибуты файла. Приведем список возможных значений для этого параметра.
Когда функция OpenFile вызывается в первый раз для открытия файла, она заполняет структуру OFSTRUCT , описанную в файле windows.h следующим образом: typedef struct tagOFSTRUCT { BYTE cBytes; BYTE fFixedDisk; UINT nErrCode; BYTE reserved[4]; char szPathName[128]; } OFSTRUCT; Поле cBytes содержит размер самой структуры OFSTRUCT в байтах. С помощью поля fFixedDisk приложение может определить, находится ли открытый файл на жестком диске или на флоппи-диске. если содержимое этого поля отлично от нуля, для хранения файла используется жесткий диск. Если при открытии файла произошла ошибка, в поле nErrCode записывается код ошибки. Возможные значения для кода ошибки приведены в приложении 1. Поле reserved зарезервировано и не должно использоваться. В поле szPathName находится полный путь к файлу в кодировке OEM. Если функция OpenFile показалась вам слишком сложной в использовании, в ряде случаев для открытия файла вы сможете ограничиться функцией _lopen : HFILE WINAPI _lopen(LPCSTR lpszFileName, int fnOpenMode); Функция возвращает идентификатор открытого файла или HFILE_ERROR при ошибке. Параметр lpszFileName, так же как и для функции OpenFile, является указателем на текстовую строку в кодировке ANSI, содержащую путь к файлу и закрытую двоичным нулем. В имени файла не допускается указывать символы шаблона, такие как "*" и "?". Параметр fuOpenMode определяет режим, в котором открывается файл. Приведем список возможных значений для этого параметра.
Если вам надо открыть файл в каталоге, где находится сама операционная система Windows или в системном каталоге Windows, воспользуйтесь функциями, соответственно, GetWindowsDirectory и GetSystemDirectory. Функция GetWindowsDirectory позволяет определить расположение каталога, в который была установлена операционная система Windows: UINT WINAPI GetWindowsDirectory( LPSTR lpSysPath, // адрес буфера UINT cbSysPath); // размер буфера Параметр lpSysPath является указателем на буфер размером не менее 144 байт, в который будет записан путь к искомому каталогу. С помощью параметра cbSysPath необходимо указать размер буфера в байтах. Учтите, что операционная система Windows может быть установлена в локальном и сетевом варианте. В локальном варианте пользователь имеет доступ на запись как к тому каталогу, в который установлена операционная система Windows, так и к системному каталогу Windows. В сетевом варианте системный каталог Windows расположен на сервере и обычный пользователь имеет в этом каталоге права на чтение, но не на запись. Для определения пути к системному каталогу Windows предназначена функция GetSystemDirectory : UINT WINAPI GetSystemDirectory( LPSTR lpSysPath, // адрес буфера UINT cbSysPath); // размер буфера Назначение параметров этой функции аналогично назначению параметров функции GetWindowsDirectory. Так как системный каталог Windows может находиться на сервере, приложение не должно пытаться создавать или изменять файлы в этом каталоге. Как правило, пользователь не имеет права записи в системный каталог Windows. Стандартные диалоговые панели для открытия файловВ составе операционной системы Windows версии 3.1 имеется DLL-библиотека commdlg.dll, экспортирующая среди прочих две функции, очень удобные для организации пользовательского интерфейса при открытии файлов. Это функции GetOpenFileName и GetSaveFileName . Мы уже пользовались этими функциями в приложениях OEM2ANSI и OEM3ANSI. Функция GetOpenFileName выводит на экран стандартную или измененную приложением диалоговую панель "Open", позволяющую выбрать файл (рис. 4.1).
Рис. 4.1. Диалоговая панель "Open" Функция GetSaveFileName выводит стандартную или измененную приложением диалоговую панель "Save As..." (рис. 4.2).
Рис. 4.2. Диалоговая панель "Save As..." Внешний вид этих диалоговых панелей определяется структурой типа OPENFILENAME , определенной в файле commdlg.h (этот файл находится в каталоге include системы разработки Borland C++ или Microsoft Visual C++): typedef struct tagOFN { DWORD lStructSize; HWND hwndOwner; HINSTANCE hInstance; LPCSTR lpstrFilter; LPSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPSTR lpstrFile; DWORD nMaxFile; LPSTR lpstrFileTitle; DWORD nMaxFileTitle; LPCSTR lpstrInitialDir; LPCSTR lpstrTitle; DWORD Flags; UINT nFileOffset; UINT nFileExtension; LPCSTR lpstrDefExt; LPARAM lCustData; UINT (CALLBACK *lpfnHook)(HWND, UINT, WPARAM, LPARAM); LPCSTR lpTemplateName; } OPENFILENAME; Адрес структуры передается функциям GetOpenFileName и GetSaveFileName в качестве параметра lpofn: BOOL WINAPI GetOpenFileName(OPENFILENAME FAR* lpofn); BOOL WINAPI GetSaveFileName(OPENFILENAME FAR* lpofn); Обе функции возвращают ненулевое значение, если пользователь сделал выбор файла, и ноль, если он отказался от выбора, нажав кнопку "Cancel" или выбрав строку "Close" из системного меню диалоговой панели. Нулевое значение возвращается также при возникновении ошибки. В результате выбора некоторые поля структуры будут заполнены информацией о выбранном файле. Опишем назначение отдельных полей структуры OPENFILENAME. lStructSizeПоле lStructSize перед вызовом функций должно содержать размер структуры OPENFILENAME в байтах. FlagsПоле Flags позволяет задать различные режимы выбора файла, влияющие на внешний вид диалоговой панели. Приведем список флагов, комбинации которых можно использовать для заполнения этого поля.
hwndOwnerПоле hwndOwner должно содержать идентификатор окна, создавшего диалоговую панель. Можно указать значение NULL, при этом диалоговая панель не будет иметь окно-владельца. В этом случае нельзя использовать флаг OFN_SHOWHELP. hInstanceПоле hInstance используется перед вызовом функции для идентификации блока памяти, содержащего шаблон диалоговой панели. Содержимое поля игнорируется в том случае, если не указаны флаги OFN_ENABLETEMPLATE или OFN_ENABLETEMPLATEHANDLE. lpstrFilterВ поле lpstrFilter должен быть указан адрес текстовой строки, задающей фильтр для выбора имен файлов (шаблоны имен файлов). Согласно описанию структуры OPENFILENAME фильтр должен состоять из одной или нескольких расположенных непосредственно друг за другом пар текстовых строк, закрытых двоичным нулем. Последняя строка должна иметь на конце два двоичных нуля. Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов. lpstrCustomFilterВ поле lpstrCustomFilter должен быть указан адрес текстовой строки, задающей фильтр для выбора имен файлов (шаблон для выбора файлов). Согласно описанию структуры OPENFILENAME фильтр должен состоять из одной пары текстовых строк, закрытых двоичным нулем. Последняя строка должна иметь на конце два двоичных нуля. Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов. Если поле lpstrFilter содержит NULL, используется фильтр lpstrCustomFilter. nMaxCustFilterОпределяет размер буфера в байтах, указанного в поле lpstrCustomFilter. Размер этого буфера должен быть не меньше 40 байт. nFilterIndexПоле nFilterIndex определяет номер пары строк, используемой для фильтра, указанного в поле lpstrFilter. Если в качестве значения для этого поля указать 0, будет использован фильтр, определенный в поле lpstrCustomFilter. lpstrFileПоле lpstrFile должно содержать адрес текстовой строки, в которую будет записан полный путь к выбранному файлу. Если по указанному выше адресу перед вызовом функции GetOpenFileName или GetSaveFileName расположить текстовую строку, содержащую путь к файлу, этот путь будет выбран по умолчанию сразу после отображения диалоговой панели "Open" или "Save As...". nMaxFileПоле nMaxFile должно содержать размер в байтах буфера, расположенного по адресу, указанному в поле lpstrFile. Размер этого буфера должен быть достаточным для записи полного пути к файлу. Файловая система MS-DOS допускает использование для указания пути к файлу не более 128 символов. lpstrFileTitleВ поле lpstrFileTitle необходимо записать адрес буфера, в который после выбора будет записано имя файла с расширением, но без пути к файлу. Это поле должно быть использовано приложением для отображения имени выбранного файла. nMaxFileTitleПоле nMaxFileTitle должно содержать размер указанного выше буфера. lpstrInitialDirПоле lpstrInitialDir позволяет указать начальный каталог, который будет выбран для поиска файла сразу после отображения диалоговой панели "Open". Для того чтобы начать поиск в текущем каталоге, в это поле следует записать значение NULL. lpstrTitleС помощью этого поля можно определить заголовок диалоговой панели, появляющейся при вызове функции. Если это поле содержит NULL, будут использованы стандартные заголовки "Open" и "Save As...". nFileOffsetПосле возврата из функции это поле содержит смещение первого символа имени файла относительно начала буфера lpstrFile. nFileExtensionПосле возврата из функции это поле содержит смещение первого символа расширения имени файла относительно начала буфера lpstrFile. lpstrDefExtУказатель на буфер, который содержит расширение имени файла, используемое по умолчанию. Это расширение добавляется к имени выбранного файла, если при выборе расширение имени не было указано. lCustDataЗначение, передаваемой функции фильтра через параметр lParam. lpfhHookУказатель на функцию фильтра, обрабатывающую сообщения для диалоговой панели. Функция фильтра будет вызываться только в том случае, если указан флаг OFN_ENABLEHOOK. Если фильтр не обрабатывает сообщение, он должен вернуть нулевое значение. Если же он вернет значение, отличное от нуля, стандартная функция диалога не будет обрабатывать сообщение, которое уже было обработано функцией фильтра. lpTemplatenameИдентификатор ресурса, содержащего шаблон диалоговой панели, используемого вместо имеющегося в DLL-библиотеке commdlg.dll. Для ссылки на ресурс можно использовать макрокоманду MAKEINTRESOURCE. Для использования альтернативного шаблона (и, соответственно, данного поля), в поле Flags следует установить флаг OFN_ENABLETEMPLATE. Закрытие файловТеперь о том, как закрыть файл. Для закрытия файла вы должны использовать функцию _lclose : HFILE WINAPI _lclose(HFILE hf); Идентификатор закрываемого файла передается функции через параметр hf. Если файл закрыт успешно, функция _lclose возвращает нулевое значение. При ошибке возвращается значение HFILE_ERROR. Создание файловДля создания файлов вы можете использовать как универсальную функцию OpenFile, описанную нами ранее, так и более простую функцию _lcreat : HFILE WINAPI _lcreat(LPCSTR lpszFileName, int fuAttribute); В качестве параметра lpszFileName этой функции необходимо передать адрес строки, содержащей путь к создаваемому файлу в кодировке ANSI. С помощью параметра fuAttribute можно определить атрибуты создаваемого файла:
Если указанный первым параметром файл не существует, функция _lcreat создает его и открывает для записи, возвращая идентификатор файла. Если файл существует, он обрезается до нулевой длины и затем открывается для чтения и записи. Чтение и записьДля выполнения операций чтения и записи в программном интерфейсе операционной системы Windows версии 3.1 предусмотрены четыре функции: _lread, _hread, _lwrite, _hwrite. Функция _lread предназначена для чтения из открытого файла: UINT WINAPI _lread( HFILE hf, // идентификатор файла void _huge* hpvBuffer, // адрес буфера UINT cbBuffer); // размер буфера Функция возвращает количество байт данных, прочитанных из файла. Возвращенное значение может быть меньше затребованного в параметре cbBuffer, если в процессе чтения был достигнут конец файла. При ошибке функция возвращает значение HFILE_ERROR. Через параметр hf функции следует передать идентификатор файла, для которого необходимо выполнить операцию чтения. Прочитанные данные будут записаны в буфер hpvBuffer, имеющий размер cbBuffer байт. Этот буфер можно получить динамически, вызывав, например, функцию GlobalAlloc или LocalAlloc. Размер буфера не должен превышать 65534 байт. В программном интерфейсе операционной системы Windows версии 3.1 появилась функция _hread , с помощью которой можно выполнять чтение из файла блоков практически любого размера: long WINAPI _hread( HFILE hf, // идентификатор файла void _huge* hpvBuffer, // адрес буфера long cbBuffer); // размер буфера Так же как и функция _lread, функция _hread возвращает количество байт данных, прочитанных из файла. Возвращенное значение может быть меньше затребованного в параметре cbBuffer, если в процессе чтения был достигнут конец файла. При ошибке функция возвращает значение HFILE_ERROR. Вы можете с помощью функции GlobalAlloc заказать для функции _hread буфер размером, большим 64 Кбайт. С помощью функции _lwrite вы можете выполнить запись данных в файл: UINT WINAPI _lwrite( HFILE hf, // идентификатор файла void _huge* hpvBuffer, // адрес буфера UINT cbBuffer); // размер буфера Назначение параметров этой функции аналогично назначению параметров функции _lread. Перед вызовом функции _lwrite буфер должен содержать записываемые в файл данные. Функция возвращает количество байт данных, записанных в файл, или значение HFILE_ERROR при ошибке. Если вам надо писать в файл блоки, имеющие размер больше 64 Кбайт, воспользуйтесь функцией _hwrite , которая впервые появилась в программном интерфейсе Windows версии 3.1: long WINAPI _hwrite( HFILE hf, // идентификатор файла void _huge* hpvBuffer, // адрес буфера long cbBuffer); // размер буфера Назначение параметров функции аналогично назначению параметров функции _lwrite. Функция возвращает количество байт данных, записанных в файл, или значение HFILE_ERROR при ошибке. ПозиционированиеДля выполнения операции позиционирования внутри файла приложения Windows могут использовать функцию _llseek : LONG WINAPI _llseek( HFILE hf, // идентификатор файла LONG lOffset, // смещение int nOrigin); // способ использования смещения Функция _llseek перемещает указатель текущей позиции в файле на lOffset байт, причем направление смещения зависит от значения параметра nOrigin следующим образом:
Таким образом, с помощью этой функции вы можете обеспечить прямой доступ к файлу. Функция возвращает новое значение текущей позиции от начала файла или HFILE_ERROR при ошибке. Определение типа устройства ввода/выводаИногда приложению требуется определить тип и расположение используемого дискового устройства ввода/вывода. Для этого можно воспользоваться функцией GetDriveType : UINT WINAPI GetDriveType(int DriveNumber); Параметр DriveNumber определяет номер диска, для которого требуется определить тип и расположение (0 соответствует устройству A:, 1 - B:, и т. д.). Функция может вернуть 0 при ошибке или одно из следующих значений:
Использование стандартной библиотеки транслятораДля работы с файлами в приложениях Windows вы можете использовать функции из стандартной библиотеки транслятора C или C++, такие как read , fread , write , fwrite , lseek , fstat , tell , close . Можно также использовать функции потокового ввода/вывода , если преобразовать идентификатор файла в указатель на структуру FILE при помощи функции fdopen . Для того чтобы закрыть такой файл следует воспользоваться функцией fclose . Перечисленные выше функции были описаны в третьей книге первого тома "Библиотеки системного программиста" в главе "Файловая система DOS". Проверка присутствия share.exeКак мы уже говорили, в многозадачной среде утилита MS-DOS share.exe приобретает особое значение, выступая координатором доступа работающих параллельно приложений к файлам. Для того чтобы приучить забывчивых или беспечных пользователей не удалять команду загрузки этой утилиты из файла autoexec.bat вы можете сделать так, чтобы ваше приложение выдавала предупреждающее сообщение, если утилита share.exe не загружена. Однако проблема не так проста, как кажется. Для того чтобы определить, загружена ли утилита share.exe , программы MS-DOS могли воспользоваться функцией 1000h прерывания INT 2Fh. Эта функция используется самой утилитой share.exe для предотвращения повторной загрузки. Но вызвав эту функцию из приложения Windows, вы можете, к своему огорчению, убедиться, что она всегда сообщает о том, что share.exe загружена в память, даже если вы вообще стерли файл share.exe с диска. Это сделано специально, но не для того чтобы затруднить обнаружение share.exe, а для того чтобы предотвратить ее загрузку из виртуальной машины MS-DOS, работающей в среде Windows. Мы, однако, можем найти выход из этого затруднительного положения, если для определения присутствия share.exe попробуем использовать одну из функций, для выполнения которой она предназначена. В качестве такой функции проще всего использовать блокирование участка файла (функция 0x5c прерывания INT 21h). Как мы уже говорили, Windows выполняет эмуляцию большого числа функций прерывания MS-DOS, позволяя приложениям Windows обращаться к этим функциям. В листинге 4.1 приведены исходные тексты приложения ISSHARE, которое проверяет присутствие share.exe, выполняя попытку заблокировать первый байт созданного временного файла. Листинг 4.1. Файл isshare/isshare.cpp // ===================================================== // Приложение определяет, загружена ли утилита MS-DOS // share.exe, и выводит соответствующее сообщение // ===================================================== #define STRICT #include <windows.h> #include <windowsx.h> #include <dos.h> int ShareLoaded(void); #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { int rc; // Проверяем, загружена ли share.exe rc = ShareLoaded(); if (!rc) { // Если код возврата равен -1, функция не смогла // создать на диске временный файл, необходимый // для выполнения проверки if(rc == -1) { MessageBox(NULL, "File creation error", "", MB_OK); } // Если код возврата равен 0, share.exe не загружена else { MessageBox(NULL, "Share NOT loaded", "", MB_OK); } } // Если код возврата равен 1, share.exe загружена else { MessageBox(NULL, "Share loaded", "SHARE Test", MB_OK); } return 0; } // ------------------------------------------------------- // Функция ShareLoaded // Проверяет, загружена ли утилита share.exe // ------------------------------------------------------- int ShareLoaded(void) { HFILE hfTempFile; OFSTRUCT ofs; char szBuf[144]; union REGS regs; int rc; // Создаем временный файл на диске GetTempFileName(0, "tst", 0, szBuf); hfTempFile = _lcreat(szBuf, 0); // Если файл создать не удалось, возвращаем -1 if (hfTempFile == HFILE_ERROR) { return(-1); } // Пытаемся заблокировать первый байт созданного файла regs.x.bx = hfTempFile; // идентификатор файла regs.h.ah = 0x5c; // код функции MS-DOS regs.h.al = 0; // код операции блокирования regs.x.cx = 0; // CX:DX - смещение в файле regs.x.dx = 0; regs.x.si = 0; // SI:DI - длина блокируемой области regs.x.di = 1; // Вызываем функцию MS-DOS intdos(®s, ®s); // Если установлен флаг переноса, выполнение блокирования // невозможно. Считаем, что в этом случае // утилита share.exe не загружена if(regs.x.cflag == 1) { rc = 0; } // Если блокирование прошло успешно, // разблокируем и удаляем временный файл else { regs.x.bx = hfTempFile; regs.h.ah = 0x5c; regs.h.al = 1; // код операции разблокирования regs.x.cx = 0; regs.x.dx = 0; regs.x.si = 0; regs.x.di = 1; intdos(®s, ®s); rc = 1; } // Закрываем временный файл _lclose(hfTempFile); // Удаляем временный файл OpenFile(szBuf, &ofs, OF_DELETE); return rc; } Функция WinMain проверяет, загружена ли утилита share.exe, вызывая функцию ShareLoaded, определенную в приложении. Эта функция может вернуть 0, 1 или -1. Если функция вернула 0, share.exe не загружена. Если функция вернула -1, произошла ошибка при создании временного файла. И, наконец, если функция ShareLoaded вернула 1, share.exe загружена. Для создания временного файла используется функции GetTempFileName и _lcreat. Первая из этих двух функций получает имя временного файла, вторая - создает и открывает временный файл. Для выполнения блокировки приложение вызывает функцию MS-DOS, пользуясь известной вам функцией intdos. Если утилита share.exe установлена и блокировка файла выполнена успешно, функция ShareLoaded разблокирует файл, вызывая функцию MS-DOS с кодом 0x5c еще раз, но с другим значением регистра AL. В любом случае перед возвратом из функции временный файл закрывается функцией _lclose и затем удаляется функцией OpenFile. Файл определения модуля для приложения ISSHARE приведен в листинге 4.2. Листинг 4.2. Файл isshare/isshare.def ; ============================= ; Файл определения модуля ; ============================= NAME ISSHARE DESCRIPTION 'Приложение ISSHARE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 4096 CODE preload moveable discardable DATA preload moveable multiple |