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

Ничто не пахнет хуже локали потока, чем кодовая страница потока

Это перевод Nothing stinks worse than the thread locale, other than the thread code page. Автор: Майкл Каплан.

Вот кусок из письма, которое я получил от Ken:
Привет, Майкл.

Я встретился с чем-то, что, как я считаю, является багом в MultibyteToWideChar и WideCharToMultibyte, когда параметр кодовой страницы устанавливается в CP_THREAD_ACP, 'язык по-умолчанию для не-Unicode приложений' - установленный в иврит. Это видно, когда используется вспомогательный макрос из atlconv.h вроде T2WC.

Я создал простое тестовое приложение, которое показывает неожиданные результаты на некоторых системах. Кодовая страница от CP_THREAD_ACP получается не той же самой, что GetACP. Я воспроизвёл это на двух системах с ивритом, но не на системах, где стоит традиционный китайский - одна из которым имеет полностью локализированный в традиционном китайском UI. Исходник является частью консольного проекта .NET 2003 по умолчанию.
#include "stdafx.h"
#include <ostream>
int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << "default code page is " << GetACP() << std::endl;
    std::cout << "_AtlGetConversionACP code page is " << ATL::_AtlGetConversionACP() << std::endl;

    CPINFOEX cpinfo = {};
    GetCPInfoEx(ATL::_AtlGetConversionACP(), 0, &cpinfo);

    std::cout << "Thread code page is " << cpinfo.CodePage << std::endl;
    return 0;
}
Мои результаты:
default code page is 1255
_AtlGetConversionACP code page is 3
Thread code page is 1252
Когда моё приложение вызывает T2WC, оно получает неверный результаты, а кодовые точки расширяются до 16-ти бит, но не конвертируются в кодовые точки иврита. Мы обходим это использованием _CONVERSION_DONT_USE_THREAD_LOCALE, но мне любопытно, сталкивался ли кто-то ещё с этой проблемой ранее.

Спасибо за потраченное на меня время,
Ken
Постоянные читатели могут вспомнить, почему я не люблю локаль потока.

(Фактически, меня не так давно попросили помочь вычистить несколько плохих случаев использования локали потока в различных частях Windows в shell32.dll и shlwapi.dll - наверное, скоро я за это засяду!)

В любом случае, после того как Ken указал, что использование _CONVERSION_DONT_USE_THREAD_LOCALE решает проблему, становится довольно очевидно, что CP_THREAD_ACP есть ни что иное, как LOCALE_IDEFAULTANSICODEPAGE, возвращаемая GetLocaleInfo, когда ей передают GetThreadLocale в качестве LCID.

Теперь, кодовая страница потока - довольно шаткая вещь, и не только по той причине, что заставляет меня чувствовать, как воняет локаль потока. Представьте преобразование кодовой страницы, основанное на том, что может в любой момент поменять любой код, работающей в потоке. Фу!

В самом деле, особенно противно, что ATL и MFC сделали корневое изменения в версии 7.0 в этой области (как описано здесь):
Преобразования строк

В версиях ATL до и включая ATL 3.0 в Visual C++ 6.0 строковые преобразования с использованием макросов в atlconv.h всегда выполнялись, используя кодовую страницу ANSI системы (CP_ACP). Начиная с ATL 7.0 в Visual C++ .NET, строковые преобразования выполняются с использованием кодовой страницы ANSI текущего потока, если только не указана _CONVERSION_DONT_USE_THREAD_LOCALE. В последнем случае используется кодовая страница ANSI системы, как и ранее.

Заметьте, что классы преобразования строк, вроде CW2AEX, позволяют вам передавать кодовую страницу, используемую для преобразования, в их конструкторах. Если кодовая страница не указана, то класс использует ту же кодовую страницу, что и макрос.

Для дальнейшей информации см. макросы конвертации строк ATL и MFC.
Фу. Я ненавижу настолько плохие изменения. И это определённо такой случай :-(

Извини, Ken - эти странные различия являются результатом (плохого) дизайна. И твой обходной путь, фактически, является исправлением.

В конечном итоге, мой совет - НИКОГДА не использовать ни локаль потока, ни кодовую страницу потока, для чего бы то ни было. Никогда...

This post brought to you by װ (U+05f0, a.k.a. HEBREW LIGATURE YIDDISH DOUBLE VAV)

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

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

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

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

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

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

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