2016-07-10 6 views
1

Mein C# -Programm ruft einige UTF-8-codierte Daten ab und dekodiert sie mit Encoding.UTF8.GetString(data). Wenn das Programm, das die Daten erzeugt, Zeichen außerhalb des BMP erhält, codiert es sie als 2 Ersatzzeichen, von denen jedes separat als UTF-8 kodiert wird. In solchen Fällen kann mein Programm sie nicht richtig dekodieren.Wie werden Ersatzzeichen dekodiert, die als UTF8 codiert sind?

Wie kann ich solche Daten in C# dekodieren?

Beispiel:

static void Main(string[] args) 
{ 
    string orig = ""; 
    byte[] correctUTF8 = Encoding.UTF8.GetBytes(orig); // Simulate correct conversion using std::codecvt_utf8_utf16<wchar_t> 
    Console.WriteLine("correctUTF8: " + BitConverter.ToString(correctUTF8)); // F0-9F-8C-8E - that's what the C++ program should've produced 

    // Simulate bad conversion using std::codecvt_utf8<wchar_t> - that's what I get from the program 
    byte[] badUTF8 = new byte[] { 0xED, 0xA0, 0xBC, 0xED, 0xBC, 0x8E }; 
    string badString = Encoding.UTF8.GetString(badUTF8); // ���� (4 * U+FFFD 'REPLACMENT CHARACTER') 
    // How can I convert this? 
} 

Anmerkung: Das Codierungsprogramm ist in C++ geschrieben ist, und wandelt die Daten unter Verwendung von std::codecvt_utf8<wchar_t> (Code unten). Wie @ PeterDuniho's Antwort richtig bemerkt, sollte es std::codecvt_utf8_utf16<wchar_t> verwendet haben. Leider, Ich kontrolliere dieses Programm nicht, und kann sein Verhalten nicht ändern - behandeln Sie nur seine fehlerhafte Eingabe.

std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8Converter; 
std::string utf8str = utf8Converter.to_bytes(wstr); 
+2

Ich bekomme '0xD83C 0xDF0E' für das Zeichen, nicht' 0xD83D 0xDF0E', wie Sie behaupten. Außerdem, wenn ich .NET verwende, um dieses Zeichen als UTF8 zu kodieren, bekomme ich 'F0 9F 8C 8E', nicht 'ED A0 BC ED BC 8E', wie Sie behaupten. Schließlich, wenn ich 'F0 9F 8C 8E' zurück zu einer C# Zeichenkette decodiere, bekomme ich das' '' 'Ich begann mit, und das kodiert in UTF16 als das ursprüngliche' 0xD83C 0xDF0E', genau wie erwartet. Bitte stellen Sie eine gute [mcve] bereit, die Ihr Problem zuverlässig reproduziert. Im Moment scheint das nichts anderes zu sein als ein Problem mit der Konvertierung von Code in UTF8 (die nicht wie C# aussieht ... es scheint C++ zu sein). –

+2

Ersatzcodepunkte * können nicht in UTF-8 (oder einem beliebigen UTF) codiert werden, daher ersetzt 'Encoding.UTF8.GetString' die ungültigen Bytes durch' U + FFFD'. Wie Sie aussehen [CESU-8] (http://www.unicode.org/reports/tr26/). –

+0

@PeterDuniho: Zeichen korrigiert sorry. Ich fügte eine Probe hinzu und stellte klar, dass ich das Produktionsprogramm nicht mehr kontrolliere. – Jonathan

Antwort

3

Es ist unmöglich, ohne ein gutes Minimal, Complete, and Verifiable code example sicher zu wissen. Aber es sieht für mich so aus, als ob Sie den falschen Konverter in C++ verwenden.

Das Gebietsschema std::codecvt_utf8<wchar_t> konvertiert von UCS-2, nicht UTF-16. Die beiden sind sehr ähnlich, aber UCS-2 unterstützt keine Ersatzpaare, die zum Codieren des zu codierenden Zeichens erforderlich wären.

Stattdessen sollten Sie std::codecvt_utf8_utf16<wchar_t> werden:

std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> utf8Converter; 
std::string utf8str = utf8Converter.to_bytes(wstr); 

Als ich den Konverter verwenden, erhalte ich die UTF-8-Bytes benötigt: F0 9F 8C 8E. Diese dekodieren natürlich in .NET korrekt, wenn sie als UTF-8 interpretiert werden.


Nachtrag:

Die Frage wurde aktualisiert, um anzuzeigen, dass die Code-Codierung kann nicht geändert werden. Sie stecken mit UCS-2 fest, das in ungültiges UTF8 codiert wurde. Da UTF8 ungültig ist, müssen Sie den Text selbst dekodieren.

Ich sehe ein paar vernünftige Möglichkeiten, dies zu tun. Schreiben Sie zuerst einen Decoder, der sich nicht darum kümmert, ob der UTF8 ungültige Bytefolgen enthält. Zweitens, verwenden Sie den C++ std::wstring_convert<std::codecvt_utf8<wchar_t>> Konverter, um die Bytes für Sie zu dekodieren (z. B. schreiben Sie Ihren Empfangscode in C++ oder schreiben Sie eine C++ DLL, die Sie aus Ihrem C# -Code aufrufen können, um die Arbeit zu erledigen).

Die zweite Option ist in gewissem Sinne die zuverlässigere, d. H. Sie verwenden genau den Decoder, der die schlechten Daten überhaupt erstellt hat. Auf der anderen Seite könnte es sogar zu übertrieben sein, eine DLL zu erstellen, geschweige denn den gesamten Client in C++ schreiben. Wenn Sie eine DLL erstellen, sogar mit C++/CLI, haben Sie immer noch einige Probleme damit, die Interoperabilität zu verbessern, es sei denn, Sie sind bereits ein Experte.

Ich bin vertraut, aber kaum ein Experte, mit C++/CLI. Ich bin viel besser mit C#, also hier ist ein Code für die erste Option:

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

ich es mit Ihrem Globus Charakter getestet und es funktioniert für das in Ordnung. Es dekodiert auch richtig das UTF8 für dieses Zeichen (d. H. F0 9F 8C 8E).Natürlich möchten Sie es mit einer ganzen Reihe von Daten testen, wenn Sie diesen Code für die Decodierung all Ihrer UTF8-Eingaben verwenden möchten.

+0

Danke, das ist in der Tat der richtige Code für das Produzentenprogramm. Leider kontrolliere ich es nicht, also suche ich nach einer Lösung in der C# -Seite, um dieses Verhalten zu kompensieren. – Jonathan

+0

Siehe Bearbeiten. Ich habe mir nicht die Mühe gemacht, einen C++/CLI-Decoder zu schreiben, weil das viel länger dauern würde, und 95% der Zeit würde ich mit Dingen ringen, die nichts mit der eigentlichen Frage zu tun haben. :) –

+0

Danke, das ist die Antwort, nach der ich gesucht habe, obwohl ich auf einen fertigen Decoder im .NET Framework oder eine wohlbekannte Bibliothek gehofft hatte ... Ich bin mit C++/CLI vertraut, aber es würde allein eine ungerechtfertigte Investition in unser Build-System erfordern, geschweige denn die zusätzliche DLL. Sie, Herr, sind ein Gentleman, ein Gelehrter und ein Prinz unter Männern! – Jonathan

Verwandte Themen