Это невозможно знать наверняка без хорошего Minimal, Complete, and Verifiable code example. Но мне кажется, что вы используете неправильный конвертер в C++.
std::codecvt_utf8<wchar_t>
язык изменен из 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
. Разумеется, они правильно декодируются в .NET при интерпретации UTF-8.
Добавление:
Вопрос был обновлен, чтобы указать, что код кодирования не может быть изменен. Вы застряли в UCS-2, который был закодирован в недопустимый UTF8. Поскольку UTF8 недействителен, вам придется декодировать текст самостоятельно.
Я вижу пару разумных способов сделать это. Во-первых, напишите декодер, который не заботится о том, включает ли UTF8 недопустимые байтовые последовательности. Во-вторых, используйте конвертер C++ std::wstring_convert<std::codecvt_utf8<wchar_t>>
для декодирования байтов для вас (например, напишите свой код приема на C++ или напишите C++ DLL, который вы можете вызвать из своего кода C#, чтобы выполнить эту работу).
Второй вариант в некотором смысле более надежный, то есть вы используете именно декодер, который создал плохие данные в первую очередь. С другой стороны, это может быть чрезмерным даже для создания DLL, но не стоит писать весь клиент на C++. Создавая DLL, даже используя C++/CLI, у вас все еще есть некоторые головные боли, позволяющие взаимодействию работать правильно, если вы уже не являетесь экспертом.
Я знаком, но вряд ли экспертом, с C++/CLI. Я гораздо лучше с C#, так вот некоторый код для первого варианта:
private const int _khighOffset = 0xD800 - (0x10000 >> 10);
/// <summary>
/// Decodes a nominally UTF8 byte sequence as UTF16. Ignores all data errors
/// except those which prevent coherent interpretation of the input data.
/// Input with invalid-but-decodable UTF8 sequences will be decoded without
/// error, and may lead to invalid UTF16.
/// </summary>
/// <param name="bytes">The UTF8 byte sequence to decode</param>
/// <returns>A string value representing the decoded UTF8</returns>
/// <remarks>
/// This method has not been thoroughly validated. It should be tested
/// carefully with a broad range of inputs (the entire UTF16 code point
/// range would not be unreasonable) before being used in any sort of
/// production environment.
/// </remarks>
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)
{
// Byte values in this range are used only as continuation bytes.
// If we aren't expecting any continuation bytes, then the input
// is invalid beyond repair.
if (continuationCount == 0)
{
throw new ArgumentException("invalid encoding");
}
// Each continuation byte represents 6 bits of the actual
// character value
continuationAccumulator <<= 6;
continuationAccumulator |= (b - 0x80);
if (--continuationCount == 0)
{
continuationAccumulator += highBase;
if (continuationAccumulator > 0xffff)
{
// Code point requires more than 16 bits, so split into surrogate pair
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;
}
// byte values of 0xFE and 0xFF are invalid
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++). –
Суррогатные коды * не могут быть закодированы в UTF-8 (или любой UTF), поэтому 'Encoding.UTF8.GetString' корректно заменяет недопустимые байты на' U + FFFD'. Как вы выглядите [CESU-8] (http://www.unicode.org/reports/tr26/). –
@PeterDuniho: Персонажи исправлены извините. Я добавил образец и пояснил, что я больше не контролирую производственную программу. – Jonathan