четверг, 25 июня 2009 г.

"Локальные" баги, STL-way

На днях поступил очередной bug-report из серии "при установке на мою систему неправильно отображается экран XXXX"....

Первый вопрос, который в таких ситуациях теперь возникает - на какую версию Windows была поставлена система (в данном случае ответом была - русскую).

Первое, что я теперь в таких случаях делаю - в Regional Settings заменяю Russian на English и повторяю дейстывия пользователя. Баг, пропал. Вернул настройки - баг возник.

Собственно, после этого раскрытие причины было делом техники - в результате очень быстро отыскался "забытый" вызов imbue() на один из ostringstream'ов.
При обработке XML-файла, описывающего layout экрана "выстрелило" различие между "." и ",".

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

У нас есть, в принципе, три варианта поведения при работе разных вызовов, работающих со строками:

1) Использовать текущую локаль потока. Так, например, поступают всякие функции типа lstrcmpi, экспортируемые Kernel32. C-runtime действует так же.

2) Использовать текущую Windows ANSI кодовую страницу. Это характерно, например, для макросов A2W, A2CW ATL3, которые делают вызов MultiByteToWideChar(CP_ACP,...).

3) Использовать классическую локаль. Это характерно, например, для STL.

Казалось бы, если разрабатывается мультиязычное не Unicode-приложение, достаточно различать эти варианты и уметь переходить от любого из них к выбранному для приложения базовому (а вот выбрать его во избежание бардака надо!)...

Ан нет! Сюрпризы нас ждут везде и всегда... Попробуем собрать и запустить вот такое приложение:
int main(int argc, char* argv[])
{
{ std::ofstream fs("C:\\Transas\\NS3000\\ROUTES\\Маршрут в никуда.rt3"); }
setlocale(LC_ALL, "Russian");
{ std::ofstream fs("C:\\Transas\\NS3000\\ROUTES\\Маршрут из ниоткуда.rt3"); }
return 0;
}

Как ни странно, но файл создастся только один... ;-)

Если посмотреть на реализацию реализацию fstream, точнее, basic_filebuf, а еще точнее - _Fiopen от DinkumWare для MSVC 2008 - вот там для преобразования ANSI-UNICODE тупо используется mbstowcs_s без всяких упоминаний о локали (соответственно - с текущей для потока)...
Желающие могут убедиться сами ;-)
_CRTIMP2_PURE FILE *__CLRCALL_PURE_OR_CDECL _Fiopen(const char *filename,
ios_base::openmode mode, int prot)
{ // open wide-named file with byte name
wchar_t wc_name[FILENAME_MAX];


if (mbstowcs_s(NULL, wc_name, FILENAME_MAX, filename, FILENAME_MAX - 1) != 0)
return (0);
return _Fiopen(wc_name, mode, prot);
}

Так что когда-нибудь кого-нибудь ждет письмо из тех. поддержки на тему - "почему файл не открывается", "почему данные неправильно читаются" и так далее.

Upd: очень схожая проблематика обсуждается здесь. Только у человека все на порядок брутальнее, поскольку локаль ему по ходу работы подменял внешний модуль, который (по идее) этого делать бы не должен...

BTW, все-таки в "чистом" STL преобразования ANSI/UNICODE выглядят ужасно...
static const std::ctype<wchar_t> & facet =
std::use_facet<std::ctype<wchar_t> >(std::locale::classic());
std::transform(
src.begin(),
src.end(),
std::back_inserter(dst),
bind1st(std::ptr_fun(&std::ctype<wchar_t>::widen)), &facet)
);

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