Автор : Сукиязов
Сергей Александрович
Введение.
Эта статья содержит информацию об одной из популярных
библиотек для организации графического пользовательского интерфейса (GUI)
- Qt Toolkit, разработаной
норвежской фирмой Troll Tech.
Библиотека Qt
представляет собой законченное и глубоко проработанное многоплатформенное
объектноориентированное окружение для разработки GUI-приложений с
использованием языка C++. Qt также
хорошо интегрируется с библиотеками OpenGL/Mesa 3D.
Qt является
бесплатной (free) библиотекой для разработки бесплатного программного
обеспечения (freeware) в X Window
System. Она включает в себя полный исходный код X-версии библиотеки и
make-файлы для операционных систем Linux, Solaris, SunOS, FreeBSD и др. Эта редакция (free) Qt может модифицироваться и
распространяться с соблюдением условий перечисленных в файле
LICENSE.QPL
.
Qt также очень хорошо
поддерживает операшионные системы Windows 95 и NT. Программный код
разработанный для X-версии Qt
может быть перекомпилирован и использован под управлением Windows 95/NT
версии библиотеки Qt.
В настоящее время Qt используется в сотнях проектов по
разработке программного обеспечения по всему миру, включая популярную
оболочку K Desktop Environment. Для
более полной информации смотрите ссылку http://www.trolltech.com/qtprogs.html.
Qt можно загрузить
по адресу http://www.trolltech.com/dl/ или
через анонимный FTP с сервера ftp://ftp.trolltech.com/. На этом
сервере также доступны нестабильные версии, находящиеся на стадии
разработки, в виде ежедневных "снапшотов".
Qt содержит
замечательную документацию: более 750 страниц в формате Postscript и HTML.
Документация также доступна через WEB: http://doc.trolltech.com/.
Qt является
полностью объектноориентированной библиотекой. Все "виджеты" представляют
собой C++ объекты, и, используя наследование, создание новых "виджетов"
получается простым и естественным.
В рамках этой статьи мы не будем подробно разбирать все
детали программирования с использованием библиотеки Qt. Как говорилось выше Qt содержит подробную документацию и
множество примеров. В этой статье мы остановимся на проблемах корректной
локализации (интернационализации) программ, разработанных с использованием
Qt, и попытаемся выработать
некоторые советы по использованию классов библиотеки Qt, которые помогут избежать проблем
с интернационализацией программ.
Т.к. библиотека Qt
для представления текстовых данных использует UNICODE, то при некорректном
преобразовании текстовых данных из однобайтовых кодировок в UNICODE и
проявляются проблемы с отображением национальных символов.
Внешне эти проблемы выглядят следующим образом: вместо
национальных символов (коды более 127) выводится символ '?' или вместо
строки, содержащей национальные UNICODE-символы (коды более U+00FF), в
результате преобразований получаются пустые однобайтовые строки.
Может сложиться впечатление, что в Qt вопрос преобразования из
однобайтовых строк к UNICODE-строкам недостаточно продуман. На самом деле
это не так. В Qt преобразование
строк достачно хорошо продуманно и описано в документации. К единственному
недостатку докуметации можно отнести тот факт, что документацию писали в
большинстве своем англоязычные программисты и, соответственно, вопросам
использования национальных UNICODE-символов уделено незначительное
внимание.
Дело в том, что англоязычные программисты используют
кодировку ISO-8859-1 (или US-ASCII), коды символов которой совпадают с
UNICODE-кодами этих символов. В этом случае преобразование из однобайтовой
строки в UNICODE-строку сводится к простому расширению однобайтового
значения до двухбайтового (просто в сташий байт заносится значение 0). Для
национальных символов преобразование не столь тривиально: русская буква
'А' имеет код в кодировке ISO-8859-5 равный 0xB0, в кодировке UNICODE -
код равный U+0410. В результате простого расширения однобайтового значения
русская буква 'А' получит UNICODE код U+00B0 вместо U+0410, что далеко не
одно и тоже.
Далее мы проанализируем причины приводящие к возникновению
этих ошибок и разберем несколько советов по их устранению.
Представление строк в Qt.
В библиотеке Qt,
как говорилось выше, внутренний формат строк - UNICODE. Поэтому почти все
методы и функции библиотеки Qt в
качестве своих фактических параметров желают иметь именно UNICODE строки.
Для хранения кажого символа в кодировке UNICODE в Qt отводится два байта. Для
представления UNICODE символа в Qt
используется класс QChar
. Полное и подробное описание
конструкторов, методов и операторов этого класса можно посмотреть в
документации по библиотеке Qt.
Преобразование по умолчанию из одобайтового символа (C тип
char
) в UNICODE символ (Qt тип QChar
)
выполняется простым расширением значения. Т.е. русская буква 'А' (код в
ISO-8859-5 - 0xB0) получает UNICODE код U+00B0.
Для представления UNICODE строк в Qt используется класс
QString
, который в общем представляет собой массив символов
типа QChar
. Полное и подробное описание конструкторов,
методов и операторов этого класса также можно посмотреть в документации по
библиотеке Qt. Преобразование по
умолчанию из одобайтовой строки (C тип char *
) в UNICODE
строку (Qt тип
QString
) также выполняется простым расширением значения. Т.е.
русская буква 'А' (код в ISO-8859-5 - 0xB0 ) получает UNICODE код U+00B0.
Для хранения однобайтовых строк, в библиотеке Qt, используется класс
QCString
, наследуемый от класса QByteArray
. В
этом классе строки представлены как массивы однобайтовых символов.
Для корректного преобразования из однобайтовых строк в
UNICODE-строки и обратно с учетом особенностей национальных кодировок, в
Qt используется класс
QTextCodec
. Полное и подробное описание конструкторов,
методов и операторов этого класса также можно посмотреть в документации по
библиотеке Qt. Мы только
остановимся на некоторых наиболее важных методах этого класса:
static QTextCodec* QTextCodec::codecForName(const char*
hint, int accuracy=0);
- Производит поиск среди всех объектов
QTextCodec
, и
возвращает указатель на объект QTextCodec
, имя которого
наиболее совпадает с именем, переданным через параметр hint.
Если кодек (Codec) не найден возвращает NULL
. Параметр
accuracy определяет точность совпадения имени кодека, значение
0 определяет точное совпадение.
static QTextCodec* QTextCodec::codecForLocale();
- Возвращает указатель объект который наиболее подходит для набора
символов, используемого в текущей установке локали. Про настройки локали
можно прочитать в статье "Локализация,
как она есть".
virtual QString QTextCodec::toUnicode(const char* chars,
int len) const;
- Преобразует len символов из chars в UNICODE.
virtual QCString QTextCodec::fromUnicode(const QString&
uc, int& lenInOut) const;
- Преобразует из UNUCODE в однобайтовую строку lenInOut
символов (типа
QChar
) начиная с первого символа строки
uc. Возвращает объект типа QCString
, и также
возвращает длинну результата в lenInOut.
Для
последних двух методов существуют перегруженные методы, которые позволяют
опускать второй параметр:
QCString
QTextCodec::fromUnicode(const QString& uc) const;
QString
QTextCodec::toUnicode(const char* chars) const;
В качестве примера использования класса
QTextCodec
для преобразования строк, можно привести следующий
пример (предполагается что используется локаль "ru" и она настроена):
1: char *str = "Привет мир!!!"
2: QTextCodec *c;
3: QString wstr;
4:
5: wstr = str;
6: qWarning("%s", (const char *)wstr.local8Bit() );
7: //^^ Будет напечатано : ?????? ???!!!
8: qWarning("%s", wstr.latin1() );
9: //^^ Будет напечатано : Привет мир!!!
10:
11: c = QTextCodec::codecForLocale();
12: // или c = QTextCodec::codecForName("ISO-8859-5");
13: if ( c )
14: {
15: wstr = c->toUnicode(str);
16: qWarning("%s", (const char *)c->fromUnicode(wstr) );
17: //^^ Будет напечатано : Привет мир!!!
18: // c->fromUnicode(wstr) эквивалентно wstr.local8Bit()
19: qWarning("%s", (const char *)wstr.latin1() );
20: //^^ Будет напечатана пустая строка
21: }
22: else
23: {
24: qWarning("Кодек не найден");
25: }
Разберем подробнее приведенный пример.
В строке 5 используется преобразование по умолчанию
(вызывается конструктор QString( const char * )
) из
одобайтовой строки (C тип char *
) в UNICODE строку
QString
- т.е. простое расширением значения. В этом случае
русская буква 'А' (код в ISO-8859-5 - 0xB0) получает UNICODE код U+00B0.
В строке 6 производится преобразование из строки
QString
в однобайтовую строку с использованикм метода
QCString QString::local8Bit()
. Этот метод выполняет
преобразование с учетом установок локали, и подробнее это метод будет
рассмотрен ниже. Т.к. для кириллицы в UNICODE не определены символы с
кодами от U+0080 до U+00FF, то в результате преобразования вместо русских
букв получаются символы '?'. То же самое происходит и с другими языками,
для которых UNICODE коды символов больше U+00FF.
В строке 8 производится преобразование из строки
QString
в однобайтовую строку с использованикм метода
const char *QString::latin1()
. Этот метод выполняет
преобразование простым сжатием двухбайтового значения до однобайтового.
При сжатии для значений, старший октет которых равен 0, берется значение
младшего октета, а для значений, старший октет которых отличен от нуля,
берется значение 0 (Октет - последовательность из 8 бит или байт. На
различных платформах размер байта может отличаться.). Подробнее это метод
будет рассмотрен ниже. Т.к. при отбросе старшего нулевого октета для
U+00B0 получается 0xB0 (код русской буквы 'А' в ISO-8859-5) то в строке 7
русские символы выводятся правильно.
В строке 11 мы находим кодек, который будет использоваться
по умолчанию для текущих установок локали. Если метод
QTextCodec::codecForLocale()
вернет ненулевое значение то
кодек найден и можно выполнять преобразования.
В строке 15 используется преобразование с помощью
найденного кодека. В этом случае русская буква 'А' (код в ISO-8859-5 -
0xB0) получает UNICODE код U+0410. Поэтому в строке 16 будут корректно
напечатаны русские буквы. А строке 19 при отбросе старшего не нулевого
октета для U+0410 (старший октет 0x04) получается значение 0x00 (признак
конца строки в языке C) и, соответственно, выводится пустая строка.
Для корректного преобразования из однобайтовых строк в
UNICODE-строки и обратно с учетом особенностей национальных кодировок
кроме класса QTextCodec
, в классе QString
помимо
конструкторов введены специалные методы, которые позволяют конструировать
объекты QString
с учетом особенностей определенных
однбайтовых кодировок. Это следующие методы:
const char* latin1() const;
- Этот метод возвращает Latin-1 представление строки. Этот метод
выполняет преобразование простым сжатием двухбайтового значения до
однобайтового. При сжатии для значений, старший октет которых равен 0,
берется значение младшего октета, а для значений, старший октет которых
отличен от нуля, берется значение 0. (Октет - последовательность из 8
бит или байт. На различных платформах размер байта может отличаться.).
Предполагается, что UNICODE строка
QString
представлена в
кодировке ISO-8859-1.
static QString fromLatin1(const char*, int len=-1);
- Этот метод использует для преобразования из однобайтовой строки (C
тип
char *
) в UNICODE строку QString
кодек для
кодировки ISO-8859-1. Т.о. преобразование выполняется простым
расширением значения. В этом случае русская буква 'А' (код в ISO-8859-5
- 0xB0) получает UNICODE код U+00B0. Если задан параметр len то
преобразуются только len символов исходной строки.
QCString utf8() const;
- Этот метод возвращает UTF-8 представление строки. Этот метод
выполняет преобразование с использованием кодека для кодировки UTF-8.
Кодировка UTF-8 позволяет закодировать UNICODE представление строк с
помощью однобайтовых строк.
static QString fromUtf8(const char*, int len=-1);
- Этот метод использует для преобразования из однобайтовой строки (C
тип
char *
) в UNICODE строку QString
кодек для
кодировки UTF-8. Если задан параметр len то преобразуются
только len символов исходной строки.
QCString local8Bit() const;
- Этот метод возвращает представление строки зависящее от установок
текущей локали. Он выполняет преобразование с использованием кодека для
для текущих установок локали. Т.о. преобразование выполняется с учетом
особенностей кодировки, определяемой установками текущей локали.
Например если в текущей локали используется кодировка ISO-8859-5, то
будет использован кодек для этой кодировки. В этом случае русская буква
'А' (код в UNICODE - U+0410) получает код 0xB0. Если в текущей локали
UNICODE код символа не определен, то однобайтовое значение для такого
символа будет - '?'. Предполагается, что UNICODE строка
QString
представлена в "чистом" UNICODE. Если для текущей
локали не найден подходящий кодек то, используется кодек для кодировки
ISO-8859-1.
static QString fromLocal8Bit(const char*, int len=-1);
- Этот метод использует для преобразования из однобайтовой строки (C
тип
char *
) в UNICODE строку QString
кодек для
текущих установок локали. Т.о. преобразование выполняется с учетом
особенностей кодировки, определяемой установками текущей локали.
Например, если в текущей локали используется кодировка ISO-8859-5, то
будет использован кодек для этой кодировки. В этом случае русская буква
'А' (код в ISO-8859-5 - 0xB0) получает UNICODE код U+0410. Если задан
параметр len, то преобразуются только len символов
исходной строки. Если для текущей локали не найден подходящий кодек, то
используется кодек для кодировки ISO-8859-1.
C использованием методов класса QString
приведенный выше пример можно изменить следующим образом:
1: char *str = "Привет мир!!!"
2: QTextCodec *c;
3: QString wstr;
4:
5: wstr = str;
6: qWarning("%s", (const char *)wstr.local8Bit() );
7: //^^ Будет напечатано : ?????? ???!!!
8: qWarning("%s", wstr.latin1() );
9: //^^ Будет напечатано : Привет мир!!!
10:
11: wstr = QString::fromLocal8Bit(str);
12: qWarning("%s", (const char *)c->fromUnicode(wstr) );
13: //^^ Будет напечатано : Привет мир!!!
14: // c->fromUnicode(wstr) еквивалентно wstr.local8Bit()
15: qWarning("%s", (const char *)wstr.latin1() );
16: //^^ Будет напечатана пустая строка
Разработчики Qt
реомендуют использовать перечисленные выше методы для конструирования
UNICODE строк QString
и пребразования из QString
в однобайтовые строки, вместо преобразований по умолчанию.
Явный вызов вышеперечисленных методов, для конструирования
строк типа QString
, позволит быть уверенным в том, что в
программе используется "чистый" UNICODE.
Причины возникновения ошибок при обработке национальных
символов. Рекомендации по их устранению.
Т.к. большинство программ написаны англоязычными
программистами, которые используют в качестве основной кодировку
ISO-8859-1, в таких программах мало внимания уделяется преобразованиям
символов. Как уже говорилось выше, для кодировок ISO-8859-1 (LATIN1) и
US-ASCII, преобразования из однобайтовой строки в UNICODE и обратно не
имеют особого значения, т.к. коды символов (значения кодов) в этих
кодировках совпадают. Например, латинская буква 'A' имеет код в кодировке
ISO-8859-1 (US-ASCII) 0x65 и в UNICODE код латинской буквы 'A' будет
равным U+0065. Т.е. <Код символа ISO-8859-1>==<Код символа
UNICODE>. Соответственно, для кодировок ISO-8859-1 (LATIN1) и
US-ASCII, преобразование простым расширением/сжатием значения выполняется
корректно и программа работает с "чистым" UNICODE.
Более того, англоязычные программисты не могут заметить и,
соответственно, исправить такие ошибки, т.к. в большинстве случаев для
отладки программ они используют тексты, содержащие латинские символы, и
локаль с кодировкой ISO-8859-1. Нереально представить себе ситуацию, когда
каждый программист, например, из Великобритании для отладки программы
устанавливает у себя русские шрифты и настраивает русскую локаль, а ведь
кроме русского языка существует еще множество других языков.
Поэтому, чтобы избавить себя и пользователей программы от
проблем с локализацией, нужно более четко определить ситуации когда
возникают эти ошибки и сформулировать некоторые принципы написания кода
программы. Все рекомендации, которые будут сформулированны ниже, в общем
виде, применимы не только к программам использующим библиотеку Qt, но и ко всем другим программам
которые хранят и обрабатывают строки в формате UNICODE или WideChar.
Ситуация первая.
Чаще всего ошибки с преобразованием национальных символов
в библиотеке Qt возникают из-за
использования неявного преобразования типов к типу QString
.
Неприятность этих ошибок заключается в том, что на этапе компиляции не
выдается никаких сообщений и предупреждений, т.к. с точки зрения
синтаксиса и правил языка программирования ошибки действительно нет.
Подобная ошибка проявляется только на этапе выполнения программы, причем к
исключительной ситуации не приводит.
В каких местах программы возникают такие ошибки ?
Наиболее частое место возникновения этой ошибки -
преобразование типа при передаче параметров в процедуру или функцию.
Например, есть некоторая функция, которая в качестве параметра требует
строку типа QString
, а при вызове этой функции ей в качестве
фактического параметра передается обчная C-like строка:
1: #include <qstring.h>
2:
3: void foo( const QString & str )
4: {
5: qWarning("%s",(const char *)str.local8Bit());
6: }
7:
8: int main( int argc, char *argv[] )
9: {
10: foo( "Привет мир!!!" );
11: //^^ Будет напечатана строка : ?????? ???!!!
12: }
В этом примере, в строке 10, выполняется неявное
преобразование типов из const char *
в QString
с
использованием конструктора QString::QString( const char * )
.
В результате такого преобразования, как говорилось выше, происходит
простое расширение однобайтового значения до двухбайтового. Т.е. строка
"Привет мир!!!" в формате UNICODE будет иметь следующее значение:
"U+00BF,U+00E0,U+00D8,U+00D2,U+00D5,U+00E2,U+0020,U+00DC,
U+00D8,U+00E0,U+0021,U+0021,U+0021"
В "чистом" UNICODE эта строка должна иметь
значение:
"U+041F,U+0440,U+0438,U+0432,U+0435,U+0442,U+0020,
U+043C,U+0438,U+0440,U+0021,U+0021,U+0021"
В качестве другого примера можно привести функцию, которая
в качестве параметра требует C-like строку, а при вызове этой функции ей в
качестве фактического параметра передается строка типа
QString
:
1: #include <qstring.h>
2:
3: void foo( const char * str )
4: {
5: qWarning("%s",str);
6: }
7:
8: int main( int argc, char *argv[] )
9: {
10: QString qstr = QString::fromLocal8Bit( "Привет мир!!!" );
11: //^^ Инициализируем qstr в "чистый" UNICODE
12:
13: foo( qstr );
14: //^^ Будет напечатана пустая строка
15: }
В этом примере в строке 13 выполняется неявное
преобразование типов из QString
в const char *
с
использованием оператора преобразования типа QString::operator const
char *() const;
, который определен в файле qstring.h
как:
1: class Q_EXPORT QString
2: {
3: ...
4: operator const char *() const { return latin1(); }
5: ...
6: };
Как видно из определения оператора
QString::operator const char *() const
, для преобразования
типов будет вызван метод const char* QString::latin1() const
.
Этот метод для национальных символов с кодами большими U+00FF возвращает
нулевое значение, т.о. функция void foo( const char * str )
,
из примера выше, в качестве параметра получит C-like строку, первый символ
которой равен '\0', что, в свою очередь, является признаком конца строки
для C-like строк. Поэтому в результате выполнения строки 5 будет
напечатана пустая строка.
Из-за того, что в этих примерах в той или иной степени
используется "не чистый" UNICODE, и возникают проблемы с обработкой
национальных символов. Впрочем, если мы установим локаль в "en_US" (export
LC_ALL=en_US), то приведенные выше примеры будут корректно выводить
русские символы.
Этот тип ошибок очень часто встречается когда, например,
из программы использующей библиотеку Qt, обращаются к системным вызовам
ядра (например fopen(..), open(..), mkdir(..), access(..),
unlink(..)
и т.д.), которые требуют в качестве своих параметров
C-like строку, а при вызове этой функции ей в качестве фактических
параметров передаеются строки типа QString
. В случае вызова
функций fopen(..), open(..), mkdir(..), access(..),
unlink(..)
, из-за этой ошибки невозможно работать с файлами и
директориями, содержащими национальные символы в именах.
Что нужно делать, чтобы исключить ошибки связанные с
неявным преобразованием типов ?
Совет первый
Разработчики Qt
рекомендуют для гарантии того, что в программе используется "чистый"
UNICODE, указывать при компиляции программ ключи
-DQT_NO_CAST_ASCII
и
-DQT_NO_ASCII_CAST
. Применение этих ключей запретит
преобразования по умолчанию из const char *
в
QString
и обратно. Вообще с использованием ключа
-DQT_NO_CAST_ASCII
будет запрещено любое неявное
приведени типов из const char *
в QString
и для
всех остальных методов класса QString
.
После того как будет запрещено неявное приведение типов,
при компиляции рассмотренных выше примеров, будет выдана ошибка о
невозможности преобразования типов. И необходимо будет явно использовать
один из методов класса QString
:
fromLatin1/fromLocal8Bit/fromUtf8
в зависимости от ситуации.
Например вместо фрагмента программы:
1: QString qstr1("Привет мир!!!"); // Ошибка !!!
2: QString qstr2 = "Hello world!!!"; // Ошибка !!!
3: char *buff;
4: ...
5: qstr2 = qstr1 + " " + qstr2; // Ошибка !!!
6: qstr1 = buff; // Ошибка !!!
7: ...
для того, чтобы получить гарантию того, что в
программе используется чистый UNICODE, нужно использовать следующий
фрагмент кода:
1: QString qstr1 = QString::fromLocal8Bit("Привет мир!!!");
2: QString qstr2 = QString::fromLatin1("Hello world!!!");
3: char *buff;
4: ...
5: qstr2 = qstr1 + QString::fromLatin1(" ") + qstr2;
6: qstr1 = QString::fromLocal8Bit(buff);
7: ...
Небольшое замечание: Применение метода
QString::fromLatin1(...)
для преобразования однобайтовой
строки в UNICODE-строку, оправдано исключительно в случае, если
однобайтовая строка содержит только символы в кодировке ISO-8859-1.
В примерах строки 2 и 5, действительно строки "Hello world!!!" и " "
содержат только ISO-8859-1 символы. В строке 1, строка "Привет мир!!!"
содержит только русские символы, поэтому мы используем метод
QString::fromLocal8Bit(...)
. В строке 6 переменная
buff может содержать не только латинские символы, поэтому
наиболее безопасное решение - применять метод
QString::fromLocal8Bit(...)
. Это даст гарантию того, что все
символы из строки buff будут корректно преобразованы в UNICODE.
В некоторых случаях бывает невозможно использовать ключи
-DQT_NO_CAST_ASCII
и
-DQT_NO_ASCII_CAST
при компиляции программы. Например,
если вы используете другую билиотеку, базирующуюся на Qt, которая разрабатывалась без
использования ключей -DQT_NO_CAST_ASCII
и
-DQT_NO_ASCII_CAST
при компиляции. В этом случае, вам
нужно придерживаться советов, рассмотренных ниже, они помогут избежать
ошибок. Но если вы начинаете разрабатывать свою собственную программу с
использованием библиотеки Qt, то
обязательно используйте ключи -DQT_NO_CAST_ASCII
и
-DQT_NO_ASCII_CAST
при компиляции программы.
Совет второй
При проектировании программы нужно обязательно определить
в каком формате будут храниться и обрабатываться строки: в однобайтовом
или UNICODE формате. И в дальнешем, при программировании, стараться внутри
функций и классов не смешивать однобайтовые и UNICODE строки. Т.е. если
некоторый класс содержит несколько членов данных с текстовой информацией,
нужно стараться чтобы все эти члены данные были одного типа, а для доступа
к этим членам данным использовать перегружаемые методы, которые, в свою
очередь, гарантируют корректность преобразования, даже если при компиляции
не заданы ключи -DQT_NO_CAST_ASCII
и
-DQT_NO_ASCII_CAST
. Например:
1: class Foo
2: {
3: public:
4: Foo( const Foo & foo )
5: {
6: m_d1 = foo.m_d1;
7: m_d2 = foo.m_d2;
8: };
9: Foo( const QString & d1, const QString & d2 = QString::null )
10: {
11: m_d1 = d1;
12: m_d2 = d2;
13: };
14: Foo( const char * d1, const char * d2 = NULL )
15: {
16: m_d1 = QString::fromLocal8Bit(d1);
17: m_d2 = d2 ? QString::fromLocal8Bit(d2) : QString::null;
18: };
19: vod setD1( const QString & d1 )
20: {
21: m_d1 = d1;
22: };
23: void setD1( const char * d1 )
24: {
25: m_d1 = QString::fromLocal8Bit(d1);
26: };
27: vod setD2( const QString & d2 )
28: {
29: m_d2 = d2;
30: };
31: void setD2( const char * d2 )
32: {
33: m_d2 = QString::fromLocal8Bit(d2);
34: };
35: QString do()
36: {
37: return m_d1 + QString::fromLatin1(" ") + m_d2;
38: };
39: private:
40: QString m_d1;
41: QString m_d2;
42: };
Наличие перегруженных конструкторов и методов
setD1/setD2
в этом классе гарантирует, что внутри этого
класса всегда будет использоваться "чистый" UNICODE, независимо от того,
каким образом мы инициализируем объекты класса Foo
:
1: Foo f1( "Привет","мир!!!" );
2: QString s1 = QString::fromLocal8Bit("Привет");
3: Foo f2;
4:
5: f2.setD1( s1 );
6: f2.setD2( "мир!!!" );
7: qWarning("%s", (const char *)f1.do().local8Bit());
8://^^ Напечатает строку : Привет мир!!!
9: qWarning("%s", (const char *)f2.do().local8Bit());
10://^^ Напечатает строку : Привет мир!!!
Еще более безопасное решение - добавление в класс
Foo
двух конструкторов:
1: ...
2: Foo( const QString & d1, const char * d2 )
3: {
4: m_d1 = d1;
5: m_d2 = QString::fromLocal8Bit(d2);
6: };
7: Foo( const char * d1, const QString & d2 )
8: {
9: m_d1 = QString::fromLocal8Bit(d1);
10: m_d2 = d2;
11: };
12: ...
13: QString s1 = QString::fromLocal8Bit("Привет");
14: Foo f(s1,"мир!!!");
15: ...
Если мы не будем перегружать конструкторы и методы
setD1/setD2
, то мы получим все те ошибки, о которых
говорилось выше:
1: class Foo
2: {
3: public:
4: Foo( const Foo & foo )
5: {
6: m_d1 = foo.m_d1;
7: m_d2 = foo.m_d2;
8: };
9: Foo( const QString & d1, const QString & d2 = QString::null )
10: {
11: m_d1 = d1;
12: m_d2 = d2;
13: };
14: vod setD1( const QString & d1 )
15: {
16: m_d1 = d1;
17: };
18: vod setD2( const QString & d2 )
19: {
20: m_d2 = d2;
21: };
22: QString do()
23: {
24: return m_d1 + QString::fromLatin1(" ") + m_d2;
25: };
26: private:
27: QString m_d1;
28: QString m_d2;
29: };
30:
31: Foo f1( "Привет","мир!!!" ); // Неявное преобразование
32: QString s1 = QString::fromLocal8Bit("Привет");
33: Foo f2;
34:
35: f2.setD1( s1 );
36: f2.setD2( "мир!!!" ); // Неявное преобразование
37: qWarning("%s", (const char *)f1.do().local8Bit());
38://^^ Напечатает строку : ?????? ???!!!
39: qWarning("%s", (const char *)f2.do().local8Bit());
40://^^ Напечатает строку : Привет ???!!!
В реальных программах кроме отдельных строк очень часто
приходится использовать еще и списки строк. В библиотеке Qt для представления списков строк
исоользуются два класса QStringList
и QStrList
.
Первый представляет строки типа QString
, второй строки типа
QCString
. В этой ситуации самой распространенной ошибкой,
связанной с преобразованием строк, является попытка добавить или
преобразовать строку типа QString
к списку типа
QStrList
, и наоборот извлечь строку из списка
QStrList
в переменную типа QString
. Это
справедливо относительно строк типа QCString
и списков
QStringList
. Поэтому использованию списков строк тоже нужно
уделять особое внимание.
Конечно, как говорилось выше, использование ключей
-DQT_NO_CAST_ASCII
и
-DQT_NO_ASCII_CAST
при компиляции, защитит от таких
ошибок, но к сожалению не всегда возможно применение этих ключей.
В заключении этой темы, нужно отметить: Т.к. библиотека Qt внутри ориентированна на
использование UNICODE строк, то использование класса QString
вместо char *
и QCString
для передставления
строк внутри программы, а также использование класса
QStringList
вместо класса QStrList
для
представления списков строк будет самым безопасным решением с точки зрения
появления ошибок.
Совет третий
Если в своей программе вы используете системные вызовы для
доступа к файлам или директориям (например fopen(..), open(..),
mkdir(..), access(..), unlink(..)
и т.д.) более безопасным решением
для преобразования имени файла/директории из QString
в
const char *
использовать методы класса QFile
:
static QCString QFile::encodeName( const QString &
fileName );
- Преобразует имя файла, представленное в формате UNICODE типом
QString
, в однобайтовое представление типа
QCString
с использованием специфики файловой системы. По
умолчанию используется преобразование QCString
QString::local8Bit() const
, но может быть переопределено с
помощью метода static void QFile::setEncodingFunction( EncoderFn
)
.
static QString decodeName( const QCString &
localFileName );
- Преобразует имя файла, представленное в однобайтовом формате типа
QCString
, в представление UNICODE типа QString
с использованием специфики файловой системы. По умолчанию используется
преобразование QString QString::fromLocal8Bit(const char*,
int)
, но может быть переопределено с помощью метода static
void QFile::setDecodingFunction( DecoderFn )
.
Например:
1: int renameFile( const QString & old, const QString & new )
2: {
3: ::rename( QFile::encodeName(old), QFile::encodename(new) );
4: }
5: ...
6: QStringList readDir( const QString & dName )
7: {
8: DIR *dp = 0L;
9: struct dirent *ep;
10: QStringList dList;
11:
12: dp = opendir( QFile::encodeName(dName) );
13: if ( dp )
14: {
15: while ( ep=readdir( dp ) )
16: {
17: dList.append(QFile::decodeName(ep->d_name));
18: }
19: closedir( dp );
20: }
21: return dList;
22: }
23: ...
Совет последний
Если нужно проверить является строка, представленная типом
QString
или QCString
, пустой или нулевой, нужно
использовать методы:
bool QString::isEmpty() const
,
bool
QCString::isEmpty() const
- Проверка на пустую строку. Под строку отведена память, но данных
нет. Если для стоки выполняется условие
str.isEmpty()==TRUE
, то это не означает, что будет
выполняться и условие str.isNull()==TRUE
.
bool QString::isNull() const
,
bool
QCString::isNull() const
- Проверка на нулевую строку. Под строку не отведена память. Если для
строки выполняется условие
str.isNull()==TRUE
, то
обязательно будет выполняться условие str.isEmpty()==TRUE
.
Ситуация вторая.
В программах написанных с ипользованием языка C++ очень
часто используют потоки (streams) для ввода/вывода текстовых данных из/в
строки, из/в файла или какого-либо другого устройства ввода/вывода.
Действительно, с помощью потока доступ к буферу (строке), файлу или
другому устройству организуется одинаково.
В обычных тестовых файлах подавляющего большинства
операционных систем, информация представлена в виде однобайтовых строк в
кодировке, определяемой установками локали. В некоторых случаях в тестовых
файлах информация может быть представлена представлена в виде однобайтовых
строк в кодировке UTF8, реже в виде двухбайтовых строк в кодировке
UNICODE.
В библиотеке Qt
для этих целей используется класс QTextStream
. Но т.к.
библиотека Qt внутри работает со
строками в формате UNICODE, то этот класс имеет свои особенности. Эти
особенности мы рассмотрим ниже.
В качестве примера использования класса
QTextStream
можно привести следующий фрагмент кода:
1: ...
2: QFile file(QFile::decodeName("TheFile"));
3: QString s;
4:
5: if (file.exists())
6: {
7: if (file.open(IO_ReadOnly))
8: {
9: QTextStream t(&file);
10:
11: while ((s=t.readLine()) != QString::null)
12: {
13: qWarning((const char *)s.local8Bit());
14: }
15: file.close();
16: }
17: }
18: ...
Если файл TheFile содержит текст,
представленный ниже, т.е. текстовые однобайтовые строки в кодировке,
определяемой установками локали:
1: Строка 1
2: Строка 2
3: Строка 3
то соответственно в результате выполнения этого
фрагмента кода содержимое файла будет напечатано без искажений.
Для демонстрации момента, в котором возникает искажение
национальных символов, приведем другой пример (в этом примере я намеренно
использую класс QStrList
):
1: ...
2: QFile file(QFile::decodeName("TheFile"));
3: QString s;
4: QStrList strList;
5:
6: strList.append("Строка 1");
7: strList.append("Строка 2");
8: strList.append("Строка 3");
9:
10: if (file.open(IO_WriteOnly))
11: {
12: QTextStream t( & file);
13: QStrListIterator it(strList);
14:
15: const char * tmp;
16: while ( (tmp=it.current()) )
17: {
18: ++it;
19: t << tmp << "\n";
20: }
21: file.close();
22: }
23: ...
В этом примере в поток вставляются однобайтовые
строки. В результате выполнения этого фрагмента кода, файл
TheFile, будет содержать строки:
1: ?????? 1
2: ?????? 2
3: ?????? 3
Если вместо класса QStrList
использовать класс QStringList
или вместо файла связать поток
с классом QCString
(QByteArray
), то строки будут
выводиться правильно. Почему так происходит ?
Разработчики библиотеки Qt реализовали класс
QTextStream
следующим образом: предполагается, что в файле,
связанном с потоком, текст хранится в виде однобайтовых строк в кодировке,
определенной для текущей локали. Поэтому при создании потока, связанного с
файлом, устанавливается флаг Encoding == QTextStream::Locale с
помощью метода QTextStream::setEncoding( QTextStream::Encoding
e)
c параметром e = QTextStream::Locale.
При выводе однобайтового символа в поток применяется
следующее преобразование: QChar <= unsigned short <= int
<= char
(Смотри реализацию методов
QTextStream::ts_putc(...)
и QTextStream::writeBlock(
const char*, uint)
). В результате такого преобразования русская
буква 'А' (код в ISO-8859-5 - 0xB0) получает UNICODE код U+00B0.
В методе QTextStream::ts_putc(QChar)
при
непосредственной записи в файл происходит преобразование к однобатовому
символу с помощью метода QTextCodec::fromUnicode(...)
. Если
мы вставляем в поток, связанный с файлом, строки типа
QString
, то преобразование выполняется правильно и русские
буквы выводятся без искажений. Если мы вставляем в поток однобайтовые
строки, то преобразование выполняется некорректно (с точки зрения
использования UNICODE) и русские символы искажаются.
В свою очередь, для текстового потока
QTextStream
, связанного с однобайтовой строкой
(QByteArray
или QCString), устанавливается флаг
Encoding == QTextStream::Latin1, который выполняет преобразование
из UNICODE в однобайтовую строку по следующему алгоритму:
1: QChar c;
2:
3: if( c.row() ) // Если старший октет отличен от нуля
4: {
5: dev->putch( '?' ); // Выводим символ '?'
6: }
7: else
8: {
10: dev->putch( c.cell() ); // Младший октет
11: }
Если мы вставляем в поток, связанный с однобайтовой
строкой, строку типа QString
то выполняется метод
QTextStream::operator<<( const QString & s )
,
который для Encoding == QTextStream::Latin1 производит преобразование
'Русская буква' ==> '?'. Для символов в кодировках ISO-8859-1 или
US-ASCII преобразования не имеют эффекта, коды этих символов не меняются:
в UNICODE старший байт равен 0, младший байт равен коду символа. Для
национальных символов в UNICODE старший байт всегда отличен от 0 и в общем
случае младший байт не совпадает с однобайтовым кодом символа для
кодировки определенной в локали, и такие строки подвергаются изменениям.
Если тестировать программы, используя только строки в кодировке ISO-8859-1
или US-ASCII, то ошибки преобразования "UNICODE <==> однобайтовая
строка" не видны, т.к. фактически никаках преобразоаний не происходит.
Что нужно делать, чтобы при использовании QTextStream не
искажались национальные символы ?
Для того чтобы избежать искажения национальных символов
при использовании класса QTextStream
, нужно придерживаться
простого правила: после создания потока нужно явно установить для него тип
кодирования текста с помощь метода QTextStream::setEncoding(
QTextStream::Encoding e)
.
Наш пример будет выглядеть следубщим образом:
1: ...
2: QFile file(QFile::decodeName("TheFile"));
3: QString s;
4: QStrList strList;
5:
6: strList.append("Строка 1");
7: strList.append("Строка 2");
8: strList.append("Строка 3");
9:
10: if (file.open(IO_WriteOnly))
11: {
12: QTextStream t( & file);
13: t.setEncoding( QTextStream::Latin1 );
14: //^^^Сообщаем потоку, что однобайтовые строки преобразовывать
15: // не нужно
16: QStrListIterator it(strList);
17:
18: const char * tmp;
19: while ( (tmp=it.current()) )
20: {
21: ++it;
22: t << tmp << "\n";
23: }
24: file.close();
25: }
26: ...
В случае если мы используем поток, связанный с
однобайтовой строкой, и в этот поток вставляем строки типа
QString
, то нужно указать Encoding ==
QTextStream::Locale:
1: ...
2: QCString buff;
3: QString s;
4: QStringList strList;
5:
6: strList.append(QString::fromLocal8Bit("Строка 1"));
7: strList.append(QString::fromLocal8Bit("Строка 2"));
8: strList.append(QString::fromLocal8Bit("Строка 3"));
9:
10: QTextStream t( & buff );
11: t.setEncoding( QTextStream::Locale );
12: //^^^Сообщаем потоку, что UNICODE строки нужно преобразовывать
13: // в соответствии с установками локали
14: for ( QStringList::Iterator it = strList.begin(); it != strList.end(); ++it )
15: {
16: t << *it << QString::fromLatin1("+");
17: }
18: ...
В результате выполнения этого фрагмента кода
переменная buff получит значение "Строка 1+Строка 2+Строка 3".
Если мы уберем из примера строку 11, то переменная buff получит
значение "?????? 1+?????? 2+?????? 3".
Все выше сказанное можно сформулировать в виде следующих
правил:
-
Если в поток типа QTextStream
с помощью
оператора вставки в поток
(QTextStream::operator<<(...)
) будут вставляться
однобайтовые строки в кодировке, определяемой установками локали или
UTF8, то перед вызовом оператора вставки в поток необходимо установить
тип кодирования текстовой информации в значение
QTextStream::Latin1 с помощью вызова метода
QTextStream::setEncoding( QTextStream::Encoding e)
.
-
Если в поток типа QTextStream
с помощью
оператора вставки в поток
(QTextStream::operator<<(...)
) будут вставляться
строки типа QString
, то перед вызовом оператора вставки в
поток необходимо явно установить тип кодирования текстовой информации c
помощью вызова метода QTextStream::setEncoding(
QTextStream::Encoding e)
.
Тип кодирования текстовой информации долженен принимать
следующие значения:
- QTextStream::Locale
- Если необходимо чтобы при записи UNICODE строк в обычный текстовый
файл выполнялось преобразование этих строк в однобайтовые строки с
учетом кодировки, определяемой установками локали.
- QTextStream::UnicodeUTF8
- Если необходимо чтобы при записи UNICODE строк в обычный текстовый
файл выполнялось преобразование этих строк в однобайтовые строки в
кодировке UTF8.
- QTextStream::Unicode,
QTextStream::UnicodeNetworkOrder,
QTextStream::UnicodeReverse,
QTextStream::RawUnicode
- Если необходимо чтобы при записи UNICODE строк в файл не
выполнялось преобразование этих строк в однобайтовые строки. Подробнее
об особенностях этих значений можно прочитать в справочной системе
библиотеки Qt.
-
Если поток используется с одним типом строк, то тип
кодирования текстовой информации можно задавать только один раз - после
создания потока. Если поток используется с разными типами строк, то тип
кодирования текстовой информации можно задавать каждый раз перед
измением типа строк. Например:
1: ...
2: QCString mbs = "Строка 1", buff;
3: QString wcs = QString::fromLocal8Bit("Строка 2");
4:
5: QTextStream t( & buff );
6:
7: t.setEncoding( QTextStream::Latin1 );
8: //^^^Сообщаем потоку, что однобайтовые строки преобразовывать
9: // не нужно
10: t << mbs;
11:
12: t.setEncoding( QTextStream::Locale );
13: //^^^Сообщаем потоку, что UNICODE строки нужно преобразовывать
14: // в соответствии с установками локали
15: t << wcs;
16:
17: ...
-
Все вышесказанное справедливо и для оператора извлечения
из потока QTextStream::operator>>(...)
.
Патчи, исправляющие описанные проблемы в KDE 2.1.1, вы
можете скачать здесь.