没有一个好的
最小、完整和可验证的代码示例,我们无法确定,但在我看来,您可能在使用错误的C++转换器。
std::codecvt_utf8<wchar_t>
locale将从UCS-2进行转换,而不是UTF-16。这两者非常相似,但UCS-2不支持所需的替代对,以编码您想要编码的字符。
相反,您应该使用std::codecvt_utf8_utf16<wchar_t>
:
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> utf8Converter;
std::string utf8str = utf8Converter.to_bytes(wstr);
当我使用该转换器时,我得到了所需的UTF-8字节:
F0 9F 8C 8E
。当作为UTF-8解释时,这些字节当然可以在.NET中正确解码。
附加说明:
问题已经更新,指示编码代码无法更改。您必须使用已编码为无效UTF8的UCS-2。由于UTF8是无效的,因此您必须自己解码文本。
我看到有几种合理的方法可以做到这一点。首先,编写一个解码器,不关心UTF8是否包括无效的字节序列。其次,使用C++
std::wstring_convert<std::codecvt_utf8<wchar_t>>
转换器来解码字节(例如,在C++中编写接收代码,或者编写一个C++ DLL,您可以从C#代码调用它来完成工作)。
第二个选项在某种意义上更可靠,即您正在使用创建错误数据的确切解码器。另一方面,即使创建DLL,甚至使用C++/CLI,您仍然需要一些头痛才能正确地进行交互,除非您已经是专家。
我对C++/CLI有一定了解,但并不是专家。我更擅长C#,因此这是第一种选择的一些代码:
private const int _khighOffset = 0xD800 - (0x10000 >> 10);
private static string DecodeUtf8WithOverlong(byte[] bytes)
{
List<char> result = new List<char>();
int continuationCount = 0, continuationAccumulator = 0, highBase = 0;
char continuationBase = '\0';
for (int i = 0; i < bytes.Length; i++)
{
byte b = bytes[i];
if (b < 0x80)
{
result.Add((char)b);
continue;
}
if (b < 0xC0)
{
if (continuationCount == 0)
{
throw new ArgumentException("invalid encoding");
}
continuationAccumulator <<= 6;
continuationAccumulator |= (b - 0x80);
if (--continuationCount == 0)
{
continuationAccumulator += highBase;
if (continuationAccumulator > 0xffff)
{
char highSurrogate = (char)(_khighOffset + (continuationAccumulator >> 10)),
lowSurrogate = (char)(0xDC00 + (continuationAccumulator & 0x3FF));
result.Add(highSurrogate);
result.Add(lowSurrogate);
}
else
{
result.Add((char)(continuationBase | continuationAccumulator));
}
continuationAccumulator = 0;
continuationBase = '\0';
highBase = 0;
}
continue;
}
if (b < 0xE0)
{
continuationCount = 1;
continuationBase = (char)((b - 0xC0) * 0x0040);
continue;
}
if (b < 0xF0)
{
continuationCount = 2;
continuationBase = (char)(b == 0xE0 ? 0x0800 : (b - 0xE0) * 0x1000);
continue;
}
if (b < 0xF8)
{
continuationCount = 3;
highBase = (b - 0xF0) * 0x00040000;
continue;
}
if (b < 0xFC)
{
continuationCount = 4;
highBase = (b - 0xF8) * 0x01000000;
continue;
}
if (b < 0xFE)
{
continuationCount = 5;
highBase = (b - 0xFC) * 0x40000000;
continue;
}
throw new ArgumentException("invalid encoding");
}
return new string(result.ToArray());
}
我测试了您提供的地球字符,它可以正常工作。它也能正确解码该字符的UTF8编码(即F0 9F 8C 8E
)。当然,如果您打算使用该代码来解码所有UTF8输入,您必须测试它以覆盖全部数据范围。
0xD83C 0xDF0E
,而不是你所说的0xD83D 0xDF0E
。此外,如果我使用.NET将该字符编码为UTF8,我得到的是F0 9F 8C 8E
,而不是你所说的ED A0 BC ED BC 8E
。最后,当我将F0 9F 8C 8E
解码回C#字符串时,我得到了我开始的""
,并且它以UTF16编码为原始的0xD83C 0xDF0E
,正如预期的那样。请提供一个好的[mcve],可靠地重现您的问题。目前,这看起来只是您的代码转换为UTF8的问题(它看起来根本不像C#...似乎是C++)。 - Peter DunihoEncoding.UTF8.GetString
正确地用U+FFFD
替换无效字节。你所看到的类似于CESU-8。 - 一二三