Anwendungen für Unicode anpassen
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
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 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, aktivieren Sie die Warnungen 1057 und 1058, um die problematischen Bereiche in Ihrem Quelltext zu finden.
| Warnungsnummer | Warnungstext/name |
|---|---|
|
Implizite String-Umwandlung von '%s' zu '%s' (IMPLICIT_STRING_CAST) | |
|
Implizite String-Umwandlung mit potenziellem Datenverlust von '%s' zu '%s' (IMPLICIT_STRING_CAST_LOSS) | |
|
Explizite String-Umwandlung von '%s' zu '%s' (EXPLICIT_STRING_CAST) | |
|
Explizite String-Umwandlung mit potenziellem Datenverlust von '%s' zu '%s' (EXPLICIT_STRING_CAST_LOSS) |
- Die Delphi-Compiler-Warnungen aktivieren Sie auf der Seite .
- Die C++-Compiler-Warnungen aktivieren Sie auf der Seite .
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 für Arrays anders als für Pascal-Strings. Bei einem Array gibt Length die Anzahl der Array-Elemente zurück, die dem Array zugewiesen sind; bei Pascal-String-Typen gibt Length die Anzahl der Zeichen in dem String zurück.
In dem obigen Beispiel enthält das Array einen nullterminierten String, daher gibt Length die Anzahl der Elemente zurück, die in dem Array enthalten sind, nicht die Anzahl der Zeichen in dem String. Um die Anzahl der Zeichen in einem nullterminierten String zu erhalten, verwenden Sie die Funktion StrLen.
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 := SizeOf(Buffer); // Geben Sie die Puffergröße in Byte an 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 := SizeOf(Buf1); // Geben Sie die Puffergröße in Byte an 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; begin // Vorhandener Code ist falsch, wenn String UnicodeString ist Stream.Write(Pointer(S)^, Length(S)); // Richtig für Unicode-Daten Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // Geben Sie die Puffergröße in Byte an // Richtig für Ansi-Daten 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 ... Bom := TEncoding.UTF8.GetPreamble; 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 ... Temp := Utf8Encode(Str); // Str ist der String, der in die Datei geschrieben wird 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). Das trifft jetzt nicht mehr zu, daher muss solcher Code so geändert werden, dass anstelle von 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.