Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 13, М.: Диалог-МИФИ, 1993, 284 стр. 4.3. Новый вариант приложения OEM3ANSIПриведем еще один вариант приложения 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 |