C++ - 将wchar_t转换为网络字节序并返回

3

主要原因是我正在通过套接字发送Unicode数据(不是字符),我希望确保endianness匹配,因为wchar_t是UTF16。

此外,接收程序是我的另一个程序,所以我将知道它是UTF16并能够做出相应的反应。

以下是我的当前算法,它有点奇怪的结果。(这在同一个应用程序中,因为我想在发送之前学习如何转换)

case WM_CREATE: {   


    //Convert String to NetworkByte
    wchar_t Data[] = L"This is a string";
    char* DataA = (char*)Data;
    unsigned short uData = htons((unsigned int)DataA);

    //Convert String to HostByte
    unsigned short hData = ntohs(uData);
    DataA = (char*)&hData;
    wchar_t* DataW = (wchar_t*)DataA;
    MessageBeep(0);


    break;
}

结果:

쳌쳌쳌쳌쳌곭쳌쳌쳌쳌쳌ē쳌쳌쳌쳌This is a string

类型游走在C++中是未定义的行为。我认为你不能对DataA做你正在做的事情。 - asu
@Asu 我被告知如果我想通过套接字发送Unicode,则需要将其转换为字节,通过网络发送,然后通过将其转换回来重新创建字符串。如果这是一种不好的方法,那么有更好的方法吗?谢谢。 - Trevin Corkery
2
使用 MultiByteToWideCharWideCharToMultiByte 进行 UTF16(Windows 标准)和 UTF8(网络友好)之间的转换。示例 - Barmak Shemirani
2
你正在进行指针的转换和转型,而不是数据本身。 - Galik
1
你并没有重新创建数组。你是将其地址重新解释为字符数组。从现在开始,对它的写入和读取都是未定义行为。这就是为什么大多数情况下应该使用static_cast而不是C风格数组;它们可以防止这种混淆。 - asu
显示剩余6条评论
2个回答

7
UTF8和UTF16以完全不同的方式存储文本。将wchar_t*强制转换为是没有意义的,这与将float强制转换为相同。
使用WideCharToMultiByte将UTF16转换为UTF8以发送到网络功能。
从网络功能接收UTF8时,使用MultiByteToWideChar将其转换回UTF16,以便在Windows函数中使用。
示例:
#include <iostream>
#include <string>
#include <windows.h>

std::string get_utf8(const std::wstring &wstr)
{
    if (wstr.empty()) return std::string();
    int sz = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], -1, 0, 0, 0, 0);
    std::string res(sz, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], -1, &res[0], sz, 0, 0);
    return res;
}

std::wstring get_utf16(const std::string &str)
{
    if (str.empty()) return std::wstring();
    int sz = MultiByteToWideChar(CP_UTF8, 0, &str[0], -1, 0, 0);
    std::wstring res(sz, 0);
    MultiByteToWideChar(CP_UTF8, 0, &str[0], -1, &res[0], sz);
    return res;
}

int main()
{
    std::wstring greek = L"ελληνικά";

    std::string utf8 = get_utf8(greek);
    //use utf8.data() for network function...

    //convert utf8 back to utf16 so it can be displayed in Windows:
    std::wstring utf16 = get_utf16(utf8);
    MessageBoxW(0, utf16.c_str(), 0, 0);

    return 0;
}


编辑

以下示例将展示UTF16和UTF8之间的区别。该示例查看UTF16和UTF8的字节值。

请注意,对于拉丁字母表,UTF8和ANSI字节完全相同。

对于拉丁字母表,UTF8和UTF16有相似之处,但UTF16多了一个零。

对于希腊字母表和中文字符,差异明显。

//(Windows example)
void printbytes_char(const char* ANSI_or_UTF8)
{
    const char *bytes = ANSI_or_UTF8;
    int len = strlen(bytes);
    for (size_t i = 0; i < len; i++)
        printf("%02X ", 0xFF & bytes[i]);
    printf("\n");
}

void printbytes_wchar_t(const wchar_t* UTF16)
{
    //Note, in Windows wchar_t length is 2 bytes
    const char *bytes = (const char*)UTF16;
    int len = wcslen(UTF16) * 2;
    for (size_t i = 0; i < len; i++)
        printf("%02X ", 0xFF & bytes[i]);
    printf("\n");
}

int main()
{
    printbytes_char("ABC");
    printbytes_char(u8"ABC");
    printbytes_wchar_t(L"ABC");

    printbytes_char(u8"ελληνικά");
    printbytes_wchar_t(L"ελληνικά");

    printbytes_char(u8"汉字/漢字");
    printbytes_wchar_t(L"汉字/漢字");
    return 0;
}

输出:

"ABC":
41 42 43 //ANSI
41 42 43 //UTF8
41 00 42 00 43 00 //UTF16 (this is little endian, bytes are swapped)

"ελληνικά"
CE B5 CE BB CE BB CE B7 CE BD CE B9 CE BA CE AC //UTF8
B5 03 BB 03 BB 03 B7 03 BD 03 B9 03 BA 03 AC 03 //UTF16

"汉字/漢字"
E6 B1 89 E5 AD 97 2F E6 BC A2 E5 AD 97 //UTF8
49 6C 57 5B 2F 00 22 6F 57 5B //UTF16

是的,它是针对Windows特定的。OP已经标记为winsock。基于Unix的系统在任何地方都使用UTF8,因此它们不需要进行这种尴尬的转换。 - Barmak Shemirani
我认为它能工作,因为MessageBoxW处理Unicode。在控制台中尝试使用wprintf或std :: cout。[MessageBoxW(Unicode)和MessageBoxA(ANSI)](https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx) - Stargateur
@Stargateur Windows对于Windows控制台的Unicode支持有限,这是另一个复杂性。对于像MessageBox这样的Windows API,有UTF16支持(MessageBoxW)和ANSI支持(MessageBoxA)。UTF8和ANSI不同。只是碰巧在拉丁字母中,字符在ANSI和UTF8中是相同的。请参见更新的答案。Windows无法显示非拉丁字母的UTF8字符串,您需要使用基于Linux的机器进行测试。 - Barmak Shemirani

0
    wchar_t Data[] = L"test";

    //Convert String to NetworkByte
    for (wchar_t &val : Data) {
        if (sizeof(val) == 4) {
            val = htonl(val);
        }
        else if (sizeof(val) == 2) {
            val = htons(val);
        }
        else {
            static_assert(sizeof(val) <= 4, "wchar_t is gretter that 32 bit");
        }
    }

    //Convert String to HostByte
    for (wchar_t &val : Data) {
        if (sizeof(val) == 4) {
            val = ntohl(val);
        }
        else if (sizeof(val) == 2) {
            val = ntohs(val);

        }
        else {
            static_assert(sizeof(val) <= 4, "wchar_t is gretter that 32 bit");
        }
    }

不,你不能这样做。类型转换错误。尝试使用非拉丁语言并查看是否有效。 - Barmak Shemirani
@BarmakShemirani 你确定吗?因为htons和ntohs必须适用于所有类型小于或等于32位的情况。wchar_t不应该是最大32位的吗? - Stargateur
@BarmakShemirani 不好意思,我的解决方案可行,"ελληνικά"是由UTF8处理的,显然不支持宽字符。cpp.sh/4qx4ww - Stargateur
请注意,在Windows中,wchar_t的长度为2个字节,在基于Linux的系统中,wchar_t的长度为4个字节。 - Barmak Shemirani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接