Anzeigen: Delphi C++
Anzeigeeinstellungen

Anwendungen für Unicode anpassen

Aus RAD Studio XE2
Wechseln zu: Navigation, Suche

Nach oben zu Compilieren, Build erstellen und Anwendungen ausführen - Index

Dieses Thema beschreibt einige semantische Codekonstrukte, die Sie in vorhandenem Quelltext überprüfen sollten, um sicherzustellen, dass Ihre Anwendungen mit dem Typ UnicodeString kompatibel sind. Da Char jetzt WideChar entspricht, und für String per Vorgabe UnicodeString verwendet wird, könnten frühere Annahmen über die Größe in Byte eines Zeichen-Arrays oder Strings inkorrekt sein.

Allgemeine Informationen über Unicode finden Sie unter Unicode in RAD Studio

Inhaltsverzeichnis

Einrichten der Umgebung für die Migration nach Unicode

Suchen Sie nach Quelltext, der:

  • davon ausgeht, dass SizeOf(Char) 1 ist.
  • davon ausgeht, dass die Länge (Length) eines Strings gleich der Byte-Anzahl in dem String ist.
  • Strings oder PChars direkt manipuliert.
  • Strings in oder aus einem persistenten Speicher liest oder schreibt.

Die beiden ersten Annahmen sind für Unicode nicht zutreffend, weil bei Unicode SizeOf(Char) größer als 1 Byte sein kann, und die Länge (Length) eines Strings doppelt so groß ist wie die Anzahl der Bytes. Darüber hinaus muss Code, der in einen persistenten Speicher schreibt oder daraus liest, gewährleisten, dass die korrekte Anzahl von Bytes geschrieben oder gelesen wird, da ein Zeichen evtl. nicht mehr durch ein einzelnes Byte repräsentiert werden kann.

Compiler-Flags

Es wurden Flags hinzugefügt, damit Sie festlegen können, ob der Standard-String-Typ ein UnicodeString oder ein AnsiString ist. Damit lässt sich Code beibehalten, der ältere Versionen von Delphi und C++Builder in demselben Quelltext unterstützt. Für den überwiegenden Teil von Quelltext, der Standardoperationen mit Strings durchführt, dürften zwei separate UnicodeString- und AnsiString-Codeabschnitte nicht erforderlich sein. Wenn eine Prozedur jedoch Operationen ausführt, die von der internen Struktur der String-Daten abhängen oder die mit externen Bibliotheken interagieren, könnten separate Codepfade für UnicodeString und AnsiString nötig sein.

Delphi

{$IFDEF UNICODE}

C++

#ifdef _DELPHI_STRING_UNICODE

Compiler-Warnungen

Der Delphi-Compiler gibt Warnungen über Fehler bei der Typumwandlung (z.B. von UnicodeString oder WideString nach AnsiString oder AnsiChar) aus. Wenn Sie eine Anwendung für Unicode konvertieren, sollten Sie die Warnungen 1057 und 1058 aktivieren, um die problematischen Bereiche in Ihrem Quelltext zu finden.


Warnungsnummer Warnungstext/name

Fehler 1057

Implizite String-Umwandlung von '%s' zu '%s' (IMPLICIT_STRING_CAST)

Fehler 1058

Implizite String-Umwandlung mit potenziellem Datenverlust von '%s' zu '%s' (IMPLICIT_STRING_CAST_LOSS)

Fehler 1059

Explizite String-Umwandlung von '%s' zu '%s' (EXPLICIT_STRING_CAST)

Fehler 1060

Explizite String-Umwandlung mit potenziellem Datenverlust von '%s' zu '%s' (EXPLICIT_STRING_CAST_LOSS)


  • Die Delphi-Compiler-Warnungen aktivieren Sie auf der Seite Projekt > Optionen > Compiler-Meldungen.
  • Die C++-Compiler-Warnungen aktivieren Sie auf der Seite Projekt > Optionen > C++-Compiler > Warnungen.

Zu überprüfende Codebereiche

Aufrufe von SizeOf

Überprüfen Sie die Aufrufe von SizeOf für Zeichen-Arrays. Sehen Sie sich das folgende Beispiel an:


var
  Count: Integer;
  Buffer: array[0..MAX_PATH - 1] of Char;
begin
  // Vorhandener Code ist falsch, wenn String UnicodeString ist
  Count := SizeOf(Buffer);
  GetWindowText(Handle, Buffer, Count);

  // Richtig für Unicode
  Count := Length(Buffer); // <<-- Count muss Zeichen nicht Byte sein
  GetWindowText(Handle, Buffer, Count);
end;

SizeOf gibt die Größe des Array in Byte zurück, aber GetWindowText erwartet, dass Count Zeichen sind. Verwenden Sie in diesem Fall Length anstelle von SizeOf. Length arbeitet ähnlich für Arrays und Strings. Bei einem Array gibt Length die Anzahl der Array-Elemente zurück, die dem Array zugewiesen sind; bei String-Typen gibt Length die Anzahl der Zeichen in dem String zurück.

Um die Anzahl der Zeichen in einem nullterminierten String (PAnsiChar oder PWideChar) zu erhalten, verwenden Sie die Funktion </code>StrLen</code>.

Aufrufe von FillChar

Überprüfen Sie die Aufrufe von FillChar, wenn es zusammen mit Strings oder Char verwendet wird. Sehen Sie sich folgenden Quelltext an:

var
  Count: Integer;
  Buffer: array[0..255] of Char;
begin
   // Vorhandener Code ist falsch, wenn String UnicodeString ist (wenn char = 2 Byte)
   Count := Length(Buffer);
   FillChar(Buffer, Count, 0);

   // Richtig für Unicode
   Count := Length(Buffer) * SizeOf(Char); // <<-- Geben Sie die Puffergröße in Byte an
   FillChar(Buffer, Count, 0);
end;

Length gibt die Größe in Zeichen zurück, aber FillChar erwartet, dass Count Bytes sind. In diesem Beispiel sollte SizeOf anstelle von Length verwendet werden (oder Length mit der Größe von Char multipliziert werden). Außerdem füllt FillChar - weil die Standardgröße eines Char jetzt 2 ist - den String mit Bytes, nicht wie früher mit Char. Zum Beispiel:


var
  Buf: array[0..32] of Char;
begin
  FillChar(Buf, Length(Buf), #9);
end;

Dieser Code füllt das Array allerdings nicht mit Codepoint $09, sondern mit Codepoint $0909. Um das erwartete Ergebnis zu erhalten, muss der Code folgendermaßen verändert werden:


var
  Buf: array[0..32] of Char;
begin
  StrPCopy(Buf, StringOfChar(#9, Length(Buf)));
...
end;

Aufrufe von Move

Überprüfen Sie die Aufrufe von Move mit Strings oder Zeichen-Arrays, wie im folgenden Beispiel:


var
   Count: Integer;
   Buf1, Buf2: array[0..255] of Char;
begin
  // Vorhandener Code ist falsch, wenn String UnicodeString ist (wenn char = 2 Byte)
  Count := Length(Buf1);
  Move(Buf1, Buf2, Count);

  // Richtig für Unicode
  Count := Length(Buf1) * SizeOf(Char); // <<-- Geben Sie die Puffergröße in Byte an
  Move(Buf1, Buf2, Count);
end;

Length gibt die Größe in Zeichen zurück, aber Move erwartet, dass Count Bytes sind. In diesem Fall sollte SizeOf anstelle von Length verwendet werden (oder Length mit der Größe von Char multipliziert werden).

Aufrufe von Read/ReadBuffer-Methoden von TStream

Überprüfen Sie die Aufrufe von TStream.Read/ReadBuffer, wenn Strings oder Zeichen-Arrays verwendet werden. Sehen Sie sich das folgende Beispiel an:


var
  S: string;
  L: Integer;
  Stream: TStream;
  Temp: AnsiString;
begin
  // Vorhandener Code ist falsch, wenn String UnicodeString ist
  Stream.Read(L, SizeOf(Integer));
  SetLength(S, L);
  Stream.Read(Pointer(S)^, L);

  // Richtig für Unicode-String-Daten
  Stream.Read(L, SizeOf(Integer));
  SetLength(S, L);
  Stream.Read(Pointer(S)^, L * SizeOf(Char));  // <<-- Geben Sie die Puffergröße in Byte an

  // Richtig für Ansi-String-Daten
  Stream.Read(L, SizeOf(Integer));
  SetLength(Temp, L);              // <<-- Temporären AnsiString verwenden
  Stream.Read(Pointer(Temp)^, L * SizeOf(AnsiChar));  // <<-- Geben Sie die Puffergröße in Byte an
  S := Temp;                       // <<-- String auf Unicode erweitern
end;

Die Lösung hängt vom Format der zu lesenden Daten ab. Die Klasse TEncoding unterstützt Sie bei der korrekten Codierung von Stream-Text.

Aufrufe von Write/WriteBuffer-Methoden von TStream

Überprüfen Sie die Aufrufe von TStream.Write/WriteBuffer, wenn Strings oder Zeichen-Arrays verwendet werden. Sehen Sie sich das folgende Beispiel an:


var
  S: string;
  Stream: TStream;
  Temp: AnsiString;
  L: Integer;
begin
  L := Length(S);
  // Vorhandener Code 
  // ist falsch, wenn String UnicodeString ist
  Stream.Write(L, SizeOf(Integer)); // String-Länge eingeben
  Stream.Write(Pointer(S)^, Length(S));

  // Richtig für Unicode-Daten
  Stream.Write(L, SizeOf(Integer));
  Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // <<-- Geben Sie die Puffergröße in Byte an

  // Richtig für Ansi-Daten
  Stream.Write(L, SizeOf(Integer));
  Temp := S;          // <<-- Temporären AnsiString verwenden
  Stream.Write(Pointer(Temp)^, Length(Temp) * SizeOf(AnsiChar));// <<-- Geben Sie die Puffergröße in Byte an
end;

Die korrekte Code hängt vom Format der zu schreibenden Daten ab. Die Klasse TEncoding unterstützt Sie bei der korrekten Codierung von Stream-Text.

Aufrufe von GetProcAddress

Aufrufe der Windows-API-Funktion GetProcAddress sollten immer PAnsiChar verwenden, weil es in der Windows-API keine analoge Wide-Funktion gibt. Diese Beispiel zeigt die korrekte Verwendung:


procedure CallLibraryProc(const LibraryName, ProcName: string);
var
  Handle: THandle;
  RegisterProc: function: HResult stdcall;
begin
  Handle := LoadOleControlLibrary(LibraryName, True);
  @RegisterProc := GetProcAddress(Handle, PAnsiChar(AnsiString(ProcName)));
end;

Aufrufe von RegQueryValueEx

In RegQueryValueEx erhält der Parameter Len die Anzahl der Bytes (nicht der Zeichen) und gibt diese auch zurück. In der Unicode-Version ist daher für den Parameter Len ein doppelt so großer Wert erforderlich.

Hier ist ein Beispiel für einen RegQueryValueEx-Aufruf:

Len := MAX_PATH;
if RegQueryValueEx(reg, PChar(Name), nil, nil, PByte(@Data[0]), @Len) = ERROR_SUCCESS
then
  SetString(Result, Data, Len - 1) // Len schließt #0 ein
else
  RaiseLastOSError;


Dieser Code muss folgendermaßen geändert werden:


Len := MAX_PATH * SizeOf(Char);
if RegQueryValueEx(reg, PChar(Name), nil, nil, PByte(@Data[0]), @Len) = ERROR_SUCCESS
then
  SetString(Result, Data, Len div SizeOf(Char) - 1) // Len schließt #0 ein, Len enthält die Anzahl der Bytes
else
  RaiseLastOSError;

Aufrufe von CreateProcessW

Die Unicode-Version (CreateProcessW) der Windows-API-Funktion CreateProcess verhält sich etwas anders als die ANSI-Version. Folgendes (sinngemäß übersetzt) findet sich in Bezug auf den lpCommandLine-Parameter in MSDN:

"Die Unicode-Version dieser Funktion, CreateProcessW, kann den Inhalt dieses Strings ändern. Daher darf dieser Parameter kein Zeiger auf schreibgeschützten Speicher sein (wie eine const-Variable oder ein literaler String). Wenn dieser Parameter ein Konstanten-String ist, könnte die Funktion eine Zugriffsverletzung verursachen."

Wegen dieses Problems könnte vorhandener Code, der CreateProcess aufruf, eine Zugriffsverletzung verursachen.

Im Folgenden finden Sie einige Beispiele für solchen problematischen Code:


// Übergabe einer String-Konstanten
CreateProcess(nil, 'foo.exe', nil, nil, False, 0,
  nil, nil, StartupInfo, ProcessInfo);
// Übergabe eines Konstantenausdrucks
  const
    cMyExe = 'foo.exe'
  CreateProcess(nil, cMyExe, nil, nil, False, 0,
    nil, nil, StartupInfo, ProcessInfo);
// Übergabe eines Strings mit dem Referenzzähler -1:
const
  cMyExe = 'foo.exe'
var
  sMyExe: string;
  sMyExe := cMyExe;
  CreateProcess(nil, PChar(sMyExe), nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);

Aufrufe von LeadBytes

Früher führte LeadBytes alle Werte auf, die im lokalen System das erste Byte eines Doppelbyte-Zeichens sein konnten. Ersetzen Sie den Quelltext:

if Str[I] in LeadBytes then

durch einem Aufruf der Funktion IsLeadChar:

if IsLeadChar(Str[I]) then

Aufrufe von TMemoryStream

Wenn mit TMemoryStream in eine Textdatei geschrieben wird, sollte am Anfang der Datei ein Byte Order Mark (BOM) eingefügt werden. Hier ein Beispiel für das Schreiben eines BOM in eine Datei:


var
  Bom: TBytes;
begin
  tms: TMemoryStream;
  ...
  Bom := TEncoding.UTF8.GetPreamble;
  tms.Write(Bom[0], Length(Bom));

Code, der in eine Datei schreibt, muss für die Codierung des Unicode-Strings in UTF-8 geändert werden:


var
  Temp: Utf8String;
begin
  tms: TMemoryStream;
  ...
  Temp := Utf8Encode(Str); // Str ist der String, der in die Datei geschrieben wird
  tms.Write(Pointer(Temp)^, Length(Temp));
 //Write(Pointer(Str)^, Length(Str)); Originalaufruf zum Schreiben des Strings in die Datei

Aufrufe von MultiByteToWideChar

Aufrufe der Windows-API-Funktion MultiByteToWideChar können einfach durch eine Zuweisung ersetzt werden. Ein Beispiel für MultiByteToWideChar:


procedure TWideCharStrList.AddString(const S: string);
var
  Size, D: Integer;
begin
  Size := SizeOf(S);
  D := (Size + 1) * SizeOf(WideChar);
  FList[FUsed] := AllocMem(D);
  MultiByteToWideChar(0, 0, PChar(S), Size, FList[FUsed], D);
  Inc(FUsed);
end;

Nach der Änderung zu Unicode wurde dieser Aufruf zur Unterstützung der Compilierung unter ANSI und Unicode geändert:


procedure TWideCharStrList.AddString(const S: string);
{$IFNDEF UNICODE}
var
  L, D: Integer;
{$ENDIF}
begin
{$IFDEF UNICODE}
  FList[FUsed] := StrNew(PWideChar(S));
{$ELSE}
  L := Length(S);
  D := (L + 1) * SizeOf(WideChar);
  FList[FUsed] := AllocMem(D);
  MultiByteToWideChar(0, 0, PAnsiChar(S), L, FList[FUsed], D);
{$ENDIF}
  Inc(FUsed);
end;

Aufrufe von SysUtils.AppendStr

AppendStr ist veraltet und für die Verwendung von AnsiString hart codiert, und es ist keine UnicodeString>-Überladung verfügbar. Ersetzen Sie Aufrufe wie:

AppendStr(String1, String2);

durch Folgendes:

 String1 := String1 + String2;

Sie können auch die neue Klasse TStringBuilder verwenden.

Verwendung von benannten Threads

Delphi-Code, der benannte Threads verwendet, muss geändert werden. Wenn Sie in früheren Versionen mit dem Thread-Objekt-Element aus der Objektgalerie einen neuen Thread erstellt haben, wurde die folgende Typdeklaration in der Unit des neuen Threads erzeugt:

type
TThreadNameInfo = record
  FType: LongWord; // muss 0x1000 sein
  FName: PChar; // Zeiger auf den Namen (im Benutzer-Adressraum)
  FThreadID: LongWord; // Thread-ID (-1 gibt den Thread des Aufrufers an)
  FFlags: LongWord; // Für zukünftige Verwendung reserviert, muss null sein
end;


Die Behandlungsroutine für benannte Threads des Debuggers erwartet für FName ANSI-Daten, keine Unicode-Daten, daher muss die obige Deklaration folgendermaßen geändert werden:


type
TThreadNameInfo = record
  FType: LongWord; // muss 0x1000 sein
  FName: PAnsiChar; // Zeiger auf den Namen (im Benutzer-Adressraum)
  FThreadID: LongWord; // Thread-ID (-1 gibt den Thread des Aufrufers an)
  FFlags: LongWord; // Für zukünftige Verwendung reserviert, muss null sein
end;

Neue benannte Threads werden mit der aktualisierten Typdeklaration erstellt. Nur Code, der in einer früheren Delphi-Version erstellt wurde, muss manuell aktualisiert werden.

Wenn Sie in einem Thread-Namen Unicode-Zeichen oder -Strings verwenden möchten, müssen Sie den String in UTF-8 codieren, damit der Debugger ihn korrekt verarbeiten kann. Zum Beispiel:

ThreadNameInfo.FName := UTF8String('UnicodeThread_фис');
Anmerkung:  In C++Builder verwenden Thread-Objekte immer den korrekten Typ, deshalb besteht dieses Problem für C++Builder-Quelltext nicht.

Verwendung von PChar-Typumwandlungen für die Aktivierung der Zeigerarithmetik

In Versionen vor 2009 unterstützten nicht alle Zeigertypen die Zeigerarithmetik. Daher wurden nicht-char Zeiger in PChar umgewandelt, um eine Zeigerarithmetik zu ermöglichen. Jetzt ermöglichen Sie die Zeigerarithmetik durch die Verwendung der neuen Compiler-Direktive $POINTERMATH, die ausdrücklich für den Typ PByte aktiviert ist.

Hier sehen Sie ein Codebeispiel, in dem Zeigerdaten in PChar umgewandelt werden, damit eine Zeigerarithmetik durchgeführt werden kann:


function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
begin
  if (Node = FRoot) or (Node = nil) then
    Result := nil
  else
    Result := PChar(Node) + FInternalDataOffset;
end;

Sie sollten dies so ändern, dass anstelle von PChar PByte verwendet wird:


function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
begin
  if (Node = FRoot) or (Node = nil) then
    Result := nil
  else
    Result := PByte(Node) + FInternalDataOffset;
end;

In dem obigen Beispiel sind Node keine eigentlichen Zeichendaten. Node wird in PChar umgewandelt, damit über die Zeigerarithmetik auf die Daten zugegriffen werden kann, die sich eine bestimmte Anzahl von Bytes nach Node befinden. Früher hat das funktioniert, weil SizeOf(Char) == Sizeof(Byte)<code>. Das trifft jetzt nicht mehr zu, daher muss solcher Code so geändert werden, dass anstelle von <code>PChar PByte verwendet wird. Ohne diese Änderung zeigt Result auf die falschen Daten.

Variante offene Array-Parameter

Wenn in Ihrem Quelltext mit TVarRec variante offene Array-Parameter behandelt werden, müssen Sie ihn für die Behandlung von UnicodeString erweitern. Für UnicodeString wurde der neue Typ vtUnicodeString definiert. Der Typ vtUnicodeString enthält die UnicodeString-Daten. Das folgende Beispiel zeigt einen Fall, für den neuer Code für die Behandlung des UnicodeString-Typs hinzugefügt wurde.


procedure RegisterPropertiesInCategory(const CategoryName: string;
  const Filters: array of const); overload;
var
I: Integer;
begin
  if Assigned(RegisterPropertyInCategoryProc) then
    for I := Low(Filters) to High(Filters) do
      with Filters[I] do
        case vType of
          vtPointer:
            RegisterPropertyInCategoryProc(CategoryName, nil,
              PTypeInfo(vPointer), );
          vtClass:
            RegisterPropertyInCategoryProc(CategoryName, vClass, nil, );
          vtAnsiString:
            RegisterPropertyInCategoryProc(CategoryName, nil, nil,
              string(vAnsiString));
          vtUnicodeString:
            RegisterPropertyInCategoryProc(CategoryName, nil, nil,
              string(vUnicodeString));
        else
          raise Exception.CreateResFmt(@sInvalidFilter, [I, vType]);
        end;
 end;

Weitere zu überprüfende Codebereiche

Suchen Sie nach den folgenden Codekonstrukten, um Probleme bei der Unicode-Aktivierung aufzudecken:

  • AllocMem
  • AnsiChar
  • of AnsiChar
  • AnsiString
  • of Char
  • Copy
  • GetMem
  • Length
  • PAnsiChar
  • Pointer
  • Seek
  • ShortString
  • string

Code, der solche Konstrukte enthält, muss für die korrekte Unterstützung des Typs UnicodeString evtl. geändert werden.

Siehe auch

Frühere Versionen
In anderen Sprachen