2009-11-02 23 views
7

El siguiente código muestra un comportamiento inesperado en mi máquina (probado con Visual C++ 2008 SP1 en Windows XP y VS 2012 en Windows 7):UTF-8 salida en la consola de 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); 
} 

simplemente he realizado con cl /EHsc test.cpp.

Windows XP: de salida en una ventana de la consola es ü0ü (traducido a la página de códigos 1252, originalmente muestra alguna línea de dibujo charachters en la página de códigos, tal vez 437 por defecto). Cuando cambio la configuración de la ventana de la consola para utilizar el juego de caracteres "Lucida Console" y ejecutar mi test.exe de nuevo, la salida se cambia a , lo que significa

  • el carácter ü se puede escribir usando fputs y su codificación UTF-8 C3 BC
  • std::cout no funciona por cualquier razón
  • las corrientes failbit está fijando después de tratar de escribir el carácter

Windows 7: La salida con Consolas es ��0ü. Aún más interesante. Los bytes correctos se escriben, probablemente (al menos cuando se redirige el resultado a un archivo) y el estado de la secuencia es correcto, pero los dos bytes se escriben como caracteres separados).

Intenté plantear este problema en "Microsoft Connect" (ver here), pero MS no me ha ayudado mucho. Bien podría mirar here como algo similar se ha pedido antes.

¿Puede reproducir este problema?

¿Qué estoy haciendo mal? ¿No deberían el std::cout y el fputs tener el mismo efecto ?

resuelto: (tipo de) Siguiendo la idea de mike.dld he implementado un std::stringbuf hacer la conversión de UTF-8 a Windows-1252 en sync() y se sustituye el streambuf de std::cout con este convertidor (ver mi comentario sobre Mike. respuesta del dld).

+0

he tenido problemas con C++ iostreams antes. hay muchas maldades ocultas que causan problemas. esto no vale una respuesta, pero cuando iostreams te da problemas, usa el stdio de c, lo he tenido muchas veces antes con problemas como este. –

+0

Sí, usar iostreams es más complicado que stdio, incluso hay [libros de texto completos] (http://www.amazon.com/Standard-Iostreams-Locales-Programmers-Reference/dp/0201183951) acerca de esto. Pero iostreams te dan una gran flexibilidad, que estoy usando con mucho gusto. – mkluwe

+0

¿No es un problema de la consola de Windows? Recuerdo que no es unicode consciente de ninguna manera, creando muchos de esos problemas ... –

Respuesta

0

Es hora de cerrar esto ahora. Stephan T. Lavavej says el comportamiento es "por diseño", aunque no puedo seguir esta explicación.

Mi conocimiento actual es: la consola de Windows XP en la página de códigos UTF-8 no funciona con C++ iostreams.

Windows XP está pasando de moda ahora y VS 2008. Me interesaría saber si el problema persiste en los sistemas Windows más nuevos.

En Windows 7 el efecto probablemente se deba a la forma en que los flujos de C++ generan caracteres. Como se ve en una respuesta a Properly print utf8 characters in windows console, la salida UTF-8 falla con C stdio cuando se imprime un byte tras otro como putc('\xc3'); putc('\xbc'); también. Quizás esto es lo que hacen las transmisiones C++ aquí.

+0

Existe :(Estoy intentando encontrar una solución alternativa en https://stackoverflow.com/questions/23584160/correct-and-crossplatform-way-to-use-utf- 8-in-c-streams Serás bienvenido :) – eraxillan

1

Oi. Felicitaciones por encontrar una forma de cambiar la página de códigos de la consola desde el interior de su programa. No sabía acerca de esa llamada, siempre tuve que usar chcp.

Supongo que la configuración predeterminada de C++ se está involucrando. De forma predeterminada, utilizará la página de códigos proporcionada por GetThreadLocale() para determinar la codificación de texto de elementos que no sean wstring. Por lo general, esto por defecto es CP1252. Podría intentar usar SetThreadLocale() para llegar a UTF-8 (si es que lo hace, no lo puede recuperar), con la esperanza de que std :: locale establezca de manera predeterminada algo que pueda manejar su codificación UTF-8.

+0

Definitivamente no es una solución, pero es algo en lo que no había pensado antes. Lo intentaré cuando regrese al trabajo en unos días (en mi casa estoy usando Linux ...). – mkluwe

+0

Miré esto de nuevo, pero SetThreadLocale no trata con la codificación, o no entiendo la documentación http://msdn.microsoft.com/en-us/library/dd374051(VS.85).aspx. Intenté un poco con std :: cout.imbue pero fue en vano. Este problema no se ha resuelto ... – mkluwe

3

Entiendo que la pregunta es bastante antigua, pero si alguien todavía estaría interesado, a continuación se muestra mi solución. Implementé un descendiente std :: streambuf bastante simple y luego lo pasé a cada una de las transmisiones estándar al principio de la ejecución del programa.

Esto le permite usar UTF-8 en cualquier lugar de su programa. En la entrada, los datos se toman de la consola en Unicode y luego se convierten y devuelven a usted en UTF-8. En el resultado, se realiza lo contrario, tomando datos de usted en UTF-8, convirtiéndolo a Unicode y enviándolo a la consola. No se encontraron problemas hasta el momento.

También tenga en cuenta que esta solución no requiere ninguna modificación de la página de códigos, ya sea con SetConsoleCP, SetConsoleOutputCP o chcp, o algo más.

Ese es el búfer de la secuencia:

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); 
} 

El uso de continuación es la siguiente:

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; 

    // ... 
} 

dejado fuera wstring_to_utf8 y utf8_to_wstring podría implementarse fácilmente con WideCharToMultiByte y MultiByteToWideChar funciones WinAPI.

+0

Esa fue una idea útil. Para la salida, terminé con una clase derivada de 'std :: stringbuf' (por lo que no tengo que hacer el búfer por mi cuenta) y simplemente implementé' sync() 'haciendo la conversión. En lugar de cablear el receptor de salida en el código, my 'sync()' inserta la cadena convertida en las corrientes originales streambuf. – mkluwe

Cuestiones relacionadas