在Windows

时间:2016-07-02 13:54:02

标签: c powershell winapi utf-8

我正在尝试使用stdinfgets()读取UTF-8字符串。控制台输入模式之前已设置为CP_UTF8。我还在PowerShell中将控制台字体设置为Lucida Console。最后,我通过使用Ä将德语printf()(UTF-8:0xC3,0x84)打印到控制台,验证了UTF-8输出是否有效。这工作正常,但fgets()似乎无法从控制台读取UTF-8。这是一个小测试程序:

#include <stdio.h>  
#include <windows.h>

int main(int argc, char *argv[])
{
    unsigned char s[64];

    memset(s, 0, 64);

    SetConsoleOutputCP(CP_UTF8);    
    SetConsoleCP(CP_UTF8);

    printf("UTF-8 Test: %c%c\n", 0xc3, 0x84);  // print Ä

    fgets(s, 64, stdin);

    printf("Result: %d %d\n", s[0], s[1]);

    return 0;
}

运行此程序并输入“Ä”然后点击ENTER时,它只打印以下内容:

Result: 0 0

即。没有写到s。但是,当输入“A”时,我得到以下正确的结果:

Result: 65 10

那么如何让fgets()在Windows上使用UTF-8字符呢?

修改

根据Barmak的解释,我现在更新了我的代码以使用wchar_t函数而不是ANSI函数。但是,它仍然无效。这是我的代码:

#include <stdio.h>
#include <io.h>
#include <fcntl.h>

#include <windows.h>

int main(int argc, char *argv[])
{
    wchar_t s[64];

    memset(s, 0, 64 * sizeof(wchar_t));

    _setmode(_fileno(stdin), _O_U16TEXT);       
    fgetws(s, 64, stdin);

    wprintf(L"Result: %d\n", s[0]);

    return 0;
}   

输入A时,程序会打印Result: 3393,但我希望它是65。输入Ä时,程序会打印Result: 0,但我希望它是196。到底发生了什么事?为什么现在甚至没有为ASCII字符工作?我只使用fgets()的旧程序可以正常使用A之类的ASCII字符,但只有Ä这样的非ASCII字符才会失败。但新版本甚至不适用于ASCII字符,或3393 A的正确结果是65?我希望它是var regex = new RegExp('^/(.+)/(.*)$') function stringToRegex(s) { var match = s.match(regex) return new RegExp(match[1], match[2]) } var test = new RegExp("weather", "gi") console.log(stringToRegex(test.toString())) console.log(stringToRegex(test.toString()).toString() === test.toString())。我现在很困惑......请帮忙!

2 个答案:

答案 0 :(得分:3)

Windows使用UTF16。最有可能是Windows&#39;控制台不支持UTF8。

使用UTF16和宽字符串函数(wcsxxx而不是strxxx)。然后,您可以使用WideCharToMultiByte将UTF16转换为UTF8。例如:

#include <stdio.h>
#include <string.h>
#include <io.h> //for _setmode
#include <fcntl.h> //for _O_U16TEXT

int main()
{
    _setmode(_fileno(stdout), _O_U16TEXT);
    _setmode(_fileno(stdin), _O_U16TEXT);
    wchar_t s[64];
    fgetws(s, 64, stdin);
    _putws(s);
    return 0;
}

请注意,在调用_setmode(_fileno(stdout), _O_U16TEXT)后您无法使用ANSI打印功能,必须重置它。您可以尝试下面的功能,重置文本模式。

char* mygets(int wlen)
{
    //may require fflush here, see _setmode documentation
    int save = _setmode(_fileno(stdin), _O_U16TEXT);
    wchar_t *wstr = malloc(wlen * sizeof(wchar_t));
    fgetws(wstr, wlen, stdin);

    //make UTF-8:
    int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, 0, 0, 0, 0);
    if (!len) return NULL;
    char* str = malloc(len);
    WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, 0, 0);
    free(wstr);

    _setmode(_fileno(stdin), save);
    return str;
}

void myputs(const char* str)
{
    //may require fflush here, see _setmode documentation
    int save = _setmode(_fileno(stdout), _O_U16TEXT);

    //make UTF-16
    int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0);
    if (!wlen) return;
    wchar_t* wstr = malloc(wlen * sizeof(wchar_t));
    memset(wstr, 0, wlen * 2);
    MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);

    _putws(wstr);
    _setmode(_fileno(stdout), save);
}

int main()
{
    char* utf8 = mygets(100);
    if (utf8)
    {
        myputs(utf8);
        free(utf8);
    }
    return 0;
}

答案 1 :(得分:2)

所有Windows本机字符串操作(极少有例外)都在UNICODE(UTF-16)中 - 所以我们必须在任何地方使用unicode函数。使用ANSI变体 - 非常糟糕的做法。如果您将在您的示例中使用unicode函数 - 所有这些都将正常工作。用ANSI这不起作用.. windows bug! 我可以用所有细节(在win 8.1上研究)来涵盖这个:

1)在控制台服务器进程中存在2个全局变量:

UINT gInputCodePage, gOutputCodePage;

它可以通过GetConsoleCP / SetConsoleCP和GetConsoleOutputCP / SetConsoleOutputCP读/写。 当需要转换时,它们用作WideCharToMultiByte / MultiByteToWideChar的第一个参数。如果你只使用unicode函数 - 它们从未使用过

2.a)当您写入控制台UNICODE文本时 - 它将按原样写入而不进行任何转换。在服务器端,这在SB_DoSrvWriteConsole函数中完成。看图片: enter image description here 2.b)当您写入控制台ANSI文本时 - SB_DoSrvWriteConsole也将被调用,但是还有一个额外的步骤--MultiByteToWideChar(gOutputCodePage,...) - 您的文本将首先转换为UNICODE。 enter image description here 但在这一刻。看: enter image description here 在MultiByteToWideChar中调用cchWideChar == cbMultiByte。如果我们只使用'english'字符集(字符&lt; 0x80)UNICODE的长度和字符中的多字节字符串总是相等,但是使用其他语言 - 通常的多字节版本使用比UNICODE更多的字符但是这里没有问题,只是输出缓冲区的大小更多需要,但没关系。所以printf一般都会正常工作。仅限一个注释 - 如果您在源代码中硬编码多字节字符串 - 更快的是它将采用CP_ACP格式,并且使用CP_UTF8转换为UNICODE - 会给出不正确的结果。所以这取决于您的源文件保存在磁盘上的格式:)

3.a)当你从具有UNICODE功能的控制台读取时 - 你得到了完全UNICODE文本。这里没有任何问题。如果需要 - 你可以通过自我转换为多字节直接

3.b)当你从带有ANSI函数的控制台读取时 - 服务器首先将UNICODE字符串转换为ANSI,然后返回给你的ANSI表单。这是由功能

完成的
int ConvertToOem(UINT CodePage /*=gInputCodePage*/, PCWSTR lpWideCharStr, int cchWideChar, PSTR lpMultiByteStr, int cbMultiByte)
{
    if (CodePage == g_OEMCP)
    {
        ULONG BytesInOemString;
        return 0 > RtlUnicodeToOemN(lpMultiByteStr, cbMultiByte, &BytesInOemString, lpWideCharStr, cchWideChar * sizeof(WCHAR)) ? 0 : BytesInOemString;
    }
    return WideCharToMultiByte(CodePage, 0, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, 0, 0);
}

但是让我们看看更接近,ConvertToOem如何调用: enter image description here 这里再次cbMultiByte == cchWideChar,但这是100%的错误!多字节字符串可以比UNICODE更长(当然是chars)。例如“Ä” - 这是1个UNICODE字符和2个UTF8字符。结果WideCharToMultiByte  返回0.(ERROR_INSUFFICIENT_BUFFER)