Anwendungen für Unicode anpassen
Nach oben zu So compilieren und erzeugen Sie Anwendungen
In diesem Thema werden einige semantische Codekonstrukte beschrieben, 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 jetzt falsch sein.
Allgemeine Informationen über Unicode finden Sie unter Unicode in RAD Studio.
Inhaltsverzeichnis
- 1 Einrichten der Umgebung für die Migration nach Unicode
- 2 Zu überprüfende Codebereiche
- 2.1 Aufrufe von SizeOf
- 2.2 Aufrufe von FillChar
- 2.3 Aufrufe von Move
- 2.4 Aufrufe von Read/ReadBuffer-Methoden von TStream
- 2.5 Aufrufe von Write/WriteBuffer-Methoden von TStream
- 2.6 Aufrufe von GetProcAddress
- 2.7 Aufrufe von RegQueryValueEx
- 2.8 Aufrufe von CreateProcessW
- 2.9 Aufrufe von LeadBytes
- 2.10 Aufrufe von TMemoryStream
- 2.11 Aufrufe von MultiByteToWideChar
- 2.12 Aufrufe von SysUtils.AppendStr
- 2.13 Verwendung von benannten Threads
- 2.14 Verwendung von PChar-Typumwandlungen für die Aktivierung der Zeigerarithmetik
- 2.15 Variante offene Array-Parameter
- 2.16 Weitere zu überprüfende Codebereiche
- 3 Siehe auch
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 ist, und die Länge (Length
) eines Strings halb 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 möglicherweise nicht mehr durch ein einzelnes Byte repräsentiert werden kann.
Compiler-Flags
Es wurden Flags hinzugefügt, damit Sie festlegen können, ob String
UnicodeString
oder AnsiString
ist. Damit können Sie Code verwenden, 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 zu Fehlern 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 problematische Bereiche in Ihrem Quelltext zu finden.
Warnungsnummer | Text/Name der Warnung |
---|---|
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 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 // Existing code - incorrect when string = UnicodeString Count := SizeOf(Buffer); GetWindowText(Handle, Buffer, Count); // Correct for Unicode Count := Length(Buffer); // <<-- Count should be chars not bytes GetWindowText(Handle, Buffer, Count); end;
SizeOf
gibt die Größe des Arrays in Byte zurück, aber GetWindowText
erwartet, dass Count
Zeichen angibt. 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 Elemente in dem String zurück.
Verwenden Sie die Funktion StrLen
, um die Anzahl der Zeichen in einem nullterminierten String (PAnsiChar
oder PWideChar
) zu erhalten.
Aufrufe von FillChar
Überprüfen Sie die Aufrufe von FillChar
, wenn sie zusammen mit Strings oder Char
verwendet werden. Sehen Sie sich den folgenden Quelltext an:
var Count: Integer; Buffer: array[0..255] of Char; begin // Existing code - incorrect when string = UnicodeString (when char = 2 bytes) Count := Length(Buffer); FillChar(Buffer, Count, 0); // Correct for Unicode Count := Length(Buffer) * SizeOf(Char); // <<-- Specify buffer size in bytes FillChar(Buffer, Count, 0); end;
Length
gibt die Größe in Elementen zurück, aber FillChar
erwartet, dass Count
Bytes angibt. In diesem Beispiel sollte Length
multipliziert mit der Größe von Char
verwendet werden. Außerdem füllt FillChar
den String mit Bytes, nicht wie früher mit Char
, weil die Standardgröße eines Char jetzt 2 beträgt. 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 // Existing code - incorrect when string = UnicodeString (when char = 2 bytes) Count := Length(Buf1); Move(Buf1, Buf2, Count); // Correct for Unicode Count := Length(Buf1) * SizeOf(Char); // <<-- Specify buffer size in bytes Move(Buf1, Buf2, Count); end;
Length
gibt die Größe in Elementen zurück, aber Move
erwartet, dass Count
Bytes angibt. In diesem Fall sollte Length
multipliziert mit der Größe von Char
verwendet 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 // Existing code - incorrect when string = UnicodeString Stream.Read(L, SizeOf(Integer)); SetLength(S, L); Stream.Read(Pointer(S)^, L); // Correct for Unicode string data Stream.Read(L, SizeOf(Integer)); SetLength(S, L); Stream.Read(Pointer(S)^, L * SizeOf(Char)); // <<-- Specify buffer size in bytes // Correct for Ansi string data Stream.Read(L, SizeOf(Integer)); SetLength(Temp, L); // <<-- Use temporary AnsiString Stream.Read(Pointer(Temp)^, L * SizeOf(AnsiChar)); // <<-- Specify buffer size in bytes S := Temp; // <<-- Widen string to Unicode end;
Die Lösung hängt vom Format der zu lesenden Daten ab. Mit der Klasse TEncoding können Sie Stream-Text korrekt codieren.
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); // Existing code // Incorrect when string = UnicodeString Stream.Write(L, SizeOf(Integer)); // Write string length Stream.Write(Pointer(S)^, Length(S)); // Correct for Unicode data Stream.Write(L, SizeOf(Integer)); Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // <<-- Specify buffer size in bytes // Correct for Ansi data Stream.Write(L, SizeOf(Integer)); Temp := S; // <<-- Use temporary AnsiString Stream.Write(Pointer(Temp)^, Length(Temp) * SizeOf(AnsiChar));// <<-- Specify buffer size in bytes end;
Die korrekte Code hängt vom Format der zu schreibenden Daten ab. Mit der Klasse TEncoding
können Sie Stream-Text korrekt codieren.
Aufrufe von GetProcAddress
In Aufrufen der Windows-API-Funktion GetProcAddress
sollte immer PAnsiChar
verwendet werden, weil es in der Windows-API keine analoge Wide-Funktion gibt. Dieses 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 includes #0 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_SUCCES then SetString(Result, Data, Len div SizeOf(Char) - 1) // Len includes #0, Len contains the number of 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
aufruft, eine Zugriffsverletzung verursachen.
Im Folgenden finden Sie einige Beispiele für solchen problematischen Code:
// Passing in a string constant CreateProcess(nil, 'foo.exe', nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo); // Passing in a constant expression const cMyExe = 'foo.exe' CreateProcess(nil, cMyExe, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo); // Passing in a string whose refcount is -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 is string being written to file tms.Write(Pointer(Temp)^, Length(Temp)); //Write(Pointer(Str)^, Length(Str)); original call to write string to file
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 := Length(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
Vorhandener Delphi-Code, in dem benannte Threads verwendet werden, muss geändert werden. Wenn Sie in früheren Versionen mit dem neuen Eintrag Thread-Objekt der Objektgalerie einen neuen Thread erstellt haben, wurde die folgende Typdeklaration in der Unit des neuen Threads erzeugt:
type TThreadNameInfo = record FType: LongWord; // must be 0x1000 FName: PChar; // pointer to name (in user address space) FThreadID: LongWord; // thread ID (-1 indicates caller thread) FFlags: LongWord; // reserved for future use, must be zero 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; // must be 0x1000 FName: PAnsiChar; // pointer to name (in user address space) FThreadID: LongWord; // thread ID (-1 indicates caller thread) FFlags: LongWord; // reserved for future use, must be zero 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_фис');
Hinweis: 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 aktivieren Sie die Zeigerarithmetik durch die Verwendung der neuen Compiler-Direktive $POINTERMATH
, die ausdrücklich für den Typ PByte
vorgesehen ist.
In dem folgenden Codebeispiel werden Zeigerdaten in PChar
umgewandelt, 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. Es 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)
gleich Sizeof(Byte)
war. 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 im Quelltext mit TVarRec
variante offene Array-Parameter verarbeitet 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 Verarbeitung 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 zu lokalisieren:
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
möglicherweise geändert werden.