Русский язык в консоли

Тема в разделе "LANGS.C", создана пользователем Praetor11, 29 окт 2009.

  1. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    Хочу поднять старую и давно избитую тему об использовании русского языка в консольных приложениях. Многим кажеться, что раз на эту тему многие пишут на форумах то ответ на неё давно найден. Я тоже так думал пока пара часов гугления не убедили меня в обратном. Итак, варианты решения задачи:

    1) Ручной конверт ANSI -> OEM. Выглядит это самописное счастье примерно так:

    Код (Text):
    1. //из ДОС в Windows
    2. char* Decode_DOS_to_Win(char * str)
    3. {
    4.  unsigned char *cstr=str;//"unsigned" - чтоб избежать
    5. предупреждений комп-ра
    6.  for(;*cstr;cstr++)
    7.  {
    8.    if(*cstr>=128 && *cstr<=175)
    9.      *cstr+=64;
    10.    else if(*cstr>=224 && *cstr<=239)
    11.      *cstr+=16;
    12.    else if(*cstr==252)
    13.      *cstr=185;
    14.  }
    15.  return str;
    16. }
    17. //----------------------------------------------------
    18. //из Windows в ДОС
    19. char* Decode_Win_to_DOS(char * str)
    20. {
    21.  unsigned char *cstr=str;
    22.  for(;*cstr;cstr++)
    23.  {
    24.    if(*cstr>=240)
    25.      *cstr-=16;
    26.    else if(*cstr>=192)
    27.      *cstr-=64;
    28.    else if(*cstr==185)
    29.      *cstr=252;
    30.  }
    31.  return str;
    32. }
    Вывод: аналог решению 2 (см. выводы решения 2)

    2) Использование CharToOEM, OEMToChar, CharToOEMBuff, OEMToCharBuff из user32.dll.
    Вывод: переводить каждую строчку этими функциями глупо, некрасиво, громоздко.

    3)Использование Unicode.
    Вывод: Собственно единственное действенное решение этой задачи (работает на всей линейке Win95/WinNT). Но если в С++ вы не хотите использовать Unicode по каким-либо причинам (а таких может быть много) он вам не подойдет.

    4)Использование SetConsoleCP и SetConsoleOutputCP (только для линейки NT, начиная с XP проф., но учитвая что 99% используемых "окон" и есть НТ, то фактически для всех)

    Вывод: На первый взгляд красивое и удобное решение проблемы, достаточно только написать где-то в начале своего консольного приложения: SetConsoleCP(1251); SetConsoleOutputCP(1251); и все по идее должно работать, однако...написанные мною на Си строчки:

    Код (Text):
    1.    
    2.    DWORD len = 5, write;
    3.    printf("Set Input: %d\n",SetConsoleCP (1251));
    4.    printf("Set Output: %d\n",SetConsoleOutputCP (1251));
    5.    
    6.    WriteConsole (GetStdHandle (0xfffffff5), (void *) "Вася\n", len, &write, NULL);
    7.    
    8.    printf("Get Input: %d\n",GetConsoleCP ());
    9.    printf("Get Output: %d\n",GetConsoleOutputCP ());
    Вывело вместо ожидаемого "Васи" набор кракозябр, несмотря на то, что установление новых кодовых страниц консоли прошло успешно, и судя по значениям, возвращенным GetConsoleCP и GetConsoleOutputCP текущей кодовой страницей консоли является именно страница ANSI (1251). В MSDN в описании функции WriteConsole прямым текстом написано следующее:

    Что в переводе на неизвестный язык означает примерно следующее:
    Функция использует для вывода на консоль текущую кодовую страницу консоли. По умолчанию для консоли назначается кодовая страница OEM, но вы можете изменить её через SetConsoleCP, SetConsoleOutputCP для ввода/вывода соотвественно.

    Вопрос: Какого хрена так НЕ происходит?? (моя винда XP SP3)

    P.S: При выводе стандартными функциями наблюдается такое же поведение.

    Если кто знает причину, или, того лучше, знает как исправить это - пишите. Давайте раз и навсегда решим эту гадкую проблему кодировок (Unicode, CString и CharToOEM не предлагать)))!
     
  2. FatMoon

    FatMoon New Member

    Публикаций:
    0
    Регистрация:
    28 ноя 2002
    Сообщения:
    954
    Адрес:
    Russia
    Не знаю, чего там предлагать или нет, и чего ты хочешь в итоге - но OemToChar и CharToOem ЕДИНСТВЕННОЕ правильное решение. Если ты не хочешь использовать юникод, естественно.

    SetConsoleCP и смена шрифта консоли (да, есть и такое, недокументированное - позволяет сменить шрифт с терминала на люциду... правда, не по имени шрифта, а по номеру, который от системы к системе отличается) - не годятся, и даже потенциальная причина косяков на других машинах.

    Из-за того, что по умолчанию твоя консоль использует растровый шрифт типа Terminal, и вытекающую отсюда единственную ОЕМ-кодировку, вне зависимости от того, какую кодовую страницу ты выбираешь. Создай ярлык для запуска, пропиши в свойствах шрифта только Lucida Console - и узрей работающую смену кодировок...
     
  3. reverser

    reverser New Member

    Публикаций:
    0
    Регистрация:
    27 янв 2004
    Сообщения:
    615
    setlocale(LC_ALL, ".1251") и всё работает.
     
  4. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    Я тоже так думал. Но, открыл консольное приложение в C#, там по-умолчанию русский шрифт работает норм (видимо из-за Unicode) и потом посмотрел какой шрифт имеет данное консольное окно - это была не Lucida, когда врубил люсиду вместо русского шрифта отобразило кракозярбры.

    А как это реализовать через API?
     
  5. reverser

    reverser New Member

    Публикаций:
    0
    Регистрация:
    27 янв 2004
    Сообщения:
    615
    setlocale действует только для функций из CRT (printf и т.п.). Если юзаешь только API, конвертируй сам.
     
  6. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    Этот способ не работает. Компилятор говорит что такой функции знать не знает и знать не хочет(

    Идея с установкой шрифта действительно работает. Через функцию SetConsoleFont можно установить подходящий шрифт (эксперименты под моей ХР показали, что шрифтами с корректным отображением русских символов ANSI являются шрифты с номерами 6,7,10,11). Но работает это не ЧЕРЕЗ раз. Т.е. один раз запускаешь все норм, на следующий день ни один шрифт не работает. По-моему тут какое-то колдунство..
     
  7. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Варианты продолжение:
    5) отказаться от консоли и юзать гуй.
    6) написать свою версию консоли с расширенными возможностями и без стандартных болячек, кстати в сети несколько таких версий валяются (сейчас искать лень).
     
  8. SL7549

    SL7549 New Member

    Публикаций:
    0
    Регистрация:
    24 мар 2009
    Сообщения:
    17
    по крайней мере у меня работает:
    Код (Text):
    1. #include <locale>
    2. #include <iostream>
    3.  
    4. int main()
    5. {
    6.     setlocale(LC_ALL,"Russian");
    7.     std::cout << "Тест" << std::endl;
    8.     system("pause");
    9.     return 0;
    10. }
     
  9. redcat

    redcat New Member

    Публикаций:
    0
    Регистрация:
    20 ноя 2007
    Сообщения:
    62
    У меня не работает (XP 64-bit SP1, VS2003)
     
  10. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    SL7549 спасибо что подсказали где находятся объявление функции setlocale. Подключив locale компилятор перестал на неё ругаться, однако:

    Код (Text):
    1.    
    2.  setlocale(LC_ALL,".1251");
    3.  printf("Вася дурак\n");
    и

    Код (Text):
    1.    
    2.  setlocale(LC_ALL,"Russian");
    3.  printf("Вася дурак\n");
    упорно выводят на экран нечитабельную ахинею(.. Боюсь что придется переходить на Unicode. Жаль только что мой компилятор от gnu не умеет с ним работать (( на L"Text" ругаецо, а макрос _T("Text") не переводит текст в Unicode (при надписи wchar_t* str = _T("Text"); выдает cannot convert `const char*' to `wchar_t*' in initialization) Так и знал что freeware синоним слову гавно. *Пошел за комплятором от мелкомягких...
     
  11. redcat

    redcat New Member

    Публикаций:
    0
    Регистрация:
    20 ноя 2007
    Сообщения:
    62
    Пробывал
    #define _UNICODE
     
  12. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    Пробовал. И #define UNICODE пробовал..На wchar_t* str = _T("Text"); ругаеца как и раньше. L"Text" не распознает (Illegal byte sequence).
     
  13. redcat

    redcat New Member

    Публикаций:
    0
    Регистрация:
    20 ноя 2007
    Сообщения:
    62
    Ты именно с чертой внизу _UNOCODE писал или просто UNICODE?
    Дело в том, что UNICODE - для поддержки юникода в WinAPI, а _UNICODE - для поддержки юникода в CRT. Причем #define _UNICODE должно находиться ДО заголовков.

    Короче вот работающий код в VC2003

    Код (Text):
    1. #define _UNICODE
    2. #include <tchar.h>
    3. #include <locale>
    4. #include <iostream>
    5.  
    6. int main()
    7. {
    8.     wchar_t* str = _T("Кириллица");
    9.     _wsetlocale(LC_ALL,_T("Russian_Russia.866"));
    10.     std::wcout << str << std::endl;
    11.     system("pause");
    12.     return 0;
    13. }
     
  14. RET

    RET Well-Known Member

    Публикаций:
    17
    Регистрация:
    5 янв 2008
    Сообщения:
    789
    Адрес:
    Jabber: darksys@sj.ms
    Без форматирования
    Код (Text):
    1. void rus_txt_out(char *str)
    2. {
    3.     char out_str[255];
    4.     CharToOem(str,out_str);
    5.     printf("%s",out_str);
    6.     return;
    7. }
    С форматированием
    Код (Text):
    1. void rus_printf(PCSTR Format, ...)
    2. {
    3.     char sz_buffer[512];
    4.     char sz_outbuffer[512];
    5.     va_list va;
    6.     va_start (va, Format);
    7.     _vsnprintf(sz_buffer, sizeof(sz_buffer)-1, Format, va);
    8.     va_end(va);
    9.     CharToOem(sz_buffer,sz_outbuffer);
    10.     printf(sz_outbuffer);
    11.     return;
    12. }
     
  15. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    Код (Text):
    1. Ты именно с чертой внизу _UNOCODE писал или просто UNICODE?
    2. Дело в том, что UNICODE - для поддержки юникода в WinAPI, а _UNICODE - для поддержки юникода в CRT. Причем #define _UNICODE должно находиться ДО заголовков.
    Да, я писал сначала _UNICODE. Результат тот же поэтому пока на это дело забил, до того как установлю компилятор от МС.

    Ув. RET, я же просил советы с CharToOem не предлагать, потому как это уже давно всем известный и через одно местовский метод.
     
  16. RET

    RET Well-Known Member

    Публикаций:
    17
    Регистрация:
    5 янв 2008
    Сообщения:
    789
    Адрес:
    Jabber: darksys@sj.ms
    Вот Си-листинг CharToOemA, полученный через HexRays:
    Код (Text):
    1. BOOL __stdcall CharToOemA(LPCSTR lpszSrc, LPSTR lpszDst)
    2. {
    3.   LPCSTR v2;
    4.   LPSTR v3;
    5.   BOOL result;
    6.   const CHAR v5;
    7.  
    8.   v2 = lpszSrc;
    9.   if ( lpszSrc && (v3 = lpszDst, lpszDst) )
    10.   {
    11.     do
    12.     {
    13.       *v3 = *(_BYTE *)(*v2 + dword_77FA1208 + 1210);
    14.       v5 = *v2;
    15.       ++v3;
    16.       ++v2;
    17.     }
    18.     while ( v5 );
    19.     result = 1;
    20.   }
    21.   else
    22.   {
    23.     result = 0;
    24.   }
    25.   return result;
    26. }
    где dword_77FA1208, как я понимаю что-то связанное с локалью (можно в отладчике глянуть).
    При желании можно вставить уже готовый мелкософтовский код, что бы не был
     
  17. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    Хм а где вы скачали ХексРейс + IDA, киньте ссылочку плиз))
     
  18. RET

    RET Well-Known Member

    Публикаций:
    17
    Регистрация:
    5 янв 2008
    Сообщения:
    789
    Адрес:
    Jabber: darksys@sj.ms
    купил ;)
     
  19. Praetor11

    Praetor11 New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2009
    Сообщения:
    80
    Так, подведем итоги 2-го дня обсуждения. Итог 1: никто не знает как заставить консоль виндовс выводить текст в кодировке ANSI. Итог 2: Прогеры из микрософт - козлы.

    Впрочем тесты показали , что SetConsoleCP(1251); срабатывает, scanf, ReadConsole и даже ReadFile на буфер консольного ввода срабатывают на ура возвращая ANSI.

    В описании SetConsoleOutputCP однако написано что если стоит какой-то нетакой шрифт то она не будет иметь эффекта. Я обнаружил почему способ SetConsoleFont (,10); срабатывало через раз: если хоть раз за сессию (от вкл до выкл винды) вручную был установлен шрифт Lucida (даже если потом сразу заменен - для любого из консольных окон) то все работает. До этого - все 13 системных консольных шрифтов оторбражают кракозюли.

    Между прочим использование Unicode тоже не спасает, к примеру WriteConsoleW все равно выдает кракозюли для русского текста.

    Мб покопаться немного в кернеле IDой?)
     
  20. dyn

    dyn New Member

    Публикаций:
    0
    Регистрация:
    30 окт 2009
    Сообщения:
    566
    В простейшем случае, сделать надстройку над принтф. Например myprintf. Которая сперва пропускает строку через CharToOEM, а потом передает ее оригинальной принтф.

    В более извращенной форме - похукать принтф.