Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 27, часть 2, М.: Диалог-МИФИ, 1996, 272 стр. Приложение Oem2CharПриложение Oem2Char, исходные тексты которого мы приведем в этом разделе, выполняет перекодировку текстовых файлов из формата OEM (используется программами MS-DOS) в формат ANSI (используется приложениями Microsoft Windows) и обратно. Вы можете оттранслировать исходные тексты этого приложения таким образом, чтобы оно использовало для работы с файлами один из трех методов: синхронный или асинхронный ввод/вывод, а также отображение файлов на память. Главное окно приложения Oem2Char, запущенного в среде Microsoft Windows 95, показано на рис. 1.2. Точно такой же вид это окно будет иметь при запуске этого приложения в среде Microsoft Windows NT версии 4.0. Рис. 1.2. Главное окно приложения Oem2Char Выбирая из меню File строку Convert вы можете указать режим перекодировки: из OEM в ANSI или обратно, из ANSI в OEM. Соответстсвующая диалоговая панель, предназначенная для указания режима перекодировки, называется Conversion Options и показана на рис. 1.3. Рис. 1.3. Диалоговая панель Conversion Options панель, предназначенная для выбора режима перекодировки По умолчанию приложение выполняет перекодировку текстовых файлов из формата OEM в формат ANSI. Если исходные тексты приложения оттранслированы таким образом, что для работы с файлами используется синхронный либо асинхронный метод, вам предоставляется возможность выбрать исходный файл, который будет прекодироваться, и файл, в который будет записан результат перекодировки. При использовании отображения файла в память перекодировка выполняется “по месту”. Выбрав из меню File строку Convert, вы увидите на экране диалоговую панель Select source file, показанную на рис. 1.4. Рис. 1.4. Диалоговая панель Select source file, предназначенная для выбора исходного файла С помощью этой диалоговой панели вы можете выбрать исходный файл для перекодировки. Напомним, что при работе с файлами в режиме отображения на память результат перекодировки будет записан в исходный файл. Если же приложение работает с файлами в синхронном или асинхронном режиме, после выбора исходного файла вам будет представлена возможность выбрать файл для записи результата перекодировки (рис. 1.5). Различные режимы работы с файлами были описаны нами в предыдущем томе “Библиотеки системного программиста”. Рис. 1.5. Диалоговая панель Select destination file, с помощью которой можно выбрать файл для записи результата перекодировки Исходные тексты приложенияГлавный файл исходных текстов приложения Oem2Char приведены в листинге 1.1. Листинг 1.1. Файл oem2char/oem2char.c // ================================================== // Приложение OEM2CHAR // Демонстрация использования файловых операций // для перекодировки текстовых файлов // // (С) Фролов А.В., 1996 // Email: frolov@glas.apc.org // ================================================== #define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" // Различные режимы работы с файлами #define SYNCHRONOUS_IO 1 // синхронные операции #define ASYNCHRONOUS_IO 2 // асинхроные операции #define MEMORYMAPPED_IO 3 // отображение на память // Для использования различных режимов работы // с файлами используйте только одну из // приведенных ниже трех строк //#define FILEOP SYNCHRONOUS_IO //#define FILEOP ASYNCHRONOUS_IO #define FILEOP MEMORYMAPPED_IO #include "oem2char.h" HINSTANCE hInst; char szAppName[] = "Oem2CharApp"; char szAppTitle[] = "Text File Converter"; // Тип преобразования: // OEM -> ANSI или ANSI -> OEM BOOL fConversionType = OEM_TO_ANSI; // Идентификатор файла, который будет // перекодирован в соответствии с содержимым // глобальной переменной fConversionType HANDLE hSrcFile; // Эти определения используются для всех способов // работы с файлами, кроме отображения на память #if FILEOP != MEMORYMAPPED_IO // Идентификатор файла, в который будет записан // результат перекодировки HANDLE hDstFile; // Буфер для работы с файлами CHAR cBuf[2048]; #endif // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; // Сохраняем идентификатор приложения hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { // Если было, выдвигаем окно приложения на // передний план if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл // обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { // Выполняем преобразование файла case ID_FILE_CONVERT: { // Если не удалось открыть файлы, выводим // сообщение об ошибке if(!StartConversion(hWnd)) MessageBox(hWnd, "Conversion Error\n" "Unable to open file(s)", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } case ID_FILE_OPTIONS: { // Отображаем диалоговую панель, предназначенную // для настройки параметров преобразования DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc); break; } case ID_FILE_EXIT: { // Завершаем работу приложения PostQuitMessage(0); return 0L; break; } case ID_HELP_ABOUT: { MessageBox(hWnd, "Text File Converter\n" "(C) Alexandr Frolov, 1996\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция DlgProc // ----------------------------------------------------- LRESULT WINAPI DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc_OnCommand); default: return FALSE; } } // ----------------------------------------------------- // Функция DlgProc_OnInitDialog // ----------------------------------------------------- BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // При инициализации диалоговой панели включаем // переключатель "OEM -> ANSI" CheckDlgButton(hdlg, IDC_OEMANSI, 1); return TRUE; } // ----------------------------------------------------- // Функция DlgProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { // Определяем и сохраняем состояние переключателей case IDOK: { if(IsDlgButtonChecked(hdlg, IDC_OEMANSI)) { // Включен режим преобразования из кодировки // OEM в кодировку ANSI fConversionType = OEM_TO_ANSI; } else if(IsDlgButtonChecked(hdlg, IDC_ANSIOEM)) { // Включен режим преобразования из кодировки // ANSI в кодировку OEM fConversionType = ANSI_TO_OEM; } EndDialog(hdlg, 0); return TRUE; } // Если пользователь нажимает кнопку Cancel, // завершаем работу диалоговой панели без // изменения режима перекодировки case IDCANCEL: { EndDialog(hdlg, 0); return TRUE; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция StartConversion // ----------------------------------------------------- BOOL StartConversion(HWND hwnd) { OPENFILENAME ofn; char szFile[256]; char szDirName[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0"; char szDlgTitle[] = "Select source file"; char szDlgTitleSave[] = "Select destination file"; // Подготавливаем структуру для выбора исходного файла memset(&ofn, 0, sizeof(OPENFILENAME)); GetCurrentDirectory(sizeof(szDirName), szDirName); szFile[0] = '\0'; ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFilter = szFilter; ofn.lpstrInitialDir = szDirName; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrTitle = szDlgTitle; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выводим на экран диалоговую панель, предназначенную // для выбора исходного файла if(GetOpenFileName(&ofn)) { // Если файл выбран, открываем его if (*ofn.lpstrFile) { #if FILEOP == SYNCHRONOUS_IO // В синхронном режиме указываем флаг // FILE_FLAG_SEQUENTIAL_SCAN hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO // В асинхронном режиме необходимо указывать // флаг FILE_FLAG_OVERLAPPED hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); #elif FILEOP == MEMORYMAPPED_IO // В режиме отображения на память мы не используем // дополнительные флаги, однако указываем, что // файл будет открываться не только для чтения, // но и для записи hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); #endif } else return FALSE; } else return FALSE; // Если исходный файл открыть не удалось, // возвращаем значение FALSE if(hSrcFile == INVALID_HANDLE_VALUE) return FALSE; #if FILEOP == MEMORYMAPPED_IO // В том случае, когда используется отображение // файла на память, этот файл перекодируется // "по месту". Для этого используется функция // Oem2Char с одним параметром. Oem2Char(hSrcFile); CloseHandle(hSrcFile); #elif FILEOP == SYNCHRONOUS_IO || FILEOP == ASYNCHRONOUS_IO // При использовании синхронного или асинхронного // режима работы с файлами результат перекодировки // будет записан в новый файл. Поэтому в этом // случае мы открываем второй файл и используем // другой вариант функции Oem2Char - с двумя // параметрами ofn.lpstrTitle = szDlgTitleSave; ofn.Flags = OFN_HIDEREADONLY; if(GetSaveFileName(&ofn)) { // Если файл выбран, открываем его if (*ofn.lpstrFile) { #if FILEOP == SYNCHRONOUS_IO // При использовании синхронных операций указываем // флаг FILE_FLAG_SEQUENTIAL_SCAN hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO // При использовании асинхронных операций // необходимо указать флаг FILE_FLAG_OVERLAPPED hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); #endif } else return FALSE; } else return FALSE; // Если выходной файл открыть не удалось, // возвращаем значение FALSE if(hDstFile == INVALID_HANDLE_VALUE) return FALSE; // Выполняем перекодировку файла hSrcFile с записью // результата перекодировки в файл hDstFile Oem2Char(hSrcFile, hDstFile); // После перекодировки закрываем оба файла CloseHandle(hSrcFile); CloseHandle(hDstFile); #endif // В случае успеха возвращаем значение TRUE return TRUE; } // ----------------------------------------------------- // Функция Oem2Char // ----------------------------------------------------- // Синхронный вариант функции // ----------------------------------------------------- #if FILEOP == SYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile) { DWORD dwBytesRead; DWORD dwBytesWritten; BOOL bResult; // Выполняем перекодировку файла в цикле while(TRUE) { // Читаем блок данных из исходного файла // в буфер cBuf bResult = ReadFile(hSrcFile, cBuf, 2048, &dwBytesRead, NULL); // Проверяем, не был ли достигнут конец файла if(bResult && dwBytesRead == 0) break; // Выполняем преобразование в соответствии с // содержимым глобальной переменной fConversionType if(fConversionType == OEM_TO_ANSI) // Преобразование из OEM в ANSI OemToCharBuff(cBuf, cBuf, dwBytesRead); else if(fConversionType == ANSI_TO_OEM) // Преобразование из ANSI в OEM CharToOemBuff(cBuf, cBuf, dwBytesRead); // Запись содержимого буфера в выходной файл WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, NULL); } } // ----------------------------------------------------- // Функция Oem2Char // ----------------------------------------------------- // Асинхронный вариант функции // ----------------------------------------------------- #elif FILEOP == ASYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile) { DWORD dwBytesRead; DWORD dwBytesWritten; BOOL bResult; DWORD dwError; // Структуры для выполнения асинхронной работы OVERLAPPED ovRead; OVERLAPPED ovWrite; // Инициализация структуры для асинхронного чтения ovRead.Offset = 0; ovRead.OffsetHigh = 0; ovRead.hEvent = NULL; // Инициализация структуры для асинхронной записи ovWrite.Offset = 0; ovWrite.OffsetHigh = 0; ovWrite.hEvent = NULL; // Выполняем перекодировку файла в цикле while(TRUE) { // Запускаем операцию асинхронного чтения bResult = ReadFile(hSrcFile, cBuf, sizeof(cBuf), &dwBytesRead, &ovRead); // Проверяем результат запуска if(!bResult) { // Если произошла ошибка, анализируем ее код switch (dwError = GetLastError()) { // При достижении конца файла завершаем работу // цикла и возвращаемся из функции case ERROR_HANDLE_EOF: { return; } // Операция чтения запущена и еще выполняется case ERROR_IO_PENDING: { // Здесь вы можете разместить вызов функции, // которая будет выполняться параллельно с // только что запущенной операцией чтения // // IdleWork(); // Перед тем как перейти к перекодировке // считанного из файла блока, необходимо // дождаться завершения операции WaitForSingleObject(hSrcFile, INFINITE); // Получаем результат выполнения асинхронной // операции чтения bResult = GetOverlappedResult(hSrcFile, &ovRead, &dwBytesRead, FALSE); if(!bResult) { switch (dwError = GetLastError()) { // При достижении конца файла завершаем работу // цикла и возвращаемся из функции case ERROR_HANDLE_EOF: { return; } default: break; } } } default: break; } } // Получаем результат выполнения асинхронной // операции чтения GetOverlappedResult(hSrcFile, &ovRead, &dwBytesRead, FALSE); // Выполняем преобразование if(fConversionType == OEM_TO_ANSI) OemToCharBuff(cBuf, cBuf, dwBytesRead); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(cBuf, cBuf, dwBytesRead); // Продвигаем указатель позиции, с которой // начнется следующая операция асинхронного // чтения на количество считанных байт ovRead.Offset += dwBytesRead; // Запускаем асинхронную операцию записи bResult = WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, &ovWrite); // Если произошла ошибка, анализируем ее код if(!bResult) { switch (dwError = GetLastError()) { // Операция записи запущена и еще выполняется case ERROR_IO_PENDING: { // Здесь вы можете разместить вызов функции, // которая будет выполняться параллельно с // только что запущенной операцией чтения // // IdleWork(); // Получаем результат выполнения асинхронной // операции записи GetOverlappedResult(hDstFile, &ovWrite, &dwBytesWritten, TRUE); if(!bResult) { switch (dwError = GetLastError()) { default: break; } } } default: break; } } // Продвигаем указатель позиции, с которой // начнется следующая операция асинхронной // записи на количество записанных байт ovWrite.Offset += dwBytesWritten; } } // ----------------------------------------------------- // Функция Oem2Char // ----------------------------------------------------- // Вариант функции с использованием отображения // на память // ----------------------------------------------------- #elif FILEOP == MEMORYMAPPED_IO void Oem2Char(HANDLE hSrcFile) { DWORD dwFileSize; HANDLE hFileMapping; LPVOID lpFileMap; // Получаем и сохраняем размер файла dwFileSize = GetFileSize(hSrcFile, NULL); // Создаем объект-отображение для исходного файла hFileMapping = CreateFileMapping(hSrcFile, NULL, PAGE_READWRITE, 0, dwFileSize, NULL); // Если создать не удалось, возвращаем управление if(hFileMapping == NULL) return; // Выполняем отображение файла на память. // В переменную lpFileMap будет записан указатель на // отображаемую область памяти lpFileMap = MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0); // Если выполнить отображение не удалось, // возвращаем управление if(lpFileMap == 0) return; // Выполняем преобразование файла за один прием if(fConversionType == OEM_TO_ANSI) OemToCharBuff(lpFileMap, lpFileMap, dwFileSize); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(lpFileMap, lpFileMap, dwFileSize); // Отменяем отображение файла UnmapViewOfFile(lpFileMap); // Освобождаем идентификатор созданного // объекта-отображения CloseHandle(hFileMapping); } #endif В листинге 1.2 приведен файл oem2char.h, в котором находятся определения и прототипы функций. Листинг 1.2. Файл oem2char/oem2char.h // Режимы перекодировки #define OEM_TO_ANSI 1 #define ANSI_TO_OEM 2 // ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); void WndProc_OnDestroy(HWND hWnd); LRESULT WINAPI DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify); BOOL StartConversion(HWND hwnd); // Выбираем разный прототип функции в зависимости // от выбранного режима работы с файлами #if FILEOP == MEMORYMAPPED_IO void Oem2Char(HANDLE hSrcFile); #elif FILEOP == SYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile); #elif FILEOP == ASYNCHRONOUS_IO void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile); #endif Файл resource.h (листинг 1.3), который создается автоматически, содержит определения для файла описания ресурсов приложения. Листинг 1.3. Файл oem2char/resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by OEM2CHAR.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDD_DIALOG1 105 #define IDC_OEMANSI 1004 #define IDC_ANSIOEM 1005 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40002 #define ID_FILE_OPTIONS 40004 #define ID_FILE_CONVERT 40005 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 106 #define _APS_NEXT_COMMAND_VALUE 40006 #define _APS_NEXT_CONTROL_VALUE 1010 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif В листинге 1.4 вы найдете файл oem2char.rc. Это файл описания ресурсов приложения. Листинг 1.4. Файл oem2char/oem2char.rc //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Convert...", ID_FILE_CONVERT MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "&Exit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About", ID_HELP_ABOUT END END ////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure // application icon // remains consistent on all systems. IDI_APPICON ICON DISCARDABLE "OEM2CHAR.ICO" IDI_APPICONSM ICON DISCARDABLE "OEM2CHSM.ICO" ////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 169, 59 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Conversion Options" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,103,14,50,14 PUSHBUTTON "Cancel",IDCANCEL,103,31,50,14 GROUPBOX "Conversion type",IDC_STATIC,7,7,78,42 CONTROL "OEM -> ANSI", IDC_OEMANSI,"Button",BS_AUTORADIOBUTTON | WS_GROUP,15,18,61,10 CONTROL "ANSI -> OEM", IDC_ANSIOEM,"Button", BS_AUTORADIOBUTTON,15, 32,61,10 END ////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_DIALOG1, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 162 TOPMARGIN, 7 BOTTOMMARGIN, 52 END END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED Определения и глобальные переменныеТак как объем нашей книги ограничен, мы решили не приводить по одному примеру приложений на каждый метод работы с файлами (синхронный, асинхронный, отображение файла на память), а совместить все в одном приложении. Для этого мы сделали три определения, показанных ниже: #define SYNCHRONOUS_IO 1 // синхронные операции #define ASYNCHRONOUS_IO 2 // асинхроные операции #define MEMORYMAPPED_IO 3 // отображение на память Каждая из этих строк соответствует одному из режимов работы с файлами. Для того чтобы исходные тексты были оттранслированы для использования нужного вам режима работы с файлами, необходимо определить значение макропеременной FILEOP. Это можно сделать одним из трех способов: //#define FILEOP SYNCHRONOUS_IO //#define FILEOP ASYNCHRONOUS_IO #define FILEOP MEMORYMAPPED_IO Перед трансляцией исходных текстов приложения вы должны выбрать одну из этих строк, закрыв две другие символами комментария. При этом с помощью операторов условной трансляции, расположенных в исходных текстах приложения, будет выбран нужный вариант исходного текста. Строка включения файла определений oem2char.h должна расплагаться после строки определения макропеременной FILEOP, так как в этом файле тоже имеются операторы условной трансляции. В глобальной переменной fConversionType хранится текущий режим преобразования, который можно изменить при помощи диалоговой панели Conversion Options, показанной на рис. 1.3. Сразу после запуска приложения в этой переменной хранится значение OEM_TO_ANSI, в результате чего приложение будет выполнять перекодировку из OEM в ANSI. Функция диалога диалоговой панели Conversion Options может записать в переменную fConversionType значение ANSI_TO_OEM. В результате приложение будет перекодировать текстовые файлы из ANSI в OEM. Далее в области глобальных переменных определены переменные для хранения идентификаторов файлов hSrcFile, hDstFile, а также буфер cBuf размером 2048 байт: HANDLE hSrcFile; #if FILEOP != MEMORYMAPPED_IO HANDLE hDstFile; CHAR cBuf[2048]; #endif В переменной hSrcFile хранится идентификатор исходного файла. Что же касается переменной hDstFile, то она предназначена для хранения идентификатора файла, в который будет записан результат перекодировки. Эта переменная, а также буфер для временного хранения перекодируемых данных cBuf не нужны при работе с использованием отображения файла в память. Поэтому если значение макропеременной FILEOP не равно константе MEMORYMAPPED_IO, строки определения переменной hDstFile и буфера cBuf пропускаются при трансляции исходных текстов приложения. Описание функцийПриведем описание функций, определенных в нашем приложении. Функция WinMainФункция WinMain не имеет никаких особенностей. Сразу после запуска приложения она сохраняет идентификатор приложения в глобальной переменной hInst и проверяет, нет ли в памяти копии приложения, запущенной раньше. Для такой проверки используется методика, основанная на вызове функции FindWindow XE "FindWindow" и описанная в предыдущем томе “Библиотеки системного программиста”, посвященного программированию для операционной системы Microsoft Windows NT. Если будет найдена копия работающего приложения, его окно выдвигается на передний план при помощи функций ShowWindow и SetForegroundWindow. Далее функция WinMain регистрирует класс главного окна прилжения, создает и отображает это окно, а затем запускает цикл обработки сообщений. Функция WndProcВ задачу функции WndProc входит обработка сообщений, поступающих в главное окно приложения. Если от главного меню приложения приходит сообщение WM_COMMAND, вызывается функция WndProc_OnCommand. При уничтожении главного окна приложения в его функцию поступает сообщение WM_DESTROY, для обработки которого вызывается функция WndProc_OnDestroy. Все остальные сообщения передаются функции DefWindowProc. Функция WndProc_OnDestroyЭта функция вызывается при уничтожении главного окна приложения для обработки сообщения WM_DESTROY. Функция WndProc_OnDestroy вызывает функцию PostQuitMessage, в результате чего цикл обработки сообщений завершает свою работу. Функция WndProc_OnCommandФункция WndProc_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения. Для обработки мы использовали макрокоманду HANDLE_MSG, описанную в предыдущем томе “Библиотеки системного программиста”. Если пользователь выберет из меню File строку Convert, будет выполняться преобразование файла. Для этого функция WndProc_OnCommand вызовет функцию StartConversion, передав ей в качестве единственного параметра идентификатор главного окна приложения. При выборе из меню File строки Options приложение выведет на экран диалоговую панель Conversion Options, вызвав для этого функцию DialogBox: DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc); Диалоговая панель имеет идентификатор IDD_DIALOG1 и определена в файле описания ресурсов приложения. Функция DlgProcФункция диалога DlgProc обрабатывает сообщения WM_INITDIALOG и WM_COMMAND, поступающие от диалоговой панели Conversion Options. Для обработки этих сообщений вызываются, соответственно, функции DlgProc_OnInitDialog и DlgProc_OnCommand. Функция DlgProc_OnInitDialogСообщение WM_INITDIALOG поступает в функцию диалога при инициализации диалоговой панели. Функция DlgProc_OnInitDialog, обрабатывающая это сообщение, выполняет единственную задачу: она включает переключатель OEM -> ANSI, устанавливая таким образом режим перекодировки из OEM в ANSI: CheckDlgButton(hdlg, IDC_OEMANSI, 1); Функция DlgProc_OnCommandКогда в диалоговой панели Conversion Options пользователь нажимает одну из кнопок или клавиши <Esc> и <Enter>, в функцию диалога поступает сообщение WM_COMMAND. Обработчик этого сообщения, расположенный в функции DlgProc_OnCommand, определяет текущее состояние переключателей режима перекодировки, расположенных на посверхности диалоговой панели, и записывает соответствующее значение в глобальную переменную fConversionType: if(IsDlgButtonChecked(hdlg, IDC_OEMANSI)) { fConversionType = OEM_TO_ANSI; } else if(IsDlgButtonChecked(hdlg, IDC_ANSIOEM)) { fConversionType = ANSI_TO_OEM; } Если при работе с диалоговой панелью пользователь нажимает кнопку Cancel или клавишу <Esc>, содержимое глобальной переменной fConversionType не изменяется. Функция StartConversionВ задачу функции StartConversion входит выбор и открывание исходного файла и файла, в который будет записан результат перекодировки. Когда приложение работает с файлом в режиме отображения на память, открывается только один файл - исходный. Для выбора файла мы использовали функцию GetOpenFileName, хорошо знакомую вам по предыдущим томам “Библиотеки системного программиста”, посвященным программированию для операционной системы Microsoft Windows версии 3.1. Выбранные файлы открываются при помощи функции CreateFile XE "CreateFile" . Однако способ открывания зависит от режима работы с файлами. Ниже мы привели фрагмент исходного текста приложения, в котором открывается исходный файл: #if FILEOP == SYNCHRONOUS_IO hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); #elif FILEOP == MEMORYMAPPED_IO hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); #endif В синхронном режиме исходный файл будет читаться последовательно, поэтому мы указали режим GENERIC_READ (только чтение) и флаг FILE_FLAG_SEQUENTIAL_SCAN. Так как в процессе перекодировки исходный файл не будет изменяться, нет причин запрещать чтение этого файла для других процессов. Чтобы предоставить доступ другим процессам на чтение исходного файла, мы указали режим совместного использования файла FILE_SHARE_READ XE "FILE_SHARE_READ" . В асинхронном режиме необходимо указывать флаг FILE_FLAG_OVERLAPPED, что мы и сделали в нашем примере. Что же касается режима отображения файла на память, то сдесь при открывании файла мы указали режимы GENERIC_READ и GENERIC_WRITE. В результате файл открывается и для чтения, и для записи. После того как в режиме отображения файла на память исходный файл будет открыт, функция StartConversion вызывает функцию Oem2Char, передавая ей в качестве единственного параметра идентификатор исходного файла: Oem2Char(hSrcFile); CloseHandle(hSrcFile); Функция Oem2Char выполняет перекодировку файла “по месту”. Далее идентификатор исходного файла закрывается функцией CloseHandle, после чего функция StartConversion возвращает управление. В синхронном и асинхронном режиме функция StartConversion после открывания исходного файла дополнительно открывает выходной файл, в который будет записан результат перекодировки. Для выбора выходного файла вызывается функция GetSaveFileName. Так же как и исходный файл, выходной файл открывается при помощи функции CreateFile, причем в синхронном и асинхронном режиме этот файл открывается по-разному: #if FILEOP == SYNCHRONOUS_IO hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); #elif FILEOP == ASYNCHRONOUS_IO hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); #endif В синхронном режиме мы указываем режим доступа на запись GENERIC_WRITE и флаг FILE_FLAG_SEQUENTIAL_SCAN (так как запись в выходной файл будет выполняться последовательно от начала до конца). В асинхронном режиме необходимо указать флаг FILE_FLAG_OVERLAPPED. Кроме того, в обоих случаях мы указали режим открывания файла CREATE_ALWAYS. В результате выходной файл будет создан заново даже в том случае, если в выбранном каталоге уже есть файл с таким именем. При этом содержимое старого файла будет уничтожено. После открывания исходного и выходного файла вызывается функция Oem2Char, выполняющая перекодировку, а затем оба файла закрываются при помощи функции CloseHandle: Oem2Char(hSrcFile, hDstFile); CloseHandle(hSrcFile); CloseHandle(hDstFile); Обратите внимание, что в синхронном и асинхронном режиме работы с файлами в нашем приложении используется другой вариант фукнции Oem2Char - вариант с двумя параметрами. Как вы сейчас увидите, в нашем приложении используются три варианта этой функции. Функция Oem2Char (синхронные операции с файлами)Если приложение подготовлено таким образом, что оно работает с файлами при помощи синхронных операций, используется следующий вариант исходного текста функции Oem2Char: void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile) { DWORD dwBytesRead; DWORD dwBytesWritten; BOOL bResult; while(TRUE) { bResult = ReadFile(hSrcFile, cBuf, 2048, &dwBytesRead, NULL); if(bResult && dwBytesRead == 0) break; if(fConversionType == OEM_TO_ANSI) OemToCharBuff(cBuf, cBuf, dwBytesRead); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(cBuf, cBuf, dwBytesRead); WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, NULL); } } Здесь мы работаем с файлом обычным образом. Перекодировка файла выполняется в цикле, который прерывается при достижении конца исходного файла. Функция Oem2Char при помощи функции ReadFile читает фрагмент исходного файла в буфер cBuf, расположенный в области глобальных переменных. Количество прочитанных байт записывается при этом в локальную переменную dwBytesRead. При достижении конца исходного файла количество байт, прочитанных функцией ReadFile из файла, а также значение, возвращенное этой функцией, равно нулю. Этот факт мы используем для завершения цикла перекодировки. После прочтения блока данных из исходного файла функция Oem2Char анализирует содержимое глобальной переменной fConversionType, определяя тип преобразования, который нужно выполнить. В зависимости от содержимого этой переменной вызывается либо функция OemToCharBuff, выполняющая преобразование из кодировки OEM в кодировку ANSI, либо функция CharToOemBuff, которая выполняет обратное преобразование. На следующем этапе преобразованное содержимое буфера cBuf записывается в выходной файл при помощи функции WriteFile. При этом количество действительно записанных байт сохраняется в локальной переменной dwBytesWritten, однако в нашем приложении оно никак не используется. Сделаем важное замечание относительно функций OemToCharBuff и CharToOemBuff. Для преобразования текстовых строк из кодировки OEM в кодировку ANSI и обратно в программном интерфейсе операционной системы Microsoft Windows версии 3.1 имелся набор функций, описанный нами в 12 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть вторая”. Это такие функции как OemToAnsi XE "OemToAnsi" , AnsiToOem XE "AnsiToOem" , OemToAnsiBuff XE "OemToAnsiBuff" , AnsiToOemBuff XE "AnsiToOemBuff" . В программном интерфейсе операционной системы Microsoft Windows NT эти функции оставлены для совместимости, однако ими не рекомендуется пользоваться. Причина заключается в том, что в Microsoft Windows NT можно работать с символами в кодировке Unicode, когда для представления каждого символа используется не один, а два байта. Соответственно, вместо перечисленных выше функций необходимо использовать функции OemToChar, CharToOem, OemToCharBuff, CharToOemBuff, которые имеют такие же параметры, что и их 16-разрядные прототипы (за исключением того, что функциям OemToCharBuff и CharToOemBuff можно передавать 32-разрядную длину преобразуемой текстовой строки). В зависимости от того, используется ли приложением кодировка Unicode, эти функции могут преобразовывать строки из OEM в строки ANSI или Unicode (и обратно). Функция Oem2Char (асинхронные операции с файлами)Вариант функции Oem2Char, предназначенный для использования асинхронных операций с файлами, выглядит сложнее, однако он позволяет приложению выполнять дополнительную работу в то время когда происходит чтение или запись блока данных. В асинхронном режиме для чтения и записи необходимо подготовить структуры типа OVERLAPPED. Мы делаем это следующим образом: OVERLAPPED ovRead; OVERLAPPED ovWrite; ovRead.Offset = 0; ovRead.OffsetHigh = 0; ovRead.hEvent = NULL; ovWrite.Offset = 0; ovWrite.OffsetHigh = 0; ovWrite.hEvent = NULL; Структура ovRead используется для выполнения операций чтения. В поля OffsetHigh и Offset этой структуры мы записываем нулевые значения, поэтому чтение будет выполняться с самого начала файла. Что же касается поля hEvent, то в него мы записываем значение NULL. При этом мы не будем создавать для синхронизации отдельный объект-событие, а воспользуемся идентификатором файла. Структура ovWrite, которая предназначена для выполнения операций записи, используется аналогичным образом. После подготовки структур ovRead и ovWrite функция Oem2Char начинает цикл перекодировки. Прежде всего, в этом цикле запускается операция асинхронного чтения, для чего вызывается функция ReadFile: bResult = ReadFile(hSrcFile, cBuf, sizeof(cBuf), &dwBytesRead, &ovRead); В качестве последнего параметра мы передаем этой функции адрес заранее подготовленной структуры ovRead. Заметим, что в данном случае функция ReadFile вернет управление еще до завершения операции чтения, поэтому в переменную dwBytesRead не будет записано количествао прочитанных байт (так как пока неизвестно, сколько их удастся прочитать). Далее функция Oem2Char проверяет код возврата фукнции ReadFile. При этом дополнительно вызывается функция GetLastError. Если при запуске процедуры чтения был достигнут конец файла, эта функция вернет значение ERROR_HANDLE_EOF. В этом случае функция Oem2Char просто возвращает управление. Если же функция GetLastErrorвернула значение ERROR_IO_PENDING, то это означает, что в настоящий момент происходит выполнение операции чтения. Приложение может вызвать функцию, например, IdleWork, для выполнения какой-либо фоновой работы. Перед тем как продолжить свою работу, функция дожидается завершение операции чтения, вызывая для этого функцию WaitForSingleObject, описанную в предыдущем томе “Библиотеки системного программиста”: WaitForSingleObject(hSrcFile, INFINITE); При этом главная задача приложения перейдет в состояние ожидания до тех пор, пока не закончится операция чтения. Напомним, что в состоянии ожидания задача не отнимает циклы процессорного времени и не снижает производительность системы. Далее функция проверяет результат выполнения асинхронной операции чтения, вызывая функцию GetOverlappedResult: bResult = GetOverlappedResult(hSrcFile, &ovRead, &dwBytesRead, FALSE); Помимо всего прочего, эта функция записывает в локальную переменную dwBytesRead количество байт, прочитанных из исходного файла. После дополнительных проверок ошибок функция Oem2Char выполняет преобразование содержимого буфера, заполненного прочитанными данными. Вслед за этим в структуре ovRead изменяется содержимое поля Offset: ovRead.Offset += dwBytesRead; Значение, которое находится в этом поле, увеличивается на количество прочитанных байт. В результате при очередном вызове функции ReadFile будет запущено чтение для следующего блока данных. Так как мы не изменяем поле OffsetHigh, наше приложение способно работать с файлами, имеющими размер не более 4 Гбайт (что, однако, вполне достаточно). Асинхронная операция записи прочитанных данных запускается при помощи функции WriteFile: bResult = WriteFile(hDstFile, cBuf, dwBytesRead, &dwBytesWritten, &ovWrite); В качестве последнего параметра этой функции передается адрес заранее подготовленной структуры ovWrite. После анализа кода возврата функции WriteFile вызывается функция GetOverlappedResult, с помощью которой определяется результат завершения операции записи: GetOverlappedResult(hDstFile, &ovWrite, &dwBytesWritten, TRUE); Так как через последний параметр этой функции передается значение TRUE, функция GetOverlappedResult выполняет ожидание завершения операции записи. Кроме того, в локальную переменную dwBytesWritten эта функция заносит количество байт данных, записанных в выходной файл. Так как асинхронные операци чтения и записи не изменяют текущую позицию в файле, после выполнения записи мы изменяем соответствующим образом содержимое поля Offset в структуре ovWrite: ovWrite.Offset += dwBytesWritten; Далее цикл перекодировки продолжает свою работу. Функция Oem2Char (отображение файла на память)Третий вариант функции Oem2Char, который используется для работы через отображение файла на память, выглядит очень просто. В нем даже нет цикла, а перекодировка выполняется за один прием: void Oem2Char(HANDLE hSrcFile) { DWORD dwFileSize; HANDLE hFileMapping; LPVOID lpFileMap; dwFileSize = GetFileSize(hSrcFile, NULL); hFileMapping = CreateFileMapping(hSrcFile, NULL, PAGE_READWRITE, 0, dwFileSize, NULL); if(hFileMapping == NULL) return; lpFileMap = MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0); if(lpFileMap == 0) return; if(fConversionType == OEM_TO_ANSI) OemToCharBuff(lpFileMap, lpFileMap, dwFileSize); else if(fConversionType == ANSI_TO_OEM) CharToOemBuff(lpFileMap, lpFileMap, dwFileSize); UnmapViewOfFile(lpFileMap); CloseHandle(hFileMapping); } Вначале функция Oem2Char определяет размер исходного файла, идентификатор которого передается ей через параметр hSrcFile. Для определения размера файла используется функция GetFileSize XE "GetFileSize" . На следующем шаге с помощью функции CreateFileMapping создается объект-отображение. Страницы этого объекта будут доступны и на чтение, и на запись, так как указан режим PAGE_READWRITE XE "PAGE_READWRITE" . Для того чтобы получить доступ к памяти, отображенной на файл, наше приложение вызывает функцию MapViewOfFile. Отображение выполняется для чтения и записи, поэтому мы указали флаг FILE_MAP_WRITE XE "FILE_MAP_WRITE" . В случае успешного завершения функции адрес отображенной области записывается в локальную переменную lpFileMap. Что же касается перекодировки файла, то она выполняется исключительно просто: мы передаем через первые два параметра функции перекодировки адрес, на который отображен файл, а через третий параметр - размер файла. После того как перекодировка будет выполнена, необходимо вначале отменить отображение, а затем освободить идентификатор объекта-отображения. Первая задача решается в нашем приложении спомощью функции UnmapViewOfFile XE "UnmapViewOfFile" , а вторая - с помощью функции CloseHandle XE "CloseHandle" , которой в качестве единственного параметра передается идентификатор объекта-отображения. |