среда, 8 декабря 2010 г.

Почему я не люблю API функцию IsTextUnicode

Это перевод Why I don't like the IsTextUnicode API. Автор: Майкл Каплан.

API функция IsTextUnicode существовала со времён NT 3.5, если верить истории в Platform SDK. Согласно PSDK, её цель такова:
Функция IsTextUnicode определяет, возможно ли, что буфер содержит какую-то форму Unicode текста. Функция использует различные статистические и детерминированные методы для вынесения своего решения, что также контролируется указываемыми флагами. Когда функция возвращает управление, результаты тестов сообщаются через параметр lpi.
Затем документация описывает различные тесты, которые может делать функция, когда вы указываете соответствующий флаг:
IS_TEXT_UNICODE_ASCII16
Текст Unicode и содержит только ASCII символы (расширенные нулём).
IS_TEXT_UNICODE_REVERSE_ASCII16
То же, что и предыдущий, но только с обратным порядком байт.
IS_TEXT_UNICODE_STATISTICS
Текст, вероятно, является Unicode, что определено статистическим анализом. Абсолютная уверенность не гарантируется.
IS_TEXT_UNICODE_REVERSE_STATISTICS
То же, что и предыдущий, но только с обратным порядком байт.
IS_TEXT_UNICODE_CONTROLS
Текст содержит Unicode представления одного или более из следующих непечатных символов: RETURN, LINEFEED, SPACE, CJK_SPACE, TAB.
IS_TEXT_UNICODE_REVERSE_CONTROLS
То же, что и предыдущий, но только с обратным порядком байт.
IS_TEXT_UNICODE_BUFFER_TOO_SMALL
В тексте слишком мало символов для анализа (менее двух байт).
IS_TEXT_UNICODE_SIGNATURE
Текст содержит отметку byte-order (BOM) Unicode ($FEFF).
IS_TEXT_UNICODE_REVERSE_SIGNATURE
Текст содержит отметку byte-order (BOM) Unicode с обратным порядком байт ($FFFE).
IS_TEXT_UNICODE_ILLEGAL_CHARS
Текст содержит один из следующих недопустимых в Unicode символов: встроенный Reverse BOM, UNICODE_NUL, CRLF (упакованный в один WORD) или $FFFF.
IS_TEXT_UNICODE_ODD_LENGTH
Число символом в строке нечётно. Строка с нечётной байтовой длиной не может быть Unicode строкой по определению.
IS_TEXT_UNICODE_NULL_BYTES
Текст содержит нулевые байты, что указывает на не-ASCII текст.
IS_TEXT_UNICODE_UNICODE_MASK
Этот флаг является комбинацией IS_TEXT_UNICODE_ASCII16, IS_TEXT_UNICODE_STATISTICS, IS_TEXT_UNICODE_CONTROLS и IS_TEXT_UNICODE_SIGNATURE.
IS_TEXT_UNICODE_REVERSE_MASK
Этот флаг является комбинацией IS_TEXT_UNICODE_REVERSE_ASCII16, IS_TEXT_UNICODE_REVERSE_STATISTICS, IS_TEXT_UNICODE_REVERSE_CONTROLS и IS_TEXT_UNICODE_REVERSE_SIGNATURE.
IS_TEXT_UNICODE_NOT_UNICODE_MASK
Этот флаг является комбинацией IS_TEXT_UNICODE_ILLEGAL_CHARS, IS_TEXT_UNICODE_ODD_LENGTH и ещё двух не используемых сегодня битовых полей.
IS_TEXT_UNICODE_NOT_ASCII_MASK
Этот флаг является комбинацией IS_TEXT_UNICODE_NULL_BYTES и трёх не используемых сегодня битовых полей.
Вроде звучит впечатляюще и интересно, не так ли?

Тривиальный факт: код для флага, который когда-то был документирован (IS_TEXT_UNICODE_DBCS_LEADBYTE), всё ещё сидит в функции (и флаг всё ещё объявлен в заголовочнике - PSDK никогда так грубо не ломает код других людей). Но этот флаг работает откровенно ужасно - видимо, поэтому он и больше не документируется. Я настоятельно рекомендую его не использовать. И игнорировать его, когда вам его возвращают. Флаг не опасен - ничего такого, просто он не является ужасно полезным для своей цели (определения, что текст является DBCS).

Как я упоминал, эта функция была с нами со времён NT 3.5. Она была написана кем-то вне команды NLS (как она была в те дни). Это довольно впечатляюще, поскольку в те дни не было такой осведомлённости о Unicode, как сегодня...

В те горячие дни, когда для большинства разработчиков Unicode был немногим большим, чем иностранное слово, воспринимаемое как "удвоение памяти и пространства, необходимых для строк", эта функция в основном использовалась как способ узнать, когда вызывать WideCharToMultiByte, когда преобразовывать строки из Unicode1, и даже для такой цели было не так уж много вызывающих. В NT 4.0 эта функция не получила значительно большего использования, хотя Windows 2000 число вызывающих её во всём дереве исходных кодов Windows увеличилось примерно в три раза (до 65-ти или около того). Не так много изменений было с вызывающей стороны и в XP или Server 2003. Я не особо против этого факта.

В какое-то время между XP и Server 2003, я добавил её в MSLU, как хороший жест для разработчиков, которые были разочарованы тем, что она имеется только в NT API2.

Тем не менее, как указывает название этого поста, я не люблю IsTextUnicode.

Вы можете подумать, почему бы это - давайте, я даю вам три попытки.

Попытка №1: потому что я ею не владею?

Извините, это не так - но ваше мнение о моём эго взято на заметку :-)

Я дам вам подсказку.

Подсказка №1: посмотрите на описание в Platform SDK (я добавлю выделение, чтобы усилить подсказку):
Функция IsTextUnicode определяет, возможно ли, что буфер содержит какую-то форму Unicode текста. Функция использует различные статистические и детерминированные методы для вынесения своего решения, что также контролируется указываемыми флагами. Когда функция возвращает управаление, результаты тестов сообщаются через параметр lpi.

Попытка №2: Извини, я имел ввиду, потому что функция не принадлежит команде NLS?

Хмм, извините. Я понял, что вы имели ввиду ещё в первой попытке.

Я дам ещё одну подсказку.

Подсказка №2: У этой функции за всё время было только одно значительное изменение - к параметру lpBuffer был добавлен модификатор const.

Теперь дошло? Подумайте внимательно, это ваша последняя попытка.

Попытка №3: Потому что она рассматривает "CRLF (упакованные в одно WORD)" как недопустимый символ, даже хотя U+0D0A - это MALAYALAM LETTER UU?

Ооо, отлично - это выглядит как баг флага IS_TEXT_UNICODE_ILLEGAL_CHARS. Что ещё круче - вы даже разобрались с порядком байт. Или, может быть, вы просто не заметили этого, поскольку и ASCII CRLF, упакованные в одно WORD, и этот символ будут перевёрнуты на little-endian системах, так что они будут выглядеть как $0A0D в памяти, а если вы не разрешаете проверки на обратный порядок байт, то вы всё равно оказываетесь правы.

С учётом того, что поддержка Malayalam, описанная в предыдущем посте, это довольно неудобная ситуация. Или, может быть, с учётом факта, что эта кодовая точка была добавлена в Unicode 1.1 (согласно DerivedAge.txt), который был выпущен в Июне 1993-го (согласно enumeratedversions.html), это будет в особенности стыдно. Хотя это делает этот комментарий особенно изумительным:
            //  Следующее не является пока Unicode символом,
            //  но он ожидается при чтении в ASCII файлах, 
            // которые используют CRLF на little endian машине.
Если вы подумаете об этом, то большинство UTF-16 big endian файлов должно придти от какой-то другой системы - которая, вероятно, использует просто CR или LF в качестве разделителя строк, даже если это просто ASCII. Я думаю, теперь понятно, почему нет аналогичной проверки для этой "недопустимой" комбинации для обратного порядка байт (big-endian)? :-) Что делает всю проверку IS_TEXT_UNICODE_ILLEGAL_CHARS весьма странной, если не полностью бесполезной.

Для фанатов MSLU: да, я портировал этот баг, хотя и не целенаправленно. Извините, я не привык читать кодовые точки как реверсированные байты...

Конечно же, поскольку я не знал об этой проблеме раньше, она не может быть причиной, почему я не люблю эту функцию. Чёрт, да если бы не этот придуманный разговор, я бы так и не узнал про неё. К счастью для всех, я проявил эту психологическую дисфункцию на глазах у всех и поэтому не могу ещё больше быть смущённым, сообщая по ней баг, верно?

Так как вы нашли баг десятилетней давности: ладно, это все еще попытка №3 :-)

Ещё одна подсказка:

Подсказка №3: В нижележащих мезанизмах этой функции не было изменений как минимум со времён NT 3.51 (а, вероятно, и со времён изначального релиза в NT 3.5).

Ещё варианты будут?

Попытка №4: Потому что, кажется, она тестирует только первые 256 байтов, вне зависимости от того, насколько большой текст я передаю?

Ну, вообще-то нет. Меня это никогда не волновало, даже когда я ещё не работал в Microsoft. Я никогда не встречался со случаем, когда это на что-то влияло. Было бы неплохо, если бы кто-то изменил это поведение, но я не потеряю свой сон из-за этого - определённо это не та причина, почему я не люблю функцию!

Ладно, я просто вам скажу. Потому что функция должна проверять, следует ли строка стандарту. Почему следующие флаги так и не были добавлены за все эти годы, или даже в начальном релизе?
IS_TEXT_UNICODE_UNPAIRED_SURROGATES
Поскольку ситуация, когда старший (high) суррогат появляется без младшего (low), который идёт за ним, является недопустимой, то почему бы не обнаруживать такие случаи?
IS_TEXT_REVERSE_UNICODE_ILLEGAL_CHARS
Кажется, отсутсвие этого флага является нечестным для UTF-16BE, не так ли?
IS_TEXT_UNICODE_INVALID_FOR_4_00
Очевидно, такие новые флаги могли быть добавлены для каждой значительной версии - что может быть проще для проверки того, что недопустимо, в каждом официальном списке?
IS_TEXT_UNICODE_INVALID_SCRIPT_USAGE
Существует множество типов последовательностей байт, которые могут указать на недопустимое использование, начиная от комбинирующих отметок из одного языка, используемых для символов другого языка, заканчивая недопустимыми последовательностями с неверным порядком канонического комбинирования классов, и так далее.
IS_TEXT_UNICODE_VALID_UTF8_PER_RFC2799
Первоначальное описание UTF-8 в RFC 279, которое, я думаю, используется Блокнотом3.
IS_TEXT_UNICODE_VALID_UTF8_PER_UNICODE
Более строгое определение UTF-8, которое не допускает суррогатные последовательности и другие не кратчайшие формы.
IS_TEXT_UNICODE_VALID_UTF32 / IS_TEXT_UNICODE_VALID_REVERSE_UTF32
Эти флаги могут быть скомбинированы с некоторыми из старых флагов определения, если найдена сигнатура UTF-32 LE или BE.
IS_TEXT_UNICODE_UCS2_32 / IS_TEXT_UNICODE_REVERSE_UCS2_32
Аналог флагов IS_TEXT_UNICODE_ASCII16/IS_TEXT_UNICODE_REVERSE_ASCII16, они укажут на UTF-32, который выглядит так, что он может быть представлен как UTF-16 без суррогатных пар.

Вы уловили идею: Unicode - это динамичный стандарт, становящийся со времением всё более интересным и сложным, не только сам по себе, но и в том, как он используется платформой. Как может API функция, написанная больше десятилетия назад и никогда не обновляемая, чьей работой является выяснение "в этом буфере лежит Unicode текст?", даже хотя бы надеяться догнать такой стандарт?

1 - Блокнот является достойным упоминания исключением из этого правила, поскольку он использует функцию только для файлов без BOM.

2 - Аналогично тому, как были добавлены BeginUpdateResource, UpdateResource и EndUpdateResource, хотя я должен признать, что функции *UpdateResource появились потому, что Matt Curland сделал всю работу, чтобы заставить эти функции работать в Win9x :-)

3 - Это правила, которые используются MultiByteToWideChar в последние годы. Ирония в том, что функция MultiByteToWideChar используется Блокнотом для конвертации файлов, которые были определены как UTF-8 по правилам RFC 2279, что означает, что любые недопустимые последовательности будут молчаливо проигнорированы даже без предупреждения. Лучше держите эти файлы в CESU-8 подальше от последних версий Блокнота!

This post sponsored by out much maligned little brother "ഊ" (U+0d0a, a.k.a. MALAYALAM LETTER UU)
Кто, как и остальные символы из алфавита Malayalam script, чувствует себя хорошо поддерживаемым XP SP 2, но только затем, чтобы обнаружить, что функция IsTextUnicode не разделяет это мнение...

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.