2009-11-02 22 views
7

Poniższy kod pokazuje nieoczekiwane zachowanie na moim komputerze (testowane z Visual C++ 2008 SP1 w systemie Windows XP i VS 2012 na Windows 7):UTF-8 wyjście na konsoli systemu Windows

#include <iostream> 
#include "Windows.h" 

int main() { 
    SetConsoleOutputCP(CP_UTF8); 
    std::cout << "\xc3\xbc"; 
    int fail = std::cout.fail() ? '1': '0'; 
    fputc(fail, stdout); 
    fputs("\xc3\xbc", stdout); 
} 

po prostu skompilowany z cl /EHsc test.cpp.

Windows XP: Wyjście w oknie konsoli jest ü0ü (tłumaczona na stronę kodową 1252, pierwotnie pokazuje pewne rysowanie linii charachters w domyślnej strony kodowej, chyba 437). Kiedy zmienić ustawienia okna konsoli, aby użyć „Lucida Console” zestaw znaków i ponownie uruchomić mój test.exe, wyjście zmienia się na , co oznacza

  • postać ü mogą być zapisywane za pomocą fputs i jego kodowanie UTF-8 C3 BC
  • std::cout nie działa z jakiegoś powodu
  • strumieni failbit wyznacza po próbuje pisać postać

Windows 7: Wyjście przy użyciu konsoli Consolas to ��0ü. Jeszcze bardziej interesujące. Prawidłowe bajty są zapisywane prawdopodobnie (przynajmniej podczas przekierowywania wyjścia do pliku), a stan strumienia jest w porządku, ale dwa bajty są zapisywane jako oddzielne znaki).

Próbowałem poruszyć ten problem w "Microsoft Connect" (patrz here), , ale MS nie było bardzo pomocne. Równie dobrze możesz wyglądać here jak już wcześniej zadawano coś podobnego.

Czy możesz odtworzyć ten problem?

Co robię źle? Czy znaki std::cout i fputs nie powinny mieć tego samego efektu?

rozwiązany: (w pewnym sensie) Po idei mike.dld za I wdrożone std::stringbuf robi konwersję z UTF-8 dla Windows-1252 w sync() i zastąpił streambuf z std::cout z tego konwertera (patrz mój komentarz na mikrofonie. odpowiedź dld).

+0

Miałem problemy z wersją C++ Iostreams. jest dużo ukrytej złośliwości, która powoduje problemy. nie jest to warte odpowiedzi, ale kiedy iostreams sprawia ci kłopot, użyj stdio c, musiałem już wiele razy z takimi właśnie problemami. –

+0

Tak, używanie iostreams jest bardziej skomplikowane niż stdio, istnieją nawet [pełne teksty] (http://www.amazon.com/Standard-Iostreams-Locales-Programmers-Reference/dp/0201183951) na ten temat. Ale iostreams daje dużą elastyczność, z której korzystam chętnie. – mkluwe

+0

Czy to nie problem z konsolą systemu Windows? Pamiętam, że nie jest on w żaden sposób znany z Unicode, tworząc wiele takich problemów ... –

Odpowiedz

0

Czas to teraz zamknąć. Stephan T. Lavavej says zachowanie jest "z założenia", chociaż nie mogę postępować zgodnie z tym wyjaśnieniem.

Moja obecna wiedza: konsola Windows XP na stronie kodowej UTF-8 nie działa z iostreamami C++.

Windows XP wychodzi teraz z mody, podobnie jak VS 2008. Byłbym zainteresowany, gdyby problem nadal występował w nowszych systemach Windows.

W systemie Windows 7 efekt jest prawdopodobnie spowodowany sposobem, w jaki strumienie C++ wyprowadzają znaki. Jak widać w odpowiedzi na Properly print utf8 characters in windows console, wyjście UTF-8 kończy się niepowodzeniem z C stdio przy drukowaniu jednego bajtu po drugim, podobnie jak putc('\xc3'); putc('\xbc');. Być może to tutaj robią strumienie C++.

+0

Istnieje :(próbuję znaleźć obejście w https://stackoverflow.com/questions/23584160/correct-and-crossplatform-way-to-use-utf- 8-in-c-stream Powitanie:) – eraxillan

1

Oi. Gratuluję znalezienia sposobu na zmianę strony kodowej konsoli z poziomu twojego programu. Nie wiedziałem o tym telefonie, zawsze musiałem korzystać z chcp.

Zgaduję, że domyślne locale C++ się angażuje. Domyślnie użyje strony kodowej dostarczonej przez GetThreadLocale() w celu określenia kodowania tekstu nie-wstringowych rzeczy. Zwykle domyślnie jest to CP1252. Możesz spróbować użyć SetThreadLocale(), aby dostać się do UTF-8 (jeśli to nawet robi, nie pamiętasz), z nadzieją, że std :: locale domyślnie będzie obsługiwać twoje kodowanie UTF-8.

+0

Zdecydowanie nie jest to rozwiązanie, ale coś, o czym wcześniej nie myślałem. Spróbuję, gdy wrócę do pracy za kilka dni (w domu używam Linuksa ...). – mkluwe

+0

Spojrzałem na to jeszcze raz, ale SetThreadLocale nie zajmuje się kodowaniem, lub nie rozumiem dokumentacji http://msdn.microsoft.com/en-us/library/dd374051(VS.85).aspx. Próbowałem trochę z std :: cout.imbue, ale bez skutku. Ten problem pozostaje nierozwiązany ... – mkluwe

3

Rozumiem, że pytanie jest dość stare, ale jeśli ktoś nadal będzie zainteresowany, poniżej jest moje rozwiązanie. Zaimplementowałem dość prostego potomka std :: streambuf, a następnie przekazałem go do każdego ze standardowych strumieni na samym początku wykonywania programu.

Umożliwia to używanie UTF-8 wszędzie w swoim programie. Na wejściu dane są pobierane z konsoli w Unicode, a następnie konwertowane i zwracane w UTF-8. Na wyjściu robi się odwrotnie, pobierając dane od ciebie w UTF-8, konwertując je do Unicode i wysyłając na konsolę. Nie znaleziono żadnych problemów.

Należy również zauważyć, że to rozwiązanie nie wymaga modyfikacji strony kodowej, ani z SetConsoleCP, SetConsoleOutputCP lub chcp, ani z czymś innym.

To bufor strumienia:

class ConsoleStreamBufWin32 : public std::streambuf 
{ 
public: 
    ConsoleStreamBufWin32(DWORD handleId, bool isInput); 

protected: 
    // std::basic_streambuf 
    virtual std::streambuf* setbuf(char_type* s, std::streamsize n); 
    virtual int sync(); 
    virtual int_type underflow(); 
    virtual int_type overflow(int_type c = traits_type::eof()); 

private: 
    HANDLE const m_handle; 
    bool const m_isInput; 
    std::string m_buffer; 
}; 

ConsoleStreamBufWin32::ConsoleStreamBufWin32(DWORD handleId, bool isInput) : 
    m_handle(::GetStdHandle(handleId)), 
    m_isInput(isInput), 
    m_buffer() 
{ 
    if (m_isInput) 
    { 
     setg(0, 0, 0); 
    } 
} 

std::streambuf* ConsoleStreamBufWin32::setbuf(char_type* /*s*/, std::streamsize /*n*/) 
{ 
    return 0; 
} 

int ConsoleStreamBufWin32::sync() 
{ 
    if (m_isInput) 
    { 
     ::FlushConsoleInputBuffer(m_handle); 
     setg(0, 0, 0); 
    } 
    else 
    { 
     if (m_buffer.empty()) 
     { 
      return 0; 
     } 

     std::wstring const wideBuffer = utf8_to_wstring(m_buffer); 
     DWORD writtenSize; 
     ::WriteConsoleW(m_handle, wideBuffer.c_str(), wideBuffer.size(), &writtenSize, NULL); 
    } 

    m_buffer.clear(); 

    return 0; 
} 

ConsoleStreamBufWin32::int_type ConsoleStreamBufWin32::underflow() 
{ 
    if (!m_isInput) 
    { 
     return traits_type::eof(); 
    } 

    if (gptr() >= egptr()) 
    { 
     wchar_t wideBuffer[128]; 
     DWORD readSize; 
     if (!::ReadConsoleW(m_handle, wideBuffer, ARRAYSIZE(wideBuffer) - 1, &readSize, NULL)) 
     { 
      return traits_type::eof(); 
     } 

     wideBuffer[readSize] = L'\0'; 
     m_buffer = wstring_to_utf8(wideBuffer); 

     setg(&m_buffer[0], &m_buffer[0], &m_buffer[0] + m_buffer.size()); 

     if (gptr() >= egptr()) 
     { 
      return traits_type::eof(); 
     } 
    } 

    return sgetc(); 
} 

ConsoleStreamBufWin32::int_type ConsoleStreamBufWin32::overflow(int_type c) 
{ 
    if (m_isInput) 
    { 
     return traits_type::eof(); 
    } 

    m_buffer += traits_type::to_char_type(c); 
    return traits_type::not_eof(c); 
} 

Wykorzystanie następnie przedstawia się następująco:

template<typename StreamT> 
inline void FixStdStream(DWORD handleId, bool isInput, StreamT& stream) 
{ 
    if (::GetFileType(::GetStdHandle(handleId)) == FILE_TYPE_CHAR) 
    { 
     stream.rdbuf(new ConsoleStreamBufWin32(handleId, isInput)); 
    } 
} 

// ... 

int main() 
{ 
    FixStdStream(STD_INPUT_HANDLE, true, std::cin); 
    FixStdStream(STD_OUTPUT_HANDLE, false, std::cout); 
    FixStdStream(STD_ERROR_HANDLE, false, std::cerr); 

    // ... 

    std::cout << "\xc3\xbc" << std::endl; 

    // ... 
} 

pominięte wstring_to_utf8 i utf8_to_wstring może być łatwo realizowane z WideCharToMultiByte i MultiByteToWideChar funkcji WinAPI.

+0

To był pomocny pomysł. W przypadku wyjścia otrzymałem klasę wyprowadzoną z 'std :: stringbuf' (więc nie muszę sam wykonywać buforowania) i właśnie zaimplementowałem' sync() 'wykonującą konwersję. Zamiast twardego okablowania wyjścia w kodzie, moje 'sync()' wstawia przekonwertowany ciąg do strumieni oryginalnych streambuf. – mkluwe

Powiązane problemy