* Пока мы пользуемся голым 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 такие вольности себе не позволяет.