Создание программ, способных использовать и ANSI, и Unicode
Неплохая мысль — заранее подготовить свое приложение к Unicode, даже если Вы пока не планируете работать с этой кодировкой. Вот главное, что для этого нужно:
Разрабатывая программы-примеры для первого издания книги, я сначала написал их так, что они компилировались только с использованием ANSI. Но, дойдя до этой главы (она была тогда в конце), понял, что Unicode лучше, и решил написать примеры, которые показывали бы, как легко создавать программы, компилируемые с применением и Unicode, и ANSI. B конце концов я преобразовал все программы-примеры так, чтобы их можно было компилировать в расчете на любой из этих стандартов.
Конверсия всех программ заняла примерно 4 часа — неплохо, особенно если учесть, что у меня совсем не было опыта в этом деле.
В Windows есть набор функций для работы с Unicode-строками. Эти функции перечислены ниже.
Функция | Описание | ||
lstrcat |
| Выполняет конкатенацию строк | |
lstrcmp | Сравнивает две строки с учетом регистра букв | ||
lstrcmpi | Сравнивает две строки без учета регистра букв | ||
lstrcpy | Копирует строку в другой участок памяти | ||
lstrlen | Возвращает длину строки в символах |
Они реализованы как макросы, вызывающие либо Unicode-, либо ANSI-версию функции в зависимости от того, определен ли UNICODE при компиляции исходного модуля Например, если UNICODE не определен, lstrcat раскрывается в lstrcatA, определен — в lstrcatW.
Строковые функции lstrcmp и lstrcmpi ведут себя не так, как их аналоги из библиотеки С (strcmp, strcmpi, wcscmp и wcscmpf), которые просто сравнивают кодовые позиции в символах строк. Игнорируя фактические символы, они сравнивают числовое значение каждого символа первой строки с числовым значением символа второй строки. Но lstrcmp и lstrcmpi реализованы через вызовы Windows-функции CompareString:
int CompareString(
LCID lcid,
DWORD fdwStyle,
PCWSTR pString1,
int cch1,
PCWSTR pString2,
int cch2);
Она сравнивает две Unicode-строки. Первый параметр задаст так называемый идентификатор локализации (locale ID, LCID) — 32-битное значение, определяющее конкретный язык. С помощью этого идентификатора CompareString сравнивает строки с учетом значения конкретных символов в данном языке. Так что она действует куда осмысленнее, чем функции библиотеки С.
Когда любая из функций семейства lstrcmp вызывает CompareString, в первом параметре передается результат вызова Windows-функции GetThreadLocale.
LCID GetThreadLocale();
Она возвращает уже упомянутый идентификатор, который назначается потоку в момент его создания.
Второй параметр функции CompareString указывает флаги, модифицирующие метод сравнения строк. Допустимые флаги перечислены в следующей таблице.
Флаг | Действие |
NORM_IGNORECASE | Различия в регистре букв игнорируются |
NORM_IGNOREKANATYPE | Различия между знаками хираганы и катаканы игнорируются |
NORM_IGNORENONSPACE | Знаки, отличные от пробелов, игнорируются |
NORM_IGNORESYMBOLS | Символы, отличные от алфавитно-цифровых, игнорируются |
NORM_IGNOREWIDTH |
Разница между одно- и двухбайтовым представлением одного и того же символа игнорируется |
SORT_STRINGSORT |
Знаки препинания обрабатываются так же, как и символы, от- личные от алфавитно-цифровых |
Вызывая CompareString, функция lstrcmp передает в параметре fdwStyle нуль, а lstrcmpi — флаг NORM_IGNORECASE. Остальные четыре параметра определяют две строки и их длину. Если cch1 равен -1, функция считает, что строка pString2 завершается нулевым символом, и автоматически вычисляет ее длину. То же относится и к параметрам cch2 wpString2.
Многие функции С-библиотеки с Unicode-строками толком не работают. Так, tolower и toupper неправильно преобразуют регистр букв со знаками ударения. Поэтому для Unicode-строк лучше использовать соответствующие Windows-функции. К тому же они корректно работают и с ANSI-строками.
Первые две функции:
PTSTR CharLower(PTSTR pszStnng);
PTSTR CharUpper(PTSTR pszString);
преобразуют либо отдельный символ, либо целую строку с нулевым символом в конце. Чтобы преобразовать всю строку, просто передайте ее адрес. Но, преобразуя отдельный символ, Вы должны передать его так:
TCHAR cLowerCaseCnr = CharLower((PTSTR) szString("O"));
Приведение типа отдельного символа к PTSTR вызывает обнуление старших 16 битов передаваемого значения, а в его младшие 16 битов помещается сам символ. Обнаружив, что старшие 16 битов этого значения равны 0, функция поймет, что Вы хотите преобразовать не строку, а отдельный символ. Возвращаемое 32-битное зна чение содержит результат преобразования в младших 16 битах.
Следующие две функции аналогичны двум предыдущим за исключением того, что они преобразуют символы, содержащиеся в буфере (который не требуется завершать нулевым символом).
DWORD CharLowerBuff(
PTSTR pszString,
DWORD cchString);
DWORD CharUppprRuff(
PTSTR pszString,
DWORD cchString);
Прочие функции библиотеки С (например, isalpba, islowern isupper) возвращают значение, которое сообщает, является ли данный символ буквой, а также строчная она или прописная. В Windows API тоже есть подобные функции, но они учитывают и язык, выбранный пользователем в Control Panel:
BOOL IsCharAlpha(TCHAR ch);
BOOL IsCharAlphaNumeric(TCHAR ch);
BOOL IsCharLower(TCHAR oh);
BOOL IsCharUpper(TCHAR ch);
И последняя группа функций из библиотеки С, о которых я хотел рассказать, — prmtf. Если при компиляции _UNICODE определен, они ожидают передачи всех символьных и строковых параметров в Unicode; в ином случае — в ANSI.
Microsoft ввела в семейство фупкций printf своей С-библиотеки дополнительные типы полей, часть из которых не поддерживается в ANSI C. Они позволяют легко сравнивать и смешивать символы и строки с разной кодировкой. Также расширена функция wsprintf операционной системы. Вот несколько примеров (обратите внимание на использование буквы s в верхнем и нижнем регистре):
char szA[100]; // строковый буфер e ANSI
WCHAR szW[100]; // строковый буфер в Unicode
// обычный вызов sprintf: все строки в ANSI
sprintf(szA, "%s", "ANSI Str");
// преобразуем строку из Unicode в ANSI
sprintf(szA, "%S", "Unicode Str");
// обычный вызов swprintf. все строки в Unicode
swprintf(szW, L"%s", L"Unicode Str");
// преобразуем строку из ANSI в Unicode
swprintf(s/W, L"%S", "ANSI Str");