Операционная система Windows версии 3.1, к сожалению, не имеет своей собственной файловой системы. Приложения Windows работают с файловой системой MS-DOS, поэтому они "скованы" ограничениями этой файловой системы. В добавок Windows накладывает свои ограничения, связанные с мультизадачным режимом работы.
Последнее обстоятельство имеет большое значение. Если программа MS-DOS работала с файлами в монопольном режиме, то в операционной системе Windows несколько приложений могут выполнять обращение к одному файлу. Если приложения не будут учитывать возможность доступа к файлам со стороны других приложений, результаты могут оказаться плачевными.
Для предотвращения одновременного доступа на запись к файлам со стороны нескольких приложений можно использовать утилиту MS-DOS share.exe , загрузив ее при помощи файла autoexec.bat. Однако пользователи часто забывают загрузить эту утилиту или не загружают ее специально, надеясь сэкономить таким образом оперативную память.
Если вы разрабатываете приложение, работающее с файлами, вам необходимо убедить пользователя загрузить утилиту share.exe. Здесь возможны разные приемы.
Инсталлятор текстового процессора Microsoft Word for Windows версии 2.0 в процессе установки изменяет файл autoexec.bat, добавляя в него строку загрузки утилиты share.exe.
Однако при оптимизации своей системы пользователь, обнаружив эту строку в файле autoexec.bat после установки текстового процессора, может ее удалить. При этом сам текстовый процессор будет работать нормально (некоторое время).
Поэтому в текстовом процессоре Microsoft Word for Windows версии 6.0 использован более сильный способ "убеждения" пользователей в необходимости утилиты share.exe - если эта утилита не загружена, текстовый процессор выводит на экран сообщение о том, что в системе нет share.exe и отказывается работать.
В этом разделе мы расскажем о тех ограничениях, которые накладываются на методы работы с файлами в мультизадачной среде операционной системы Windows версии 3.1.
Так как работа приложений Windows (и самой операционной системы Windows) основана на передаче и обработке сообщений, вся файловая активность приложения также начинается во время обработки какого-либо сообщения. Например, редактор текста загружает редактируемый файл, когда приходит сообщение от строки "Open..." меню "File".
С файлами можно работать по-разному.
Первый способ (используемый программами MS-DOS) заключается в том, чтобы открыть все нужные им файлы в начале работы программы и закрыть их перед завершением работы программы. Если, однако, программа работает с большим количеством файлов, ей может не хватить управляющих блоков DFCB, используемых MS-DOS для каждого открытого файла (см. 1 том "Библиотеки системного программиста", раздел 2.4 "Таблица файлов MS-DOS"). Количество таких блоков определяется оператором "FILES=" в файле config.sys.
Для более экономного использования управляющих блоков DFCB программы могут открывать и закрывать файлы по мере необходимости, сокращая таким образом количество файлов, открытых одновременно. Это второй способ.
Приложения Windows не должны держать файлы открытыми на протяжении всего времени своей работы. Более того, вся обработка файла должна выполнятся во время обработки одного сообщения. То есть обработчик сообщения должен открыть файл, выполнить над ним все нужные операции ввода/вывода, а затем закрыть файл. Есть и еще одно ограничение - необходимо закрыть все файлы перед выводом на экран немодальной или модальной диалоговой панели (в том числе и перед вызовом функции MessageBox).
Смысл этих ограничений заключается в том, что они не позволяют пользователю переключиться на другое приложение, если текущее приложение не закрыло все свои файлы.
Почему нельзя переключаться на другое приложение, не закрыв предварительно все файлы?
Представьте себе, что ваше приложение открыло файл, расположенный на дискете. После этого пользователь переключился на другое приложение, которое также работает с дискетами. Для него потребовалось установить другую дискету и пользователь это сделал. Затем он снова переключился на первое приложение, которое продолжило, например, прерванную операцию записи. Но теперь эта операция будет выполняться с другой дискетой, в результате чего обе дискеты скорее всего придется форматировать.
Поэтому приложения Windows должны придерживаться следующего правила:
Все операции с файлом, такие как открытие файла, чтение/запись и закрытие, должны выполнятся во время обработки одного сообщения. Нельзя открывать файл в обработчике одного сообщения и закрывать его в обработчике другого сообщения.
Программы MS-DOS иногда создают на диске временные файлы , задавая для них либо фиксированное имя, которое зависит от программы, либо имя, созданное на основе текущей даты и времени.
Первый способ непригоден для приложений Windows, так как в системе могут работать несколько копий одного приложения. Поэтому, если, например, две копии приложения SUPERCAD попытаются создать два временных файла с именем !suprcad.tmp, такая операция получится только у той копии приложения, которая попытается сделать это первой.
В составе программного интерфейса Windows имеется функция GetTempFileName , предназначенная для получения имени временного файла:
int WINAPI GetTempFileName( BYTE bDriveLetter, LPCSTR lpszPrefixString, UINT uUnique, LPSTR lpszTempFileName);
Параметр bDriveLetter задает диск, на котором будет расположен временный файл. Если значение этого параметра равно нулю, временный файл будет создан на текущем диске.
Параметр lpszPrefixString должен содержать указатель на текстовую строку, содержащую префикс, который будет добавлен к имени временного файла. В префиксе необходимо использовать кодировку OEM.
Параметр uUnique должен содержать целое число, которое будет добавлено к префиксу для формирование имени временного файла. Если значение этого параметра равно нулю, Windows будет использовать число, полученное из текущего системного времени.
Подготовленный полный путь для временного файла будет записан в буфер размером не менее 144 байт, адрес которого передается функции через параметр lpszTempFileName. При этом будет использована кодировка OEM.
Функция GetTempFileName возвращает значение, переданное ей через параметр uUnique, или значение, вычисленное исходя из текущего системного времени, если значение параметра uUnique равно нулю.
Учтите, что функция GetTempFileName не создает временные файлы. За создание и удаление временных файлов отвечает само приложение.
Остановимся подробнее на выборе диска для создания временных файлов.
Приложение Windows должно разрешать пользователю назначить каталог, в котором будут расположены временный файлы. В этом случае пользователь может отвести для всех временных файлов отдельный логический или даже физический диск, что благоприятно скажется на производительности файловой системы.
Для назначения каталога в простейшем случае можно использовать переменную среды TEMP, устанавливаемую в файле autoexec.bat.
Если передать функции GetTempFileName в качестве параметра bDriveLetter нулевое значение, для определения диска, на котором будет расположен временный файл, используется следующий алгоритм.
Если определена переменная среды TEMP, для размещения временного файла используется диск, указанный в этой переменной.
Если переменная TEMP не определена, для размещения временного файла используется первый жесткий диск, обычно C:.
Вы можете в параметре bDriveLetter указать дополнительно константу TF_FORCEDRIVE, определенную в файле windows.h как 0x80. В этом случае для размещения файлов используется указанный в этом параметре диск без учета переменной среды TEMP или буквы, которой обозначается первый установленный в системе жесткий диск.
В программном интерфейсе Windows есть еще одна функция, которая может быть использована для создания временных файлов. Это функция GetTempDrive :
BYTE WINAPI GetTempDrive(char unused);
Параметр функции не используется.
Функция GetTempDrive возвращает имя диска, который можно использовать для создания временных файлов. Если в системе есть жесткие диски, функция возвращает букву, соответствующую первому диску (обычно C:). В противном случае возвращается имя текущего диска.
Таким образом, для приложений Windows можно сформулировать еще одно правило.
Для создания временных файлов используйте имена, полученные от функции GetTempFileName. Не используйте фиксированные имена, закодированные внутри приложений, так как в Windows можно запустить несколько копий одного приложения.
Так как для работы с файлами операционная система Windows использует файловую систему MS-DOS, для указания пути к файлам и каталогам необходимо использовать кодировку OEM , принятую в MS-DOS. Так как Windows работает с кодировкой ANSI , в некоторых случаях необходимо выполнять соответствующие преобразования, вызывая функции AnsiToOem и OemToAnsi .
Однако при открытии файла с помощью функций OpenFile и _lopen, определенных в программном интерфейсе Windows, вы можете не беспокоиться о кодировке, так как эти функции выполняют сами все необходимые преобразования.
Итак, следующее правило.
Принимайте во внимание то обстоятельство, что Windows и MS-DOS используют разную кодировку. При необходимости выполняйте преобразования из кодировки ANSI в кодировку OEM и обратно.
С помощью функций, входящих в состав программного интерфейса операционной системы 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, а также атрибуты файла. Приведем список возможных значений для этого параметра.
Константа |
Описание |
OF_READ |
Файл открывается только для чтения |
OF_WRITE |
Файл открывается только для записи |
OF_READWRITE |
Файл открывается для чтения и записи |
OF_SHARE_COMPAT |
Открытие файла в режиме совместимости. В
этом режиме несколько приложений могут
одновременно открыть файл, причем все эти
приложения должны открывать файл в режиме
совместимости |
OF_SHARE_EXCLUSIVE |
Файл открывается в монопольном режиме.
Для всех других приложений доступ к этому файлу
на чтение и запись запрещен |
OF_SHARE_DENY_WRITE |
После открытия файла к нему запрещается
доступ со стороны других приложений на запись |
OF_SHARE_DENY_READ |
После открытия файла к нему запрещается
доступ со стороны других приложений на чтение |
OF_SHARE_DENY_NONE |
Для открываемого файла не запрещается
доступ к файлу ни на чтение, ни на запись |
OF_PARSE |
Если указан этот флаг, функция OpenFile не
выполняет никаких других действий, кроме
заполнения структуры OFSTRUCT |
OF_DELETE |
Уничтожение существующего файла |
OF_VERIFY |
Если указан этот флаг, функция OpenFile
сравнивает время и дату, записанную в структуре
OFSTRUCT с временем и датой изменений указанного
файла. Если обнаружено несоответствие, функция
OpenFile возвращает значение HFILE_ERROR |
OF_SEARCH |
Операционная система Windows выполняет
поиск файла в каталогах даже в том случае, когда
текстовая строка, указанная параметром lpszFileName,
содержит полный путь к файлу |
OF_PROMPT |
Если указан этот флаг, то в случае
невозможности найти указанный файл Windows выдает
диалоговую панель с предложением вставить в
дисковод A: дискету с файлом. Этот флаг
используется очень редко |
OF_CANCEL |
Флаг OF_CANCEL используется в сочетании с
флагом OF_PROMPT. Если он указан, то в описанную выше
диалоговую панель будет добавлена кнопка
"Cancel", позволяющая отменить открытие файла.
Приложение получит в этом случае код ошибки,
соответствующий ненайденному файлу, а
пользователь - возможность выйти из безвыходного
состояния, в которое он может попасть, не имея под
рукой дискеты с нужным файлом |
OF_CREATE |
Выполняется создание нового файла. Если
указанный файл существует, он обрезается до
нулевой длины |
OF_EXIST |
При указании этого флага функция OpenFile
вначале открывает файл, а затем сразу же его
закрывает. Эта бесполезная на первый взгляд
операция может быть использована для того чтобы
убедиться в существовании указанного файла на
диске |
OF_REOPEN |
Этот флаг используется при повторном
открытии файла на основе информации, хранящейся
в структуре OFSTRUCT |
Когда функция 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 определяет режим, в котором открывается файл. Приведем список возможных значений для этого параметра.
Константа |
Описание |
READ |
Файл открывается только для чтения |
WRITE |
Файл открывается только для записи |
READWRITE |
Файл открывается для чтения и записи |
OF_SHARE_COMPAT |
Открытие файла в режиме совместимости. В
этом режиме несколько приложений могут
одновременно открыть файл, причем все эти
приложения должны открывать файл в режиме
совместимости |
OF_SHARE_EXCLUSIVE |
Файл открывается в монопольном режиме.
Для всех других приложений доступ к этому файлу
на чтение и запись запрещен |
OF_SHARE_DENY_WRITE |
После открытия файла к нему запрещается
доступ со стороны других приложений на запись |
OF_SHARE_DENY_READ |
После открытия файла к нему запрещается
доступ со стороны других приложений на чтение |
OF_SHARE_DENY_NONE |
Для открываемого файла не запрещается
доступ к файлу ни на чтение, ни на запись |
Если вам надо открыть файл в каталоге, где находится сама операционная система 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 перед вызовом функций должно содержать размер структуры OPENFILENAME в байтах.
Поле Flags позволяет задать различные режимы выбора файла, влияющие на внешний вид диалоговой панели. Приведем список флагов, комбинации которых можно использовать для заполнения этого поля.
Флаг |
Описание |
OFN_ALLOWMULTISELECT |
Разрешается выбор нескольких файлов
одновременно. Если указан этот флаг, после выбора
поле lpstrFile будет указывать на буфер, заполненный
именами выбранных файлов (или путями к выбранным
файлам), разделенными пробелом |
OFN_CREATEPROMPT |
При использовании этого флага если
указанный файл не существует, создается
диалоговая панель, в которой предлагается
создать файл. Этот флаг устанавливается
автоматически при использовании флагов
OFN_PATHMUSTEXIST и OFN_FILEMUSTEXIST |
OFN_ENABLEHOOK |
Разрешается использовать функцию
фильтра, адрес которой указан в поле lpfnHook |
OFN_ENABLETEMPLATE |
Если указан этот флаг, для создания
диалоговой панели Windows будет использовать
шаблон, определяемый содержимым полей hInstance и
lpTemplateName |
OFN_ENABLETEMPLATEHANDLE |
При использовании этого флага поле hInstance
используется для идентификации блока памяти,
содержащий предварительно загруженный шаблон
диалоговой панели. В этом случае содержимое поля
lpTemplateName игнорируется |
OFN_EXTENSIONDIFFERENT |
Устанавливается после возвращения из
функции и указывает, что расширение
возвращенного имени файла отличается от
заданного в поле lpstrDefExt. Этот флаг не
устанавливается, если перед вызовом функции в
поле lpstrDefExt было записано значение NULL, или если
файл не имеет расширения имени |
OFN_FILEMUSTEXIST |
Можно выбирать только имена тех файлов,
которые существуют. Если в поле "File Name"
диалоговой панели набрать имя несуществующего
файла, на экране появится диалоговая панель с
предупреждающим сообщением |
OFN_HIDEREADONLY |
Убрать переключатель "Read Only" |
OFN_NOCHANGEDIR |
Для выбора используется каталог,
который был текущим при вызове функции |
OFN_NOREADONLYRETURN |
Выбранные файлы не могут иметь атрибут
"только чтение" или располагаться в
защищенном от записи каталоге |
OFN_NOTESTFILECREATE |
Перед завершением работы диалоговой
панели создание файла не выполняется. Не
выполняются и проверки на переполнение диска,
защиту записи или наличие доступа в сети |
OFN_NOVALIDATE |
В возвращаемом имени файла могут
присутствовать неразрешенные символы |
OFN_OVERWRITEPROMPT |
Используется для диалоговой панели
"Save As...". Если выбранный файл существует, на
экран выводится диалоговая панель с
предупреждением |
OFN_PATHMUSTEXIST |
Можно вводить только существующие пути
к файлам |
OFN_READONLY |
После вызова функции переключатель
"Read Only" будет находиться во включенном
состоянии |
OFN_SHAREWARE |
Флаг устанавливается после возвращения
из функции и указывает, что при вызове функции
OpenFile произошла ошибка при совместном доступе к
файлу в сети |
OFN_SHOWHELP |
Если указан этот флаг, в диалоговой
панели будет создана кнопка "Help". Если
указан этот флаг, поле hwndOwner не должно содержать
значение NULL |
Поле hwndOwner должно содержать идентификатор окна, создавшего диалоговую панель. Можно указать значение NULL, при этом диалоговая панель не будет иметь окно-владельца. В этом случае нельзя использовать флаг OFN_SHOWHELP.
Поле hInstance используется перед вызовом функции для идентификации блока памяти, содержащего шаблон диалоговой панели. Содержимое поля игнорируется в том случае, если не указаны флаги OFN_ENABLETEMPLATE или OFN_ENABLETEMPLATEHANDLE.
В поле lpstrFilter должен быть указан адрес текстовой строки, задающей фильтр для выбора имен файлов (шаблоны имен файлов).
Согласно описанию структуры OPENFILENAME фильтр должен состоять из одной или нескольких расположенных непосредственно друг за другом пар текстовых строк, закрытых двоичным нулем. Последняя строка должна иметь на конце два двоичных нуля.
Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов.
В поле lpstrCustomFilter должен быть указан адрес текстовой строки, задающей фильтр для выбора имен файлов (шаблон для выбора файлов).
Согласно описанию структуры OPENFILENAME фильтр должен состоять из одной пары текстовых строк, закрытых двоичным нулем. Последняя строка должна иметь на конце два двоичных нуля.
Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов.
Если поле lpstrFilter содержит NULL, используется фильтр lpstrCustomFilter.
Определяет размер буфера в байтах, указанного в поле lpstrCustomFilter. Размер этого буфера должен быть не меньше 40 байт.
Поле nFilterIndex определяет номер пары строк, используемой для фильтра, указанного в поле lpstrFilter. Если в качестве значения для этого поля указать 0, будет использован фильтр, определенный в поле lpstrCustomFilter.
Поле lpstrFile должно содержать адрес текстовой строки, в которую будет записан полный путь к выбранному файлу.
Если по указанному выше адресу перед вызовом функции GetOpenFileName или GetSaveFileName расположить текстовую строку, содержащую путь к файлу, этот путь будет выбран по умолчанию сразу после отображения диалоговой панели "Open" или "Save As...".
Поле nMaxFile должно содержать размер в байтах буфера, расположенного по адресу, указанному в поле lpstrFile.
Размер этого буфера должен быть достаточным для записи полного пути к файлу. Файловая система MS-DOS допускает использование для указания пути к файлу не более 128 символов.
В поле lpstrFileTitle необходимо записать адрес буфера, в который после выбора будет записано имя файла с расширением, но без пути к файлу. Это поле должно быть использовано приложением для отображения имени выбранного файла.
Поле nMaxFileTitle должно содержать размер указанного выше буфера.
Поле lpstrInitialDir позволяет указать начальный каталог, который будет выбран для поиска файла сразу после отображения диалоговой панели "Open". Для того чтобы начать поиск в текущем каталоге, в это поле следует записать значение NULL.
С помощью этого поля можно определить заголовок диалоговой панели, появляющейся при вызове функции. Если это поле содержит NULL, будут использованы стандартные заголовки "Open" и "Save As...".
После возврата из функции это поле содержит смещение первого символа имени файла относительно начала буфера lpstrFile.
После возврата из функции это поле содержит смещение первого символа расширения имени файла относительно начала буфера lpstrFile.
Указатель на буфер, который содержит расширение имени файла, используемое по умолчанию. Это расширение добавляется к имени выбранного файла, если при выборе расширение имени не было указано.
Значение, передаваемой функции фильтра через параметр lParam.
Указатель на функцию фильтра, обрабатывающую сообщения для диалоговой панели. Функция фильтра будет вызываться только в том случае, если указан флаг OFN_ENABLEHOOK. Если фильтр не обрабатывает сообщение, он должен вернуть нулевое значение. Если же он вернет значение, отличное от нуля, стандартная функция диалога не будет обрабатывать сообщение, которое уже было обработано функцией фильтра.
Идентификатор ресурса, содержащего шаблон диалоговой панели, используемого вместо имеющегося в 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 можно определить атрибуты создаваемого файла:
Значение атрибута |
Описание |
0 |
Нормальный файл, для которого разрешено
выполнение операций чтения и записи |
1 |
Этот файл можно открыть только для
чтения |
2 |
Скрытый файл |
3 |
Системный файл |
Если указанный первым параметром файл не существует, функция _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 следующим образом:
Значение |
Описание |
SEEK_SET |
Указатель текущей позиции в файле
перемещается на lOffset байт от начала файла |
SEEK_CUR |
Указатель текущей позиции в файле
перемещается на lOffset байт от текущей позиции |
SEEK_END |
Указатель текущей позиции в файле
перемещается на lOffset байт от конца файла |
Таким образом, с помощью этой функции вы можете обеспечить прямой доступ к файлу.
Функция возвращает новое значение текущей позиции от начала файла или HFILE_ERROR при ошибке.
Иногда приложению требуется определить тип и расположение используемого дискового устройства ввода/вывода. Для этого можно воспользоваться функцией GetDriveType :
UINT WINAPI GetDriveType(int DriveNumber);
Параметр DriveNumber определяет номер диска, для которого требуется определить тип и расположение (0 соответствует устройству A:, 1 - B:, и т. д.).
Функция может вернуть 0 при ошибке или одно из следующих значений:
Значение |
Описание |
DRIVE_REMOVABLE |
Сменный диск |
DRIVE_FIXED |
Несменный диск |
DRIVE_REMOTE |
Удаленный диск, расположен на другой
машине в сети |
Для работы с файлами в приложениях Windows вы можете использовать функции из стандартной библиотеки транслятора C или C++, такие как read , fread , write , fwrite , lseek , fstat , tell , close .
Можно также использовать функции потокового ввода/вывода , если преобразовать идентификатор файла в указатель на структуру FILE при помощи функции fdopen . Для того чтобы закрыть такой файл следует воспользоваться функцией fclose .
Перечисленные выше функции были описаны в третьей книге первого тома "Библиотеки системного программиста" в главе "Файловая система DOS".
Как мы уже говорили, в многозадачной среде утилита 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
Приведем еще один вариант приложения OEM3ANSI, описанного в предыдущем томе "Библиотеки системного программиста". В новом варианте приложение заказывает в глобальной области памяти буфер для чтения перекодируемого файла, причем размер этого буфера равен... размеру файла! Далее с помощью одного вызова функции _hread весь файл переписывается в буфер и перекодируется "по месту". Результат перекодировки из буфера переписывается в выходной файл за один вызов функции _hwrite. Таким образом, в нашем приложении нет привычного цикла чтения файла, который прерывается при достижении конца файла, а также нет цикла записи в файл. Механизм виртуальной памяти расширенного режима операционной системы Windows и функции _hread, _hwrite облегчают работу с файлами.
Кроме демонстрации работы перечисленных выше двух функций в новом варианте приложения OEM3ANSI для выбора входного и выходного файла мы использовали вместе со стандартными функциями GetOpenFileName и GetSaveFileName нестандартные шаблоны диалоговых панелей "Open" и "Save as...". Внешний вид диалоговой панели для выбора входного файла представлен на рис. 4.3, для выбора выходного файла - на рис. 4.4.
Рис. 4.3. Выбор входного файла
Рис. 4.4. Выбор выходного файла
Механизм шаблонов, предусмотренный во всех функциях стандартных диалоговых панелей из DLL-библиотеки commdlg.dll, можно использовать в тех случаях, когда вас не устраивает внешний вид стандартных диалоговых панелей. Например, все надписи в стандартной диалоговой панели "Open" выполнены на английском языке. Вы, вероятно, сумеете найти другой вариант библиотеки commdlg.dll (например, русифицированный), но лучше создать свой шаблон диалоговой панели, в котором вы можете перевести надписи на любой язык или внести другие изменения.
Как создать свой шаблон для стандартной диалоговой панели ?
Для этого проще всего воспользоваться приложением Borland Resource Workshop. С помощью этого приложения вы сможете "вытянуть" из DLL-библиотеки commdlg.dll описание любого имеющегося там ресурса, записав его в текстовый файл с расширением имени rc.
Запустите приложение Borland Resource Workshop. С помощью строки "Open Project" меню "File" загрузите DLL-библиотеку commdlg.dll. Вариант этой библиотеки, который вы можете распространять вместе с созданными вами приложениями, как правило, поставляется в составе системы разработки приложений. Кроме того, эта библиотека есть в системном каталоге операционной системы Windows версии 3.1.
После загрузки библиотеки в окне "commdlg.dll" вы увидите список ресурсов (рис. 4.5).
Рис. 4.5. Шаблоны диалоговых панелей и другие ресурсы в DLL-библиотеке commdlg.dll
Из ресурсов типа DIALOG выберите шаблон 1536. На экране появится окно "DIALOG:1536", в котором вы сможете редактировать стандартную диалоговую панель "Open" (рис. 4.6).
Рис. 4.6. Редактирование стандартной диалоговой панели "Open"
Однако перед тем как редактировать шаблон, его лучше сохранить в отдельном файле. Для этого из меню "Resource" выберите строку "Save resource as..." и сохраните описание шаблона, например, в файле с именем open.rc.
Таким образом, вы выделили описание нужного вам шаблона диалоговой панели в отдельный файл. Содержимое этого файла можно включить в файл описания ресурсов вашего приложения и использовать для работы с функциями GetOpenFileName и GetSaveFileName (как мы и поступили в приложении OEM3ANSI).
Учтите, что при редактировании шаблона не следует удалять из него органы управления, даже если по логике работы приложения они не нужны. Дело в том, что функции DLL-библиотеки commdlg.dll могут обращаться к ним. Например, они могут инициализировать списки. Поэтому, если вам надо сделать какой-либо стандартный орган управления невидимым, вы можете указать для него расположение вне видимой части экрана.
Теперь перейдем к описанию приложения OEM3ANSI. Исходные тексты главного файла приложения приведены в листинге 4.3.
Листинг 4.3. Файл oem3ansi/oem3ansi.cpp
// ---------------------------------------- // Перекодировка текстового файла // из OEM в ANSI с использованием // дополнительной таблицы перекодировки // ---------------------------------------- #define STRICT #include <windows.h> #include <commdlg.h> #include <mem.h> // Прототипы функций HFILE GetSrcFile(void); HFILE GetDstFile(void); int Oem3Ansi(HFILE, HFILE); // Указатель на таблицу перекодировки, // которая будет загружена из ресурсов char far * lpXlatTable; // Идентификатор копии приложения HINSTANCE hInst; // ------------------------------- // Функция WinMain // ------------------------------- #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Идентификаторы файлов HFILE hfSrc, hfDst; // Положение ресурса в файле HRSRC hResource; // Идентификатор таблицы перекодировки HGLOBAL hXlatTable; // Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance; // Определяем расположение ресурса hResource = FindResource(hInstance, "XlatTable", "XLAT"); // Получаем идентификатор ресурса hXlatTable = LoadResource(hInstance, hResource); // Фиксируем ресурс в памяти, получая его адрес lpXlatTable = (char far *)LockResource(hXlatTable); // Если адрес равен NULL, при загрузке или // фиксации ресурса произошла ошибка if(lpXlatTable == NULL) { MessageBox(NULL, "Resource loading error", NULL, MB_OK); return(-1); } // Открываем входной файл. hfSrc = GetSrcFile(); if(!hfSrc) return 0; // Открываем выходной файл hfDst = GetDstFile(); if(!hfDst) return 0; // Выполняем перекодировку файла if(Oem3Ansi(hfSrc, hfDst)) { MessageBox(NULL, "Low Memory", NULL, MB_OK); } // Закрываем входной и выходной файлы _lclose(hfSrc); _lclose(hfDst); // Разблокируем и освобождаем ресурс UnlockResource(hXlatTable); FreeResource(hXlatTable); return 0; } // ------------------------------- // Функция GetSrcFile // Выбор файла для перекодировки // ------------------------------- HFILE GetSrcFile(void) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; HFILE hf; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля // Добавляем флаг OFN_ENABLETEMPLATE, который // разрешает использование шаблона ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE; // Идентификатор модуля, содержащего шаблон // диалоговой панели. В нашем случае это // идентификатор копии приложения ofn.hInstance = hInst; // Имя ресурса, содержащего шаблон ofn.lpTemplateName = (LPSTR)"Open"; // Заполняем остальные поля ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Открываем на чтение hf = _lopen(ofn.lpstrFile, OF_READ); return hf; } else return 0; } // ------------------------------- // Функция GetDstFile // Выбор файла для записи // результата перекодировки // ------------------------------- HFILE GetDstFile(void) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; HFILE hf; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); // Добавляем флаг OFN_ENABLETEMPLATE, который // разрешает использование шаблона ofn.Flags = OFN_HIDEREADONLY | OFN_ENABLETEMPLATE; // Идентификатор модуля, содержащего шаблон ofn.hInstance = hInst; // Имя ресурса, содержащего шаблон ofn.lpTemplateName = (LPSTR)"Open"; // Изменяем заголовок диалоговой панели ofn.lpstrTitle = (LPSTR)"Выберите выходной файл"; // Заполняем остальные поля ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; // Выбираем выходной файл if (GetSaveFileName(&ofn)) { // Открываем на запись. // При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; } // ------------------------------- // Функция Oem3Ansi // Перекодировка файла // ------------------------------- int Oem3Ansi(HFILE hfSrcFile, HFILE hfDstFile) { // Счетчик прочитанных байт DWORD cbRead; // Размер файла DWORD dwFileSize; // Идентификатор глобального блока // памяти, который будет использован для // чтения файла HGLOBAL hglbBuf; // Указатель на глобальный блок памяти unsigned char huge * hBuf; // Определяем размер файла. Для этого // устанавливаем текущую позицию на // конец файла dwFileSize = _llseek(hfSrcFile, 0l, 2); // Устанавливаем текущую позицию // на начало файла _llseek(hfSrcFile, 0l, 0); // Заказываем глобальный блок памяти, // размер которого равен длине файла hglbBuf = GlobalAlloc(GMEM_FIXED, dwFileSize); hBuf = (unsigned char huge *)GlobalLock(hglbBuf); // Если мало свободной памяти, // возвращаем код ошибки if(hBuf == NULL) return(-1); // Читаем файл в полученный блок памяти cbRead = _hread(hfSrcFile, hBuf, dwFileSize); // Выполняем перекодировку for(long i=0; i < cbRead; i++) { // Перекодировка по таблице, // загруженной из ресурсов hBuf[i] = lpXlatTable[hBuf[i]]; // Перекодировка из OEM в ANSI OemToAnsiBuff((const char far*)&hBuf[i], (char far*)&hBuf[i], 1); } // Сохраняем содержимое блока памяти в // выходном файле _hwrite(hfDstFile, hBuf, dwFileSize); // Расфиксируем и освобождаем // блок памяти GlobalUnlock(hglbBuf); GlobalFree(hglbBuf); return 0; }
Функция WinMain сохраняет идентификатор копии приложения в глобальной переменной hInst. Этот идентификатор потребуется впоследствии для загрузки шаблона диалоговой панели из ресурсов приложения.
Далее, так же как и первой версии приложения OEM3ANSI, функция WinMain загружает из ресурсов дополнительную таблицу перекодировки, сохраняя ее адрес в переменной lpXlatTable.
Затем функция WinMain открывает входной и выходной файлы, вызывая функции GetSrcFile и GetDstFile, определенные в нашем приложении. Эти функции выбирают файлы с использованием шаблона, созданного нами на основе стандартного шаблона диалоговой панели "Open" и функций GetOpenFileName, GetSaveFileName.
После перекодировки выбранного файла (которая выполняется функцией Oem3Ansi), файлы закрываются, ресурс, содержащий таблицу перекодировки, расфиксируется и освобождается.
Так как диалоговые панели выбора входного и выходного файла отличаются только заголовком, для них мы создали только один шаблон. В файле ресурсов этот шаблон имеет имя "Open".
Перед тем, как вызвать функцию GetOpenFileName, предназначенную для выбора входного файла, мы должны подготовить соответствующим образом структуру ofn типа OPENFILENAME. В частности, для обеспечения возможности работы с шаблоном в поле Flags структуры ofn необходимо указать флаг OFN_ENABLETEMPLATE. В поле hInstance этой же структуры необходимо записать идентификатор копии приложения, ресурсы которого содержат шаблон, а в поле lpTemplateName нужно записать указатель на строку имени ресурса:
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE; ofn.hInstance = hInst; ofn.lpTemplateName = (LPSTR)"Open";
Функция GetDstFile инициализирует структуру ofn аналогичным образом. Единственное отличие заключается в том, что в поле lpstrTitle записывается адрес строки, содержащей заголовок "Выберите выходной файл":
ofn.Flags = OFN_HIDEREADONLY | OFN_ENABLETEMPLATE; ofn.hInstance = hInst; ofn.lpTemplateName = (LPSTR)"Open"; ofn.lpstrTitle = (LPSTR)"Выберите выходной файл";
Для перекодирования файла в приложении OEM3ANSI определена функция с именем Oem3Ansi.
Прежде всего эта функция определяет размер входного файла. Для этого она выполняет позиционирование на конец файла, используя функцию _llseek:
dwFileSize = _llseek(hfSrcFile, 0l, 2);
Эта функция возвращает текущее смещение в файле от начала файла. Так как мы установили текущую позицию н конец файла, текущее смещение от начала файла, очевидно, равно размеру файла.
После определения размера файла необходимо установить текущую позицию на начало файла. В противном случае при попытке прочитать данные мы получим состояние "Конец файла". Для установки текущей позиции на начало файла мы вызываем функцию _llseek еще раз, но с другими параметрами:
_llseek(hfSrcFile, 0l, 0);
После определения размера входного файла функция Oem3Ansi заказывает фиксированный блок из глобальной области памяти (при помощи функции GlobalAlloc), и фиксирует его для получения адреса (при помощи функции GlobalLock). Адрес блока записывается в переменную hBuf типа unsigned char huge*. Размер заказанного блока равен размеру файла.
Затем весь файл читается в буфер, для чего используется функция _hread:
cbRead = _hread(hfSrcFile, hBuf, dwFileSize);
Перекодировка выполняется в следующем цикле:
for(long i=0; i < cbRead; i++) { hBuf[i] = lpXlatTable[hBuf[i]]; OemToAnsiBuff((const char far*)&hBuf[i], (char far*)&hBuf[i], 1); }
Для перекодировки по дополнительной таблице мы адресуемся к блоку памяти через указатель типа huge, так как размер блока памяти может превышать 64 Кбайт.
Функция OemToAnsiBuff может перекодировать буфер размером не более 64 Кбайт, поэтому мы вызываем ее отдельно для каждого байта перекодируемого буфера.
После подготовки буфера мы записываем его в выходной файл, вызывая функцию _hwrite:
_hwrite(hfDstFile, hBuf, dwFileSize);
Перед возвратом управления функция Oem3Ansi расфиксирует и освобождает заказанный ранее блок памяти, вызывая функции GlobalUnlock и GlobalFree.
Файл описания ресурсов приложения OEM3ANSI приведен в листинге 4.4.
Листинг 4.4. Файл oem3ansi/oem3ansi.rc
/* Таблица перекодировки */ XlatTable XLAT xlatcyr.tbl /* Шаблон диалоговой панели*/ Open DIALOG 23, 21, 264, 134 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Выберите входной файл" FONT 8, "Helv" BEGIN LTEXT "&Файл:", 1090, 6, 6, 76, 9, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", 1152, "EDIT", ES_LEFT | ES_AUTOHSCROLL | ES_OEMCONVERT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 6, 16, 90, 12 CONTROL "", 1120, "LISTBOX", LBS_STANDARD | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_DISABLENOSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 6, 32, 90, 68 LTEXT "&Каталог:", -1, 110, 6, 92, 9, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", 1088, "STATIC", SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_GROUP, 110, 18, 92, 9 CONTROL "", 1121, "LISTBOX", LBS_STANDARD | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_DISABLENOSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 110, 32, 92, 68 LTEXT "&Типы файлов:", 1089, 6, 104, 90, 9, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", 1136, "COMBOBOX", CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 6, 114, 90, 36 LTEXT "&Дисковые устройства:", 1091, 110, 104, 92, 9, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", 1137, "COMBOBOX", CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_AUTOHSCROLL | CBS_SORT | CBS_HASSTRINGS | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 110, 114, 92, 68 CONTROL "OK", 1, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 6, 50, 14 CONTROL "Cancel", 2, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 24, 50, 14 CONTROL "&Help", 1038, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 46, 50, 14 CONTROL "&Чтение", 1040, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 68, 50, 12 END
В этом файле описаны два ресурса: дополнительная таблица перекодировки XlatTable и шаблон диалоговой панели Open.
Файл определения модуля для приложения OEM3ANSI приведен в листинге 4.5.
Листинг 4.5. Файл oem3ansi/oem3ansi.def
; ============================= ; Файл определения модуля ; ============================= NAME OEM3ANSI DESCRIPTION 'Приложение OEM3ANSI, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple