Unicode: решение, породившее новые проблемыА.В. Фролов, Г.В. Фролов, (МИР ПК #11/98, 12/98)E-mail: frolov@glasnet.ru
Стандарт Unicode был предложен некоммерческой организацией Unicode Consortium, образованной в 1991 г. Для представления каждого символа в этом стандарте используются два байта, что позволяет закодировать очень большое число символов из разных письменностей: в документах Unicode могут соседствовать русские, латинские, греческие буквы, китайские иероглифы и математические символы. Кодовые страницы становятся ненужными. Общие сведенияКоды в Unicode разделены на несколько областей. Область с кодами от 0000 до 007F содержит символы набора Latin 1 (младшие байты соответствуют кодировке ISO 8859-1). Далее идут области, в которых расположены знаки различных письменностей, а также знаки пунктуации и технические символы; часть кодов зарезервирована для использования в будущем. Символам кириллицы выделены коды в диапазоне от 0400 до 0451. Для работы с документами Unicode нужны соответствующие шрифты. Как правило, файл шрифта Unicode содержит начертания не для всех символов, определенных в стандарте, а лишь для символов из некоторых областей. Unicode в Windows NTЯдро Windows NT, ее графический интерфейс (GDI) и файловая система NTFS реализованы с использованием Unicode. Программы, запущенные в среде NT, могут работать также с однобайтовыми символами, кодировка которых в этом случае соответствует установленной по умолчанию кодовой странице ANSI (для России - Windows Cyrillic, или CP 1251). Перед вызовом некоторых функций программного интерфейса NT программы, работающие с кодовой страницей ANSI, преобразуют однобайтовые символы в Unicode. Чтобы преобразование выполнялось без ошибок, пользователь должен правильно указать страну в приложении Regional Settings. Это необходимо также для корректной работы с национальными символами программ, запущенных в сеансе MS-DOS; они используют кодовую страницу OEM, которая не всегда совпадает со страницей Windows (в частности, для России это CP 866). Unicode в Windows 95В отличие от Windows NT, ядро и графический интерфейс Windows 95 не используют Unicode, а работают с кодовыми страницами. Однако в этой ОС предусмотрена возможность динамического изменения наборов символов и раскладок клавиатуры, что позволяет создавать документы, содержащие одновременно символы из разных наборов. Буфер обмена Windows 95 способен хранить тексты в формате CF_UNICODETEXT. В составе Windows 95 поставляется набор шрифтов Unicode, с которыми, в частности, могут работать программы Microsoft Office 97. Исследование шрифтов UnicodeВыяснить, какие наборы символов присутствуют в том или ином шрифте Unicode, можно с помощью стандартной утилиты Character Map (таблица символов), включенной в состав Windows NT (аналогичная программа из Windows 95 не подходит). Запустите ее и выберите в списке Font шрифт Arial, а в списке Subset - набор символов Windows Characters. Получившаяся таблица будет повторять кодовую страницу ANSI с символами западноевропейской латиницы. Выбрав же в списке Subset строку Cyrillic, вы увидите таблицу с символами кириллицы и без символов латиницы, соответствующую кириллической области Unicode. Для программ, работающих с кодовыми страницами ANSI, вместо шрифта Arial будет подставлен (в зависимости от страницы, установленной в системе) Arial Cyr, Arial Baltic, Arial CE, Arial Greek и т. д. Чтобы увидеть, как происходит подстановка, запустите программу regedt32.exe и на странице HKEY_LOCAL_MACHINE on Local Machine в разделе SOFTWARE\Microsoft\Windows NT\Current Version раскройте папку Fonts. Вы убедитесь, что шрифтов, подобных Arial Cyr, там нет. Это виртуальные шрифты, которые создаются специально для программ, работающих с кодовыми страницами ANSI. Раскрыв папку FontSubstitutes того же раздела, можно определить, что, например, шрифт Arial Cyr образуется из шрифта Arial с использованием таблицы преобразования 204. Техника, применяемая в Windows 95, аналогична описанной с одним отличием: шрифты для подстановки определяются не в Реестре, а в файле win.ini (раздел FontSubstitutes). Работа с символами UnicodeИспользование Unicode значительно упрощает создание многоязычных приложений. Поэтому, создавая программы с прицелом на этот стандарт, вы закладываете неплохую базу для локализации своего программного продукта. Ниже мы опишем работу с символами Unicode в среде Windows на языке Си (примеры проверялись с пакетом Microsoft Visual C++ 4.0 под управлением Windows NT 4.0 и Windows 95). Определение символьных данных UnicodeПеременныеДля обычных символьных данных - символов ANSI - в языке Си используется тип char; переменная этого типа занимает в памяти один байт. Для хранения символов Unicode необходимо два байта, из-за чего их называют широкими символами (wide characters). В языке Си им соответствует тип wchar_t, с которым связаны макрокоманды WCHAR и TCHAR, описанные в файле winnt.h. При определении данных Unicode рекомендуется пользоваться не непосредственно типом wchar_t, а макрокомандами. Макрокоманда WCHAR определена: Макрокоманда TCHAR универсальная, она позволяет определять как символы Unicode, так и символы ANSI. Если в начале текста программы имеется инструкция #define UNICODE, препроцессор преобразует запись TCHAR в wchar_t, если нет - в char: #ifdef UNICODE typedef wchar_t WCHAR; typedef WCHAR TCHAR; #endif В файле winnt.h содержатся также макрокоманды для определения указателей на символы Unicode и универсальные макрокоманды (на базе TCHAR), с помощью которых можно создавать указатели на символы как ANSI, так и Unicode. Эти макрокоманды обеспечивают корректную работу при выполнении арифметических операций над указателями и при использовании указателей для индексации массивов символов Unicode. Модифицируя исходный текст своей программы для работы с Unicode, замените типы данных char, char*, а также LPSTR и LPCSTR соответствующими макрокомандами для Unicode. Кроме того, проверьте, не пользуетесь ли вы где-нибудь тем обстоятельством, что символ занимает в памяти один байт, и если да, внесите необходимые исправления. КонстантыЛитеральные константы, соответствующие символам Unicode, записываются как обычные символьные константы, только с префиксом L: char chStar = '*'; /* однобайтовый символ */ WCHAR chStar = L'*'; /* символ Unicode */ char szStar[] = ''Star''; /* строка однобайтовых символов */ WCHAR szStar[] = L''Star''; /* строка символов Unicode */ Универсальная макрокоманда TEXT позволяет определять символьные константы как двух- или однобайтовые в зависимости от того, работает ли программа с Unicode или нет: TCHAR chStar = TEXT('*'); TCHAR szStar[] = TEXT(''Star''); При наличии в тексте программы инструкции #define UNICODE такая запись будет преобразована в определение константы Unicode (с префиксом L), а при ее отсутствии - в определение обычной символьной константы. Специальные символыСимвольная строка ANSI закрывается нулевым байтом, а строка Unicode - 16-разрядным нулем. Для обозначения конца такой строки можно использовать запись TEXT('\0'). Код символов CR (возврат каретки) и LF (перевод строки) в Unicode соответственно 0x000D и 0x000A (в кодировке ASCII - 0x0D и 0x0A); не заменяйте их последовательность на символ с кодом 0x0D0A. В Unicode определены также символы разделения строк (0x2028) и абзацев (0x2029). Символы с кодами 0xFEFF и FFEF служат сигнатурами текстовых файлов Unicode; о них мы подробнее расскажем в конце статьи. Функции Win32 и UnicodeБольшинство прототипов функций Win32, получающих в качестве параметров текстовые строки или символы, реализованы как макрокоманды. В зависимости от наличия в тексте программы инструкции #define UNICODE препроцессор Си подставляет вместо имени макрокоманды имя функции с суффиксом W (для Unicode) или A (для однобайтовых символов). Благодаря этому большинством стандартных библиотечных функций Cи можно пользоваться в программах, работающих с Unicode, точно так же, как в программах, работающих с символами ANSI. Это, в частности, относится к функциям для работы со строками, такими как strcat и printf. Однако есть и исключения; например, функции atoi и atol всегда ожидают строку символов ANSI, так что перед обращением к ним строка Unicode должна быть преобразована. И разумеется, во всех случаях необходимо следить за правильностью передачи параметров. Рассмотрим в качестве примера известную функцию MessageBox, которая выводит на экран простейшую диалоговую панель с сообщением. В файле winuser.h для нее имеется два прототипа - MessageBoxA и MessageBoxW. Текст программы MsgUni, работающей с Unicode и вызывающей функцию MessageBox, приведен в листинге. Вывод диалоговой панели с русским текстом в кодировке Unicode // Файл MsgUni.c // #define UNICODE #include "windows.h" TCHAR szAppName[] = TEXT(''Приложение MsgUni''); TCHAR szAppTitle[] = TEXT(''Привет!''); //char szAppTitle[] = ''Привет!''; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MessageBox(NULL, szAppTitle, szAppName, MB_OK); return 0; } Если же заменить корректное определение
строковой константы Вот некоторые функции программного интерфейса Win32, полезные при работе с Unicode. Функции GetACP и GetOEMCP возвращают номера установленных в Windows кодовых страниц ANSI и OEM, которые нужны для корректного преобразования символов ANSI в Unicode. Функция GetTextCharset сообщает идентификатор набора Unicode для заданного контекста отображения. Более подробную информацию о шрифте Unicode можно получить при помощи функции GetTextCharsetInfo. Функции mbstowcs и MultiByteToWideChar преобразуют строку однобайтовых символов в строку Unicode, функции wcstombs и WideCharToMultiByte выполняют обратное преобразование. Выбор шрифта для контекста отображенияРассмотрим способы отображения символов кириллицы на примере программы WINDOW, сокращенный текст которой приведен в листинге 2. Программа выводит в своем окне номер и название установленного в системе набора символов, затем название текущего шрифта и строку символов "AaBbCc АаБбВв". В окне есть меню Font, позволяющее выбрать для этого сообщения шрифт и набор символов. Вообще говоря, стандартная диалоговая панель Windows для выбора шрифта, которую открывает наша программа при выборе в меню Font пункта Select font, содержит в правом нижнем углу поле Script, позволяющее выбрать набор символов для заданного шрифта. Однако нам необходима непосредственная работа с наборами символов, поэтому мы реализовали в программе специальное меню Font. Для выбора шрифта в программе применяется функция ChooseFont, которая берет необходимую ей информацию из структуры типа CHOOSEFONT. Эта структура содержит поле с именем lpLogFont, указывающее, в свою очередь, на структуру типа LOGFONT. Номер набора символов задается в поле lfCharSet структуры LOGFONT. Программа работает с четырьмя наборами: ANSI_CHARSET, OEM_ CHARSET, RUSSIAN_CHARSET и DEFAULT_CHARSET. Описанная картина, возможно, покажется знакомой тем, кто имеет опыт программирования в Windows 3.1. Однако если в Windows 3.1 можно работать с русскими буквами, задав набор символов ANSI_CHARSET или DEFAULT_CHARSET, то здесь, как мы сейчас убедимся, ситуация совершенно иная. Откомпилируйте программу WINDOW в двух вариантах - с инструкцией #define UNICODE и без нее. Первый вариант, рассчитанный на Unicode, можно будет запускать только под управлением Windows NT, второй, работающий с символами ANSI, - и под управлением Windows NT, и под управлением Windows 95. Начнем наши эксперименты с ANSI-программы. Запустим ее и выберем шрифт Courier New. Символы кириллицы отобразятся неправильно. Почему? Запустив утилиту Character Map, несложно определить, что Courier New - это шрифт Unicode. Следовательно, ANSI-программа не может работать с ним непосредственно, и система строит на основе Courier New виртуальный шрифт для кодовой страницы, которая определяется набором символов, выбранным для данного контекста отображения. Поскольку мы не заказывали никакой конкретный набор, шрифт был построен для набора DEFAULT_CHARSET, не содержащего символов кириллицы. Отметим, что DEFAULT_CHARSET никак не связан с набором символов, установленным в системе (в нашем случае - кириллическим), хотя это и можно было бы предположить, исходя из его названия. Если выбрать тот же шрифт, предварительно отметив в меню Font строку RUSSIAN_CHARSET, русские буквы появятся, поскольку система построит заказанный ей виртуальный шрифт на основе кириллического набора. Более того, если выбрать шрифт, не содержащий символов кириллицы, система попытается подобрать ближайший эквивалент, в котором эти символы присутствуют. Что же касается набора ANSI_CHARSET, то для него, как и для DEFAULT_CHARSET, будет построен шрифт без кириллицы. Таким образом, единственный надежный способ получить кириллицу в ANSI-программе, при том что шрифт построен по стандарту Unicode, - это задать в поле lfCharSet структуры LOGFONT значение RUSSIAN_CHARSET. Запустим теперь Unicode-программу. Как легко убедиться, она правильно выводит кириллицу независимо от того, какой набор символов задать в меню Font (если, конечно, выбран шрифт Unicode, содержащий кириллицу). Оно и понятно: программа работает непосредственно с Unicode-шрифтом, а набор символов, который нужен для построения виртуального шрифта, здесь никак не используется. Однако картина кардинально изменится при выборе шрифта Baltica, разработанного для Windows 3.1. Программа ANSI будет правильно выводить русские буквы независимо от заданного набора символов, а программа Unicode не будет их выводить вообще. Правда, если в последнем случае заранее отметить в меню Font строку RUSSIAN_CHARSET, русские буквы появятся, но шрифт будет другим (например, MS Sans Serif). Дело в том, что в шрифте Baltica, подготовленном в старом формате, кириллическая область Unicode не представлена. В результате ANSI-программа, которая работает с этим шрифтом непосредственно (т. е. без построения виртуального шрифта), показывает кириллицу, а Unicode-программа - нет. Этот эффект, несомненно, знаком многим пользователям Office 97. В программах Office 97 (и для Windows NT, и для Windows 95) последовательно проведено представление всей текстовой информации в Unicode. Поэтому если, например, открыть в Word 97 документ, подготовленный в Word 6 или 7 с использованием старых шрифтов, вместо русских букв в окне появятся квадратики: так обозначается отсутствие в выбранном шрифте нужного символа. Единственный возможный выход здесь - изменить шрифт на соответствующий стандарту Unicode. Перекодирование однобайтовых символов в Unicodе и обратноДля перекодирования символов из ANSI в Unicode и обратно служат, как уже упоминалось, четыре функции: mbstowcs и MultiByteToWideChar преобразуют строку однобайтовых символов в строку Unicode, а wcstombs и WideCharToMultiByte выполняют обратное преобразование (mbstowcs и wcstombs - это функции стандартной библиотеки Си, а MultiByteToWideChar и WideCharToMultiByte - функции программного интерфейса Win32). Функции mbstowcs и wcstombs принимают по три параметра: указатель на буфер, в который будет записан результат перекодирования, указатель на исходную строку и число перекодируемых символов. Тем самым они никак не учитывают при преобразовании набор символов, а значит, ими можно пользоваться лишь в самом простом случае (фактически их корректная работа гарантирована лишь при условии, что все символы относятся к набору Latin 1). В более сложных ситуациях (в частности, если предполагается работа с кириллицей) следует использовать функции MultiByteToWideChar и WideCharToMultiByte. У функции MultiByteToWideChar пять параметров. Первый - это номер кодовой страницы для исходной строки. Страницу можно задать как непосредственно, так и константой типа CP_ACP (установленная в системе кодовая страница ANSI), CP_OEMCP (установленная в системе страница OEM), CP_THREAD_ACP (страница текущего потока). Второй параметр - набор флагов, определяющий способ обработки диакритик (надстрочных и подстрочных знаков) и неправильных символов. Флаг MB_PRECOMPOSED означает, что базовый символ и диакритика объединены, несовместимый с ним флаг MB_COMPOSITE - что диакритики записаны как отдельные символы. Флаг MB_ERR_INVALID_CHARS задает проверку корректности символов в исходной строке. Оставшиеся параметры определяют адрес и длину входной и выходной строк. Функция WideCharToMultiByte дополнительно к перечисленным принимает еще два параметра, которые нужны, если применяется обработка неправильных символов. В листинге 3 приводится текст простейшей программы txt2uni, перекодирующей текстовые файлы из ANSI в Unicode. Программа открывает файл в двоичном режиме и считывает его по байтам в символьную переменную ch. Для каждого байта вызывается функция mbstowcs или MultiByteToWideChar (вы можете попробовать любую), а результат перекодирования помещается в переменную wch типа wchar_t, после чего записывается в выходной файл функцией fputwc, специально предназначенной для работы с символами Unicode. Листинг 4 содержит текст аналогичной программы uni2txt.c, преобразующей файл Unicode в текстовый файл ANSI. Это Unicode-программа, ее первая строка представляет собой инструкцию #define UNICODE. Вместо привычной вам функции main использована функция wmain, которая получает параметры в виде строк Unicode. Таким образом, путь к исходному файлу передается функции _wfopen как строка Unicode. Для открытия исходного файла применяется функция _wfopen, а для считывания из него - функция fgetwc; обе они предназначены специально для Unicode-программ. В самое начало выходного файла программа txt2uni записывает байты 0xFF и 0xFE. Это сигнатура, определяющая порядок следования байтов в символах Unicode. Байты следуют в том же порядке, что и разряды в машинном слове на той компьютерной платформе, где создается файл. Для процессоров Intel, где первым идет самый младший бит, сигнатура имеет значение 0xFEFF. Противоположному порядку (первый бит - старший), применяемому, например, в компьютерах Macintosh, соответствует сигнатура 0xFFEF. Программа uni2txt проверяет сигнатуру и, если она не равна 0xFEFF, завершается с ошибкой. В чуть более сложном варианте программа могла бы осуществлять необходимую перестановку. Работа с набором символов// Файл window.c #define UNICODE #include <windows.h> #include <windowsx.h> #include "resource.h" // ---------------------- // Прототипы функций // ---------------------- . . . // ---------------------- // Глобальные переменные // ---------------------- HINSTANCE hInst; TCHAR szAppName[] = TEXT("Window"); TCHAR szAppTitle[] = TEXT("Просмотр шрифтов"); static CHOOSEFONT cf; static LOGFONT lf; static HFONT hfont, hfOldFont; UINT uCurrentCharset = DEFAULT_CHARSET; TCHAR szChars[] = TEXT(": AaBbCc АаБбВв"); TCHAR szBuf[256]; TCHAR szFontInfo[256]; // ---------------------- // WinMain // ---------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; hInst = hInstance; . . . // Стандартные действия: регистрация класса // окна, создание окна и его отображение . . . CheckMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_FONT_DEFAULTCHARSET, MF_BYCOMMAND | MF_CHECKED); . . . // Цикл обработки сообщений . . . } // ---------------------- // WndProc // ---------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { . . . // Обработка сообщений . . . } } // ---------------------- // WndProc_OnCommand // ---------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch(id) { case ID_FILE_EXIT: { PostQuitMessage(0); return 0L; break; } case ID_FONT_SELECT: { memset(&lf, 0, sizeof(LOGFONT)); if(GetFont(hWnd, &lf, &cf)) InvalidateRect(hWnd, NULL, TRUE); return 0L; break; } case ID_FONT_DEFAULTCHARSET: { uCurrentCharset = DEFAULT_CHARSET; MenuItemsUncheck(hWnd); CheckMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_FONT_DEFAULTCHARSET, MF_BYCOMMAND | MF_CHECKED); return 0L; break; } case ID_FONT_RUSSIANCHARSET: { uCurrentCharset = RUSSIAN_CHARSET; MenuItemsUncheck(hWnd); CheckMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_FONT_RUSSIANCHARSET, MF_BYCOMMAND | MF_CHECKED); return 0L; break; } case ID_FONT_ANSICHARSET: { uCurrentCharset = ANSI_CHARSET; MenuItemsUncheck(hWnd); CheckMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_FONT_ANSICHARSET, MF_BYCOMMAND | MF_CHECKED); return 0L; break; } case ID_FONT_OEMCHARSET: { uCurrentCharset = OEM_CHARSET; MenuItemsUncheck(hWnd); CheckMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_FONT_OEMCHARSET, MF_BYCOMMAND | MF_CHECKED); return 0L; break; } } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ---------------------- // WndProc_OnPaint // ---------------------- #pragma warning(disable: 4098) void WndProc_OnPaint(HWND hWnd) { HDC hdc; PAINTSTRUCT ps; UINT nCharSet; hdc = BeginPaint(hWnd, &ps); lf.lfOrientation = lf.lfEscapement = 0; lf.lfCharSet = uCurrentCharset; hfont = CreateFontIndirect(&lf); if(hfont) { nCharSet = GetTextCharset(hdc); wsprintf(szFontInfo, TEXT("Charset: %u "), nCharSet); switch(nCharSet) { case ANSI_CHARSET: { lstrcat(szFontInfo, TEXT("(ANSI_CHARSET)")); break; } case OEM_CHARSET: { lstrcat(szFontInfo, TEXT("(OEM_CHARSET)")); break; } case RUSSIAN_CHARSET: { lstrcat(szFontInfo, TEXT("(RUSSIAN_CHARSET)")); break; } case EASTEUROPE_CHARSET: { lstrcat(szFontInfo, TEXT("(EASTEUROPE_CHARSET)")); break; } case DEFAULT_CHARSET: { lstrcat(szFontInfo, TEXT("(DEFAULT_CHARSET)")); break; } } TextOut(hdc, 20, 20, szFontInfo, lstrlen(szFontInfo)); hfOldFont = SelectFont(hdc, hfont); GetTextFace(hdc, 80, szBuf); lstrcat(szBuf, szChars); SetTextColor(hdc, cf.rgbColors); TextOut(hdc, 20, 40, szBuf, lstrlen(szBuf)); SelectFont(hdc, hfOldFont); DeleteFont(hfont); } EndPaint(hWnd, &ps); return 0; } // ---------------------- // WndProc_OnCreate // ---------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { return TRUE; } // ---------------------- // WndProc_OnDestroy // ---------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return FORWARD_WM_DESTROY(hWnd, DefWindowProc); } // ---------------------- // GetFont // ---------------------- BOOL GetFont(HWND hWnd, LOGFONT *lf, CHOOSEFONT *cf) { LPSTR szFontStyle[LF_FACESIZE]; memset(cf, 0, sizeof(CHOOSEFONT)); cf->lStructSize = sizeof(CHOOSEFONT); cf->hwndOwner = hWnd; cf->lpLogFont = lf; cf->Flags = CF_SCREENFONTS | CF_USESTYLE | CF_EFFECTS; cf->lCustData = 0L; cf->rgbColors = RGB(0,0,0); cf->lpfnHook = NULL; cf->lpTemplateName = (LPCTSTR)NULL; cf->hInstance = hInst; cf->lpszStyle = (LPTSTR)szFontStyle; cf->nFontType = SCREEN_FONTTYPE; cf->nSizeMin = 0; cf->nSizeMax = 0; return ChooseFont(cf); } // ---------------------- // MenuItemsUncheck // ---------------------- void MenuItemsUncheck(HWND hWnd) { int i; HMENU hmenu = GetSubMenu(GetMenu(hWnd), 1); for (i=0; i<GetMenuItemCount(hmenu) ; i++) { CheckMenuItem(hmenu, i, MF_BYPOSITION | MF_UNCHECKED); } } Перекодирование однобайтовых символов в Unicode// Файл txt2uni.c #include <windows.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[], char *envp[]) { FILE *SrcFile; FILE *DstFile; char ch; wchar_t wch; if(argc > 1) { if((SrcFile = fopen(argv[1], "rb")) == NULL) return 1; if((DstFile = fopen("!output.txt","wb+")) == NULL) return 1; fputc(0xFF, DstFile); fputc(0xFE, DstFile); while(TRUE) { ch = fgetc(SrcFile); if(feof(SrcFile)) break; // mbstowcs(&wch, &ch, 1); MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, &ch, 1, &wch, 1); fputwc(wch, DstFile); } fclose(SrcFile); fclose(DstFile); } else { printf("Use: txt2uni <filename>"); } return 0; } Перекодирование символов Unicode в однобайтовые// Файл uni2txt.c #define UNICODE #include <windows.h> #include <stdio.h> #include <stdlib.h> int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) { FILE *SrcFile; FILE *DstFile; char ch; wchar_t wch; wchar_t sig = 0xFEFF; if(argc > 1) { if((SrcFile = _wfopen(argv[1], TEXT("rb"))) == NULL) return 1; if((DstFile = fopen("!output.txt","wb+")) == NULL) return 1; wch = fgetwc(SrcFile); if(feof(SrcFile)) return 1; if(memcmp(&wch, &sig, 2)) return 1; while(TRUE) { wch = fgetwc(SrcFile); if(feof(SrcFile)) break; // wcstombs(&ch, &wch, 1); WideCharToMultiByte(CP_ACP, 0, &wch, 1, &ch, 1, NULL, NULL); fputc(ch, DstFile); } fclose(SrcFile); fclose(DstFile); } else { printf("Use: uni2txt <filename>"); } return 0; } Об авторах: Братья Александр Вячеславович и Григорий Вячеславович Фроловы - авторы серий книг "Библиотека системного программиста" и "Персональный компьютер. Шаг за шагом". E-mail: alexandre@frolov.pp.ru; http://www.frolov.pp.ru Размещено с разрешения редакции журнала МИР ПК |