Строка и байтовый массив -- одно и то же? только не в VB6!

Персональный блог одноименного форумчанина. Человека и парохода, не побоюсь этого сравнения :)

Модератор: tyomitch

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Строка и байтовый массив -- одно и то же? только не в VB6!

Сообщение tyomitch » 10.06.2006 (Сб) 1:49

Вы, наверное, думали, что строка и байтовый массив -- одно и то же, раз их даже можно присваивать в обе стороны? Я тоже так думал.

* Пока мы пользуемся голым VB6, мы можем хранить в строке любую гадость. Недопустимых символов в Юникоде достаточно много, но (к счастью) VB не станет "по собственной инициативе" проверять корректность строки -- до тех пор, пока он не будет вынужден это сделать при работе со строковыми функциями типа IsNumeric. Даже и при этом самое худшее, что может произойти -- поломка его "искусственного интеллекта"; исправлять ошибки в строке он не решится.

* Всё меняется, когда в смесь добавляются API. Как мы все убедились, VB6 уверен, что все API принимают и возвращают исключительно ANSI-строки -- это при том, что внутри VB строки хранятся исключительно в Юникоде. У нас нет никакого шанса избежать двух конвертаций (одна по дороге туда, вторая -- обратно) при передаче строки в качестве параметра, объявленного As String. Есть множество обходных путей (передавать StrPtr как ByVal As Long, объявлять функцию в TLB и т.д.), которые в данном контексте несущественны. Важен факт: передача строки As String -- это пара конвертаций, сначала из Юникода в ANSI, потом обратно.

Что, если нам нужно передать в API строку в виде Юникода? Лобовое решение -- передавать StrConv(vbUnicode); получается строка "в кодировке Double Secret Unicode", которая затем подвергается названной паре преобразований. Итог для передачи юникодных строк As String -- три преобразования для входного параметра и четыре для выходного. Неплохо для языка, в котором Юникод -- родная кодировка?

* Теперь -- самое интересное. Преобразование из Юникода в ANSI необратимо -- поэтому на выходе API мы гарантированно получаем испорченную строку. На самом деле это из-за того, что API сама получила испорченную строку на входе. Это не так уж разрушительно: если API по своей природе принимает ANSI-строку, то она всё равно способна обрабатывать в строке только те символы Юникода, которые есть в ANSI-кодировке.

А что, если преобразование ANSI->Юникод->ANSI нетождественное? (Так оно и есть в некоторых восточноазиатских кодировках с MBCS, "ведущими байтами", "нормализацией радикалов" и другими страшными словами.) Тогда преобразование Юникод->Двойной Юникод->Юникод тоже будет нетождественным, и наша API, принимающая юникодную строку, получит мусор -- даже хотя она была способна обработать любые символы Юникода. Самое милое в этом баге -- то, что его эффекты меняются в зависимости от системной локали, и вовсе не проявляются в европейских локалях, в которых преобразование ANSI->Юникод->ANSI тождественное.

Теперь представим себе, что рассматриваемая API -- это CallWindowProc(ByVal As String, ByVal As Long, ByVal As Long, ByVal As Long, ByVal As Long). Мы вызываем её следующим образом:
Код: Выделить всё
CallWindowProc Chr(&H14)+Chr(&H76)+Chr(&H55)+Chr(&HD0)+Chr(&HF8), 0,0,0,0

(собственно строка с кодом взята с потолка, не обращайте на неё внимания)

При вызове Chr происходит преобразование из ANSI в Юникод, при вызове CallWindowProc -- обратно. С большой вероятностью по дороге сломается байт-другой; результат -- фатальный вылет в восточноазиатских локалях. Такой баг можно было бы выискивать годами: никому не придёт в голову, что вызываемый фрагмент ассемблерного кода как-то связан с языковыми настройками машины.

* Как этот код можно починить? Близорукий европеец, наверное, заменил бы Chr на ChrW: ChrW не выполняет преобразования кодировок, значит, по идее, не будет и "круга с подвохом". И действительно, у этого европейца такой код будет работать:
Код: Выделить всё
CallWindowProc ChrW(&H14)+ChrW(&H76)+ChrW(&H55)+ChrW(&HD0)+ChrW(&HF8), 0,0,0,0

В чём баг теперь? Символы, которые произведёт на свет ChrW, будут заключены между U+0000 и U+00FF. Символов старше U+007F (расширенная латиница) гарантированно не окажется ни в одной восточноазиатской кодировке: да что там, их даже в 1251 нет. Значит, теперь на этапе преобразования из Юникода в ANSI при вызове CallWindowProc похерятся все байты старше &H7F. Баг не только не исправлен: он усугублен.

* А истинное решение -- не пытаться перехитрить VB6, и для хранения байтовых массивов использовать именно байтовые массивы. Уж с ними-то VB6 такие вольности себе не позволяет.
Изображение

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.06.2006 (Сб) 10:10

Полуоффтоп: в комментариях к посту Майкла Каплана, на который я сослался выше, ругают Мэтта Курланда, который догадался передавать ассемблерный код в строковой переменной.

На самом деле, Курланд догадался ещё и не до такого -- код короче 8 байт он передаёт RyRef As Currency :lol:
Изображение


Вернуться в Tyomitch

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 6

    TopList