Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 11, М.: Диалог-МИФИ, 1993, 269 стр. 5.5. Приложение OEM2ANSIВ качестве примера приведем исходный текст приложения OEM2ANSI, выполняющего перекодировку файла из набора OEM в набор ANSI. На примере этого приложения вы узнаете о стандартных средствах Windows, позволяющих выполнять поиск файлов в каталогах и сохранение файлов под заданным именем и в заданном каталоге. Кроме этого вы узнаете о существовании в программном интерфейсе Windows специальных функций, предназначенных для работы с файлами. Исходный текст основного файла приложения OEM2ANSI представлен в листинге 5.5. Листинг 5.5. Файл oem2ansi\oem2ansi.cpp // ---------------------------------------- // Перекодировка текстового файла // из OEM в ANSI // ---------------------------------------- #define STRICT #include <windows.h> #include <commdlg.h> #include <mem.h> // Прототипы функций HFILE GetSrcFile(void); HFILE GetDstFile(void); void Oem2Ansi(HFILE, HFILE); // ------------------------------- // Функция WinMain // ------------------------------- #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Переменные для хранения // идентификаторов входного и выходного // файлов HFILE hfSrc, hfDst; // Открываем входной файл. // В случае ошибки или отказа от выбора // файла завершаем работу приложения hfSrc = GetSrcFile(); if(!hfSrc) return 0; // Открываем выходной файл hfDst = GetDstFile(); if(!hfDst) return 0; // Выполняем перекодировку файла Oem2Ansi(hfSrc, hfDst); // Закрываем входной и выходной файлы _lclose(hfSrc); _lclose(hfDst); 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.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; // Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл 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.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; ofn.Flags = OFN_HIDEREADONLY; // Выбираем выходной файл if (GetSaveFileName(&ofn)) { // При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; } // ------------------------------- // Функция Oem2Ansi // Перекодировка файла // ------------------------------- void Oem2Ansi(HFILE hfSrcFile, HFILE hfDstFile) { // Счетчик прочитанных байт int cbRead; // Буфер для считанных данных BYTE bBuf[2048]; // Читаем в цикле файл и перекодируем его, // записывая результат в другой файл do { // Читаем в буфер 2048 байт из входного файла cbRead = _lread(hfSrcFile, bBuf, 2048); // Перекодируем содержимое буфера OemToAnsiBuff(bBuf, bBuf, cbRead); // Сохраняем содержимое буфера в // выходном файле _lwrite(hfDstFile, bBuf, cbRead); // Завершаем цикл по концу входного файла } while (cbRead != 0); } Дополнительно к стандартному для всех приложений Windows файлу windows.h в главный файл приложения включен файл commdlg.h, который содержит определения для функций стандартных диалогов Windows версии 3.1. Загрузочные модули этих функций расположены в отдельной библиотеке динамической загрузки (DLL) с именем commdlg.dll. Среди функций стандартных диалогов есть удобные функции, предназначенные для поиска и сохранения файлов, для выбора цвета и шрифта, для печати и работы с принтером и т. д. Позже мы рассмотрим применение этих функций, значительно облегчающих труд программиста. В приложении OEM2ANSI мы воспользуемся двумя такими функциями. В функции WinMain определены переменные для хранения идентификаторов файлов (file handle), вовлеченных в процесс перекодировки: HFILE hfSrc, hfDst; Тип HFILE определен в файле windows.h следующим образом: typedef int HFILE; Работа функции WinMain начинается с вызова функции GetSrcFile, определенной в нашем приложении. Эта функция используется для поиска и открытия файла. Функция GetSrcFile возвращает идентификатор файла, подлежащего перекодировке, или ноль, если вы отказались от выбора файла. Полученный идентификатор можно использовать для выполнения операций файлового ввода/вывода. Затем функция WinMain вызывает функцию GetDstFile, предназначенную для выбора имени и расположения в каталогах диска выходного (перекодированного) файла. Функция GetDstFile открывает имеющийся файл (если для записи результата перекодировки выбран уже существующий файл) или создает и открывает новый. После того как входной и выходной файлы открыты, вызывается функция Oem2Ansi, которая также определена в нашем приложении. Эта функция выполняет поблочное чтение входного файла, перекодировку прочитанных блоков в набор ANSI и запись результата перекодировки в выходной файл. В заключение функция WinMain закрывает оба файла, вызывая функцию _lclose из программного интерфейса Windows. Функция GetSrcFile вызывает функцию GetOpenFileName, которая выводит на экран знакомую вам по стандартным приложениям Windows диалоговую панель "Open", позволяющую выбрать файл для перекодировки (рис. 5.6).
Рис. 5.6. Диалоговая панель "Open" Внешний вид этой диалоговой панели определяется структурой типа 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 в качестве параметра lpfn: BOOL WINAPI GetOpenFileName(OPENFILENAME FAR* lpofn); Функция возвращает ненулевое значение, если вы сделали выбор файла, и ноль, если вы отказались от выбора, нажав кнопку "Cancel" или выбрав строку "Close" из системного меню диалоговой панели. Нулевое значение возвращается также при возникновении ошибки. В результате выбора некоторые поля структуры будут заполнены информацией о выбранном файле. Так как функциям, определенным в библиотеке commdlg.dll, будет посвящена отдельная глава, мы рассмотрим назначение только тех полей структуры OPENFILENAME, которые используются в нашем приложении OEM2ANSI. Прежде всего перед вызовом функции GetOpenFileName наше приложение записывает во все поля структуры нулевые значения: memset(&ofn, 0, sizeof(OPENFILENAME)); Затем в поле lStructSize записывается размер самой структуры в байтах: ofn.lStructSize = sizeof(OPENFILENAME); Поле hwndOwner должно содержать идентификатор окна, создавшего диалоговую панель. Так как наше приложение не создает ни одного окна, в качестве идентификатора используется значение NULL, при этом диалоговая панель не имеет окна-владельца (похожим образом мы поступали при вызове функции MessageBox): ofn.hwndOwner = NULL; В поле lpstrFilter должен быть указан адрес текстовой строки, задающей фильтр для выбора имен файлов (шаблоны имен файлов): ofn.lpstrFilter = szFilter; Наше приложение использует в качестве фильтра такую строку: char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; Согласно описанию структуры OPENFILENAME фильтр должен состоять из одной или нескольких расположенных непосредственно друг за другом пар текстовых строк, закрытых двоичным нулем. Последняя строка должна иметь на конце два двоичных нуля. Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов. В нашем приложении определены две пары строк. Одна из них предназначена для выбора только текстовых файлов с расширениями имени *.txt и *.doc, вторая - для выбора любых файлов (с любым расширением имени). Поле nFilterIndex определяет номер пары строк, используемой для фильтра. В нашем случае этот номер задается как 1, поэтому из двух фильтров выбирается первый, предназначенный для поиска текстовых файлов: ofn.nFilterIndex = 1; Поле lpstrFile должно содержать адрес текстовой строки, в которую будет записан полный путь к выбранному файлу: ofn.lpstrFile = szFile; Если по указанному выше адресу перед вызовом функции GetOpenFileName расположить текстовую строку, содержащую путь к файлу, этот путь будет выбран по умолчанию сразу после отображения диалоговой панели "Open". В нашем приложении эта возможность не используется, поэтому по адресу szFile мы расположили пустую строку, состоящую из одного нуля: szFile[0] = '\0'; Поле nMaxFile должно содержать размер буфера, расположенного по адресу, указанному в поле lpstrFile: ofn.nMaxFile = sizeof(szFile); Размер этого буфера должен быть достаточным для записи полного пути к файлу. Файловая система MS-DOS допускает использование для указания пути к файлу не более 128 символов. В поле lpstrFileTitle необходимо записать адрес буфера, в который после выбора будет записано имя файла с расширением, но без пути к файлу: ofn.lpstrFileTitle = szFileTitle; Это поле должно быть использовано приложением для отображения имени выбранного файла. Поле nMaxFileTitle должно содержать размер указанного выше буфера: ofn.nMaxFileTitle = sizeof(szFileTitle); Поле lpstrInitialDir позволяет указать начальный каталог, который будет выбран для поиска файла сразу после отображения диалоговой панели "Open". Наше приложение начинает поиск в текущем каталоге, поэтому в это поле мы записали значение NULL: ofn.lpstrInitialDir = NULL; Поле Flags позволяет задать различные режимы выбора файла, влияющие на внешний вид диалоговой панели. Наше приложение использует комбинацию из трех флагов: ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; Флаг OFN_PATHMUSTEXIST указывает, что можно выбирать только такие пути, которые соответствуют существующим каталогам. Аналогично флаг OFN_FILEMUSTEXIST определяет, что при выборе можно указывать только существующие файлы. Флаг OFN_HIDEREADONLY убирает из диалоговой панели переключатель, позволяющий открыть файл в режиме "только чтение" (мы не пользуемся этим режимом, так как не собираемся изменять открываемый файл). После подготовки структуры мы вызываем функцию GetOpenFileName и проверяем возвращаемое ей значение: if (GetOpenFileName(&ofn)) { hf = _lopen(ofn.lpstrFile, OF_READ); return hf; } else return 0; Если возвращаемое функцией GetOpenFileName значение отлично от нуля, поле lpstrFile содержит путь к выбранному файлу. Этот путь приложение передает функции _lopen, открывающей файл на чтение. Затем функция GetSrcFile возвращает идентификатор открытого файла (точнее, результат, полученный при попытке открыть файл). При ошибке или отказе от выбора файла функция GetSrcFile возвращает нулевое значение. Для выбора файла, в который будет записан результат перекодировки, функция WinMain вызывает функцию GetDstFile. Эта функция вызывает функцию GetSaveFileName, которая выводит стандартную диалоговую панель "Save As..." (рис. 5.7).
Рис. 5.7. Диалоговая панель "Save As..." Диалоговая панель "Save As..." используется многими стандартными приложениями Windows для выбора файла, в который будет записан результат работы приложения. Функция GetSaveFileName описана в файле commdlg.h: BOOL WINAPI GetSaveFileName(OPENFILENAME FAR* lpofn); Она используется аналогично функции GetOpenFileName. Вначале приложение подготавливает структуру OPENFILENAME: szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); 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; ofn.Flags = OFN_HIDEREADONLY; При этом используется тот же фильтр, что и при поиске входного (перекодируемого) файла, но другое значение поля Flags. Затем приложение вызывает функцию GetSaveFileName и проверяет возвращаемое ей значение: if ((&ofn)) { hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; Если выходной файл выбран правильно и не произошло никаких ошибок, приложение вызывает функцию _lcreat. Эта функция открывает на запись существующий файл или создает новый и затем также открывает его на запись. Затем функция GetDstFile возвращает идентификатор открытого файла или ноль, если вы отказались от выбора файла или если произошла ошибка. Для перекодирования файла функция WinMain вызывает функцию Oem2Ansi. Эта функция считывает в цикле содержимое входного файла в буфер, перекодирует буфер и затем записывает его в выходной файл: do { cbRead = _lread(hfSrcFile, bBuf, 2048); OemToAnsiBuff(bBuf, bBuf, cbRead); _lwrite(hfDstFile, bBuf, cbRead); } while (cbRead != 0); Чтение файла выполняется функцией _lread, которая входит в состав программного интерфейса Windows. В качестве первого параметра этой функции передается идентификатор файла, в качестве второго - указатель на буфер, в который выполняется чтение, и в качестве третьего - размер буфера. Функция _lread возвращает количество прочитанных из файла байт данных. Запись буфера в файл выполняется функцией _lwrite, которая также входит в состав программного интерфейса Windows. Параметры этой функции аналогичны параметрам функции _lread. Подробнее функции для работы с файлами мы рассмотрим позже в отдельной главе. Для перекодировки буфера используется знакомая вам функция OemToAnsiBuff. И первый, и второй параметры этой функции указывают на один и тот же буфер, поэтому перекодировка будет выполняться "по месту". Вы можете добавить до или после функции OemToAnsiBuff свою собственную функцию перекодировки, выполняющую какие-либо дополнительные перекодирующие действия. На рис. 5.8 представлен результат перекодировки приложением OEM2ANSI текстового файла, подготовленного в MS-DOS и содержащего символы кириллицы.
Рис. 5.8. Исходный и преобразованный файлы В верхней части рис. 5.8 изображено окно редактирования системы разработки Borland C++ for Windows, в которое загружен текст в кодировке OEM. Этот текст содержит символы кириллицы, которые в кодировке ANSI отображаются в виде различных "нечитаемых" символов. В нижней части рис. 5.8 расположено окно, в которое загружен перекодированный текст. Если при использовании нашего приложения OEM2ANSI вы получили результаты, отличные от представленного на рис. 5.8, вам следует убедиться в том, что на вашем компьютере была выполнена локализация операционной системы Windows и что она была выполнена правильно. Для локализации Windows можно использовать такие программные средства, как CyrWin или ParaWin. Вы можете также приобрести локализованную версию Windows 3.1, которая поставляется в России фирмой Microsoft A.O. На базе приложения OEM2ANSI вы без труда сможете создать приложение с названием, например, ANSI2OEM, выполняющее обратную перекодировку. Мы предлагаем вам создать такое приложение самостоятельно. Файл определения модуля для приложения OEM2ANSI представлен в листинге 5.6. Листинг 5.6. Файл oem2ansi\oem2ansi.def ; ============================= ; Файл определения модуля ; ============================= NAME OEM2ANSI DESCRIPTION 'Приложение OEM2ANSI, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple |