Unicode in RAD Studio

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Einführung in RAD Studio


In RAD Studio werden Unicode-basierte Strings verwendet: der Typ string ist nun ein Unicode-String (System.UnicodeString) anstatt eines ANSI-Strings. Dieses Thema beschreibt die korrekte Behandlung von Strings.

Mit den Typen AnsiString und WideString werden ANSI- bzw. Wide-Strings verwendet.

RAD Studio ist vollständig Unicode-kompatibel, und für diejenigen Teile Ihres Quelltextes, die die String-Behandlung betreffen, sind einige Änderungen möglicherweise erforderlich. Es wurde aber versucht, diese Änderungen auf ein Minimum zu beschränken. Es wurden zwar neue Datentypen eingeführt, aber vorhandene Datentypen wurden beibehalten und arbeiten genauso wie bisher. Ausgehend von dem mit der Unicode-Konvertierung im eigenen Haus gemachten Erfahrungen, sollten vorhandene Anwendungen ziemlich reibungslos migriert werden können.

Weitere Ressourcen:

Vorhandene String-Typen

Die bereits vorhandenen Datentypen AnsiString und System.WideString arbeiten genauso wie bisher.

Bei kurzen Strings gibt es hinsichtlich der Funktionalität ebenfalls keine Änderungen. Beachten Sie bitte, dass kurze Strings auf 255 Zeichen beschränkt sind und nur einen Zeichenzähler und Einzelbyte-Zeichendaten enthalten. Sie umfassen keine Codeseiteninformationen. Ein kurzer String kann in Ausnahmenfällen UTF-8-Daten für eine bestimmte Anwendung enthalten.

AnsiString

string war früher ein Alias für AnsiString. Die folgende Tabelle zeigt die Position der Felder im früheren Format von AnsiString:

Früheres Format des Datentyps AnsiString:

Referenzzähler Länge String-Daten (auf Byte-Größe) Nullterminiert
-8
-4
0
Length

In RAD Studio wurde das Format von AnsiString geändert. Zwei neue Felder (CodePage und ElemSize) wurden hinzugefügt. Dadurch ist das Format von AnsiString identisch mit dem neuen Typ UnicodeString. (Weitere Informationen zum neuen Format finden Sie unter Lange String-Typen.)

WideString

System.WideString wurde früher für Unicode-Zeichendaten verwendet. Das Format von WideString entspricht im Wesentlichen dem von Windows BSTR. WideString sollte weiterhin in COM-Anwendungen verwendet werden.

Neuer String-Typ: UnicodeString

Der string-Typ in RAD Studio ist der Typ UnicodeString.

In Delphi sind Char- und PChar-Typen jetzt WideChar- bzw. PWideChar-Typen.

Hinweis: Dies ist ein Unterschied zu Versionen vor 2009. In früheren Versionen war string ein Alias für AnsiString, und die Typen Char und PChar waren AnsiChar bzw. PAnsiChar.

In C++ steuert die Option _TCHAR entspricht die Definition von _TCHAR, das entweder wchar_t oder char sein kann.

RAD Studio Frameworks und Bibliotheken verwenden jetzt den Typ UnicodeString und repräsentiert String-Werte nicht mehr als Einzelbyte- oder MBCS-Strings.

Format des Datentyps UnicodeString:

Codeseite Elementgröße Referenzzähler Länge String-Daten (auf Elementgröße) Nullterminiert
-12
-10
-8
-4
0
Length * elementsize


UnicodeString kann als die folgende Delphi-Struktur dargestellt werden:

type StrRec = record
      CodePage: Word;
      ElemSize: Word;
      refCount: Integer;
      Len: Integer;
      case Integer of
          1: array[0..0] of AnsiChar;
          2: array[0..0] of WideChar;
end;

UnicodeString fügt die Felder für die Codeseite CodePage und die Elementgröße ElemSize hinzu, die den Inhalt des Strings beschreiben. UnicodeString ist zu allen anderen String-Typen zuweisungskompatibel. Zuweisungen zwischen AnsiString und UnicodeString führen aber weiterhin die entsprechenden Auf- oder Abkonvertierungen durch. Bitte beachten Sie, dass eine Zuweisung eines UnicodeString-Typs zu einem AnsiString-Typ nicht empfohlen wird und zu Datenverlusten führen kann.

Beachten Sie, dass AnsiString ebenfalls über die Felder CodePage und ElemSize verfügt.

UnicodeString-Daten sind aus folgenden Gründen in UTF-16 codiert:

  • UTF-16 entspricht dem zugrunde liegenden Format des Betriebssystems.
  • UTF-16 reduziert zusätzliche explizite/implizite Konvertierungen.
  • Es bietet bei Aufrufen der Windows-API eine bessere Performanz.
  • Es müssen keine Betriebssystem Konvertierungen mit UTF-16 durchgeführt werden.
  • Der Basic Multilingual Plane (BMP) enthält bereits die Schriftzeichen fast aller aktiven Sprachen der Welt und passt in ein einzelnes UTF-16 Char (16 Bits).
  • Unicode-Surrogatpaare entsprechen dem Multibyte-Zeichensatz (MBCS), sind aber berechenbarer und Standard.
  • UnicodeString kann für das Marshalling von COM-Interfaces verlustlose implizite Konvertierungen in und aus WideString bereitstellen.

Zeichen in UTF-16 können 2 oder 4 Byte groß sein, daher ist die Anzahl der Elemente in einem String nicht notwendigerweise gleich der Anzahl der Zeichen. Wenn der String nur aus BMP-Zeichen besteht, ist die Anzahl der Zeichen und der Elemente identisch.

UnicodeString bietet die folgenden Vorteile:

  • UnicodeString verfügt über einen Referenzzähler.
  • UnicodeString löst ein Problem von Altanwendungen in C++Builder.
  • Durch die Aufnahme von Codierungsinformationen (Codeseite) in AnsiString wird das Risiko eines möglichen Datenverlustes bei impliziten Typumwandlungen reduziert.
  • Der Compiler stellt sicher, dass die Daten korrekt sind, bevor die Daten verändert werden.

WideString verfügt über keinen Referenzzähler, deshalb ist UnicodeString für die meisten Anwendungstypen flexibler und effizienter (WideString eignet sich eher für COM).

Indizierung

Instanzen von UnicodeString können Zeichen indizieren. Die Indizierung ist 1-basiert, genau wie bei AnsiString. Sehen Sie sich den folgenden Quelltext an:

var C: Char;
    S: string;
    begin
        ...
        C := S[1];
        ...
    end;

In einem Fall, wie dem oben dargestellten, muss der Compiler sicher stellen, dass die Daten in S in einem geeigneten Format vorliegen. Der Compiler erzeugt entsprechenden Code, damit Zuweisungen zu String-Elementen den richtigen Typ haben und die Instanz eindeutig ist (d.h., dass sie einen Referenzzähler von 1 hat). Dies geschieht über einen Aufruf der Funktion UniqueString. Für den obigen Quelltext muss der Compiler, weil der String Unicode-Daten enthalten könnte, vor der Indizierung im Zeichen-Array auch die passende UniqueString-Funktion aufrufen.


Compilerbedingungen

In Delphi als auch in C++Builder können Sie Bedingungen setzen, um im selben Quelltext Unicode- und Nicht-Unicode-Code zuzulassen.

Delphi

{$IFDEF UNICODE}

C++Builder

#ifdef _DELPHI_STRING_UNICODE 

Überblick über die Änderungen

  • string wird nun UnicodeString und nicht AnsiString zugeordnet.
  • Char wird nun WideChar (2 Byte, nicht 1 Byte) zugeordnet und ist ein UTF-16-Zeichen.
  • PChar wird nun PWideChar zugeordnet.
  • In C++ wird System::String nun der Klasse UnicodeString zugeordnet.

Überblick über Unverändertes

  • AnsiString.
  • WideString.
  • AnsiChar, PAnsiChar.
  • WideChar, PWideChar
  • Implizite Konvertierungen funktionieren weiterhin.
  • AnsiString verwendet die aktive Codeseite des Benutzers.

Von der Zeichengröße unabhängige Quelltextkonstrukte

Die folgenden Operationen sind von der Zeichengröße unabhängig:

  • String-Verkettung:
    • <string var> + <string var>
    • <string var> + <literal>
    • <literal> + <literal>
    • Concat(<string> , <string>)
  • Standard-String-Funktionen:
    • Length(<string>) gibt die Anzahl der char-Elemente zurück. Diese Anzahl muss nicht unbedingt gleich der Anzahl der Byte sein. Beachten Sie bitte, dass die Funktion SizeOf die Anzahl der Byte zurückgibt, was bedeutet, dass sich der Rückgabewert für SizeOf von Length unterscheiden kann.
    • Copy(<String>, <Start>, <Länge>) gibt einen Teilstring in Char-Elementen zurück.
    • Pos(<Teilstring>, <String>) gibt den Index des ersten Char-Elements zurück.
  • Operatoren:
    • <String> <Vergleichsoperator> <String>
    • CompareStr()
    • CompareText()
    • ...
  • FillChar(<Struktur oder Arbeitsspeicher>)
    • FillChar(Rect, SizeOf(Rect), #0)
    • FillChar(WndClassEx, SizeOf(TWndClassEx), #0). Bitte beachten: WndClassEx.cbSize := SizeOf(TWndClassEx);
  • Windows-API
    • Bei API-Aufrufen werden standardmäßig deren WideString-(“W”)-Versionen verwendet.
    • Die PChar(<String>)-Typumwandlung hat eine identische Semantik.


Beispiel für GetModuleFileName:

function ModuleFileName(Handle: HMODULE): string;
    var Buffer: array[0..MAX_PATH] of Char;
        begin
            SetString(Result, Buffer, 
                      GetModuleFileName(Handle, Buffer, Length(Buffer)));
        end;

Beispiel für GetWindowText:

function WindowCaption(Handle: HWND): string;
      begin
          SetLength(Result, 1024);
          SetLength(Result, 
                    GetWindowText(Handle, PChar(Result), Length(Result)));
      end;

Beispiel für die Zeichenindizierung von Strings:

function StripHotKeys(const S: string): string;
    var I, J: Integer;
    LastChar: Char;
    begin
        SetLength(Result, Length(S));
        J := 0;
        LastChar := #0;
        for I := 1 to Length(S) do
        begin
          if (S[I] <> '&') or (LastChar = '&') then
          begin
              Inc(J);
              Result[J] := S[I];
          end;
          LastChar := S[I];
    end;
    SetLength(Result, J);
end;

Von der Zeichengröße abhängige Quelltextkonstrukte

Einige Operationen sind von der Zeichengröße abhängig. Die Funktionen und Features in der folgenden Liste enthalten - sofern möglich - auch eine “portierbare” Version. Sie können Ihren Quelltext auch in der portierbaren Version neu schreiben, er funktioniert sowohl mit AnsiString- als auch mit UnicodeString-Variablen.

  • SizeOf(<Char-Array>) - portierbare Version: Length(<Char-Array>).
  • Move(<Char-Puffer>... CharCount) - portierbare Version: Move(<Char-Puffer> ... CharCount * SizeOf(Char)).
  • Stream Read/Write - portierbare Version: AnsiString, SizeOf(Char) oder die Klasse TEncoding.
  • FillChar(<Char-Array>, <Größe>, <AnsiChar>) - verwenden Sie zum Auffüllen mit #0*SizeOf(Char) oder die portierbare StringOfChar-Funktion.
  • GetProcAddress(<Modul>, <PAnsiChar>) - verwenden Sie die überladene Funktion, die ein PWideChar übernimmt.
  • Typumwandlung oder die Verwendung von PChar für die Zeigerarithmetik - fügen Sie am Beginn der Datei {IFDEF PByte = PChar} ein, wenn Sie PChar für die Zeigerarithmetik verwenden. Oder verwenden Sie die Delphi-Compiler-Direktive {POINTERMATH <ON|OFF>}, um die Zeigerarithmetik für alle typisierten Zeiger zu aktivieren, damit das Inkrementieren/Dekrementieren nach Elementgröße ausgeführt wird.

Set of Char-Konstrukte

Sie müssen diese Konstrukte wahrscheinlich ändern.

  • <Char> in <set of AnsiChar> - Codeerzeugung ist korrekt (>#255-Zeichen sind nie in der Menge enthalten). Der Compiler gibt die Warnung WideChar in Set-Operationen verkürzt aus. Abhängig von Ihrem Quelltext können Sie die Warnung gefahrlos deaktivieren. Verwenden Sie als Alternative die Funktion CharinSet.
  • <Char> in LeadBytes - die globale Menge LeadBytes ist für MBCS-ANSI-Gebietseinstellungen vorgesehen. UTF-16 arbeitet weiterhin mit einem "Führungszeichen" (lead char) (#$D800 - #$DBFF sind hohe Surrogate, #$DC00 - #$DFFF niedrige Surrogate). Verwenden Sie, um dies zu ändern, die überladene Funktion IsLeadChar. Die ANSI-Version prüft auf LeadBytes. Die WideChar-Version prüft, ob es sich um ein hohes/niedriges Surrogat handelt.
  • Zeichenklassifikation - verwenden Sie die statische Klasse TCharacter. Die Unit Character enthält Funktionen für die Klassifikation von Zeichen: IsDigit, IsLetter, IsLetterOrDigit, IsSymbol, IsWhiteSpace, IsSurrogatePair usw. Diese Funktionen basieren auf Tabellendaten der Unicode.org.

Achtung bei diesen Konstrukten

Sie sollten die folgenden problematischen Codekonstrukte untersuchen:

  • Typumwandlungen, die den Typ verbergen:
    • AnsiString(Pointer(foo))
    • Überprüfen Sie, was beabsichtigt war.
  • Verdächtige Typumwandlungen erzeugen eine Warnung:
    • PChar(<AnsiString var>)
    • PAnsiChar(<UnicodeString var>)
  • Direktes Erstellen, Manipulieren oder Zugreifen auf interne Strukturen. Einige dieser Strukturen, wie z.B. AnsiString, wurden intern verändert, daher sind diese Aktionen unsicher. Verwenden Sie die Funktionen StringRefCount, StringCodePage, StringElementSize und andere, um String-Informationen zu ermitteln.

Laufzeitbibliothek

  • Überladungen. Für Funktionen, die PChar übernehmen, gibt es jetzt PAnsiChar- und PWideChar-Versionen, damit die richtige Funktion aufgerufen wird.
  • AnsiXXX-Funktionen sind möglich:
    • SysUtils.AnsiXXXX-Funktionen, wie z.B. AnsiCompareStr:
      • Werden weiterhin mit string deklariert und verwenden UnicodeString.
      • Bieten eine bessere Abwärtskompatibilität (Quelltext muss nicht geändert werden).
    • Die AnsiXXXX-Funktionen der Unit AnsiStrings bieten dieselben Möglichkeiten wie die SysUtils.AnsiXXXX-Funktionen, arbeiten aber nur mit AnsiString. Die AnsiStrings.AnsiXXXX-Funktionen bieten auch eine bessere Performanz für einen AnsiString als die SysUtils.AnsiXXXX-Funktionen, die sowohl mit AnsiString als auch mit UnicodeString arbeiten, weil keine impliziten Konvertierungen ausgeführt werden.
  • Write/Writeln und Read/Readln:
    • Konvertieren weiterhin in/aus ANSI/OEM-Codeseiten.
    • Die Konsole ist meist sowieso ANSI oder OEM.
    • Bieten eine bessere Kompatibilität zu Altanwendungen.
    • TFDD (Gerätetreiber für Textdateien):
      • TTextRec und TFileRec.
      • Dateinamen sind WideChar, aber wie oben sind die Daten ANSI/OEM.
    • Verwenden Sie TEncoding und TStrings für die Unicode-Dateieingabe-/ausgabe.
  • PByte - deklariert mit $POINTERMATH ON. Dadurch wird die Array-Indizierung und die Zeigermathematik wie bei PAnsiChar ermöglicht.
  • Die RTL stellt Hilfsfunktionen bereit, mit denen Anwender explizite Konvertierungen zwischen Codeseiten und Elementgrößenumwandlungen vornehmen können. Wenn Entwickler die Funktion Move für ein Zeichen-Array verwenden, können sie keine Annahme über die Elementgröße machen. Dieses Problem kann reduziert werden, indem sichergestellt wird, dass alle R-Wert-Referenzen korrekte Aufrufe der RTL erzeugen, um korrekte Elementgrößen zu gewährleisten.

Komponenten und Klassen

  • TStrings: Speichert UnicodeString intern (bleibt als string deklariert).
  • TWideStrings (wird eventuell bald nicht mehr unterstützt) ist unverändert. Verwendet WideString (BSTR) intern.
  • TStringStream
    • Wurde neu geschrieben – hat per Vorgabe die ANSI-Standardcodierung für interne Speicherung.
    • Die Codierung kann überschrieben werden.
    • Ziehen Sie zum Erstellen eines Strings aus verschiedenen Teilen TStringBuilder anstelle von TStringStream in Betracht.
  • TEncoding
    • Verwendet standardmäßig die aktive Codeseite des Anwenders.
    • Unterstützt UTF-8.
    • Unterstützt UTF-16, Big und Little Endian.
    • Unterstützt Byte Order Mark (BOM).
    • Sie können für benutzerspezifische Codierungen abgeleitete Klassen erstellen.
  • Komponenten-Streaming (Text-DFM-Dateien):
    • Sind vollständig abwärtskompatibel.
    • Verwenden für das Streaming nur UTF-8, wenn der Komponententyp, die Eigenschaft oder der Name keine ASCII-7-Zeichen enthält.
    • String-Eigenschaftswerte werden weiterhin mit dem “#”-Escaped-Format in den Stream gestellt.
    • Lassen auch Werte als UTF-8 zu (noch nicht geklärtes Problem).
    • Nur die Änderung im Binärformat ist für UTF-8-Daten für Komponentennamen, Eigenschaften und Typnamen möglich.

Byte Order Mark

Das Byte Order Mark (BOM) sollte zur Angabe der Codierung Dateien hinzugefügt werden:

  • UTF-8 verwendet EF BB BF.
  • UTF-16 Little Endian verwendet FF FE.
  • UTF-16 Big Endian verwendet FE FF.

Schritte zum Aktivieren von Unicode in Anwendungen

Sie müssen die folgenden Schritte ausführen:

  1. Überprüfen von Char- und String-bezogenen Funktionen
  2. Neu erzeugen der Anwendung
  3. Überprüfen von Surrogatpaaren
  4. Überprüfen der String-Nutzdaten

Weitere Einzelheiten finden Sie unter Anwendungen für Unicode anpassen.

Neue Warnungen des Delphi-Compilers

Dem Delphi-Compiler wurden neue Warnungen hinzugefügt, die sich auf mögliche Fehler bei der Typumwandlung beziehen (z.B. beim Umwandeln eines UnicodeString- oder WideString-Strings in AnsiString oder AnsiChar). Wenn Sie eine Anwendung nach Unicode konvertieren, sollten Sie die Warnungen 1057 und 1058 aktivieren, die Sie beim Auffinden von Problembereichen in Ihrem Quelltext unterstützen.

  • W1050: WideChar in Set-Ausdrücken auf ByteChar verkürzt (Delphi). "Set of char" definiert in Win32 eine Menge über den gesamten Bereich des Char-Typs. Da Char in Win32 ein Typ auf Byte-Größe ist, wird dadurch eine Menge mit maximaler Größe und mit 256 Elementen definiert. In .NET ist Char ein Typ auf Word-Größe, dessen Bereich (0..65535) die Kapazität des Mengen-Typs überschreitet.
  • W1057: Implizite String-Umwandlung von '%s' zu '%s' (IMPLICIT_STRING_CAST) Wird ausgegeben, wenn der Compiler einen Fall findet, bei dem ein AnsiString (oder ein AnsiChar) in eine Form von Unicode (einen UnicodeString oder einen WideString) implizit konvertiert werden muss. (HINWEIS: Diese Warnung ist eventuell per Vorgabe aktiviert).
  • W1058: Implizite String-Umwandlung mit potenziellem Datenverlust von '%s' zu '%s' (IMPLICIT_STRING_CAST_LOSS) Wird ausgegeben, wenn der Compiler einen Fall findet, bei dem eine Form von Unicode (ein UnicodeString oder ein WideString) in einen AnsiString (oder ein AnsiChar) implizit konvertiert werden muss. Dies ist eine potenziell verlustreiche Umwandlung, weil es Zeichen in dem String geben kann, die auf der Codeseite, in die der String umgewandelt wird, nicht repräsentiert werden können. (HINWEIS: Diese Warnung ist eventuell per Vorgabe aktiviert).
  • W1059: Explizite String-Umwandlung von '%s' zu '%s' (EXPLICIT_STRING_CAST) Wird ausgegeben, wenn der Compiler auf einen Fall trifft, bei dem der Programmierer explizit einen AnsiString (oder ein AnsiChar) in eine Form von Unicode (UnicodeString oder WideString) umwandelt. (HINWEIS: Diese Warnung ist standardmäßig deaktiviert und sollte nur zum Auffinden eventueller Probleme verwendet werden.)
  • W1060: Explizite String-Umwandlung mit potenziellem Datenverlust von '%s' zu '%s' (EXPLICIT_STRING_CAST_LOSS) Wird ausgegeben, wenn der Compiler einen Fall findet, bei dem der Programmierer explizit eine Form von Unicode (UnicodeString oder WideString) in einen AnsiString (oder ein AnsiChar) konvertiert. Dies ist eine potenziell verlustreiche Umwandlung, weil es Zeichen in dem String geben kann, die auf der Codeseite, in die der String umgewandelt wird, nicht repräsentiert werden können. (HINWEIS: Diese Warnung ist standardmäßig deaktiviert und sollte nur zum Auffinden eventueller Probleme verwendet werden.)

Empfehlungen

  • Belassen Sie die Quelltextdaten im UTF-8-Format:
    • Dateien können im ANSI-Format bleiben, wenn der Quelltext mit der korrekten Codeseite compiliert wird. Wählen Sie (Projekt > Optionen > C++-Compiler > Erweitert und verwenden Sie die Option "Codeseite" unter Weitere Optionen, um die korrekte Codeseite festzulegen.
    • Schreiben Sie in die Quelltextdatei ein UTF-8 BOM. Stellen Sie sicher, dass Ihr Versionsverwaltungssystem diese Dateien unterstützt.
  • Führen Sie in der IDE ein Refactoring durch, wenn der Quelltext AnsiString oder AnsiChar sein muss (der Quelltext ist weiterhin portierbar).
  • Statische Überprüfung des Quelltextes:
    • Gibt der Quelltext Daten nur weiter?
    • Führt der Quelltext eine einfache Zeichenindizierung durch?
  • Beachten Sie alle Warnungen (es könnten Fehler daraus werden):
    • Verdächtige Zeigerumwandlungen.
    • Implizite/explizite Umwandlungen.
  • Bestimmen Sie den Zweck des Quelltextes.
    • Verwendet der Quelltext einen String (AnsiString) als ein dynamisches Byte-Array? Wenn ja, dann setzen Sie stattdessen den portierbaren Typ TBytes (Byte-Array) ein.
    • Wird mit einer PChar-Umwandlung die Zeigerarithmetik aktiviert? Wenn ja, wandeln Sie stattdessen in PByte um, und aktivieren Sie $POINTERMATH ON.

Siehe auch