Activation des applications pour Unicode
Remonter à Comment compiler et construire des applications
Cette rubrique décrit les diverses constructions de code sémantiques que vous devez examiner dans votre code existant pour garantir la compatibilité de vos applications avec le type UnicodeString
. Puisque Char
équivaut maintenant à WideChar
, et les chaînes à UnicodeString
, les hypothèses précédentes sur la taille en octets d'une chaîne ou d'un tableau de caractères pourraient à présent être incorrectes.
Pour des informations générales sur Unicode, voir Unicode dans RAD Studio.
Sommaire
- 1 Configuration de votre environnement pour la migration vers Unicode
- 2 Zones spécifiques à examiner dans le code
- 2.1 Appels à SizeOf
- 2.2 Appels à FillChar
- 2.3 Appels à Move
- 2.4 Appels aux méthodes Read/ReadBuffer de TStream
- 2.5 Appels aux méthodes Write/WriteBuffer de TStream
- 2.6 Appels à GetProcAddress
- 2.7 Appels à RegQueryValueEx
- 2.8 Appels à CreateProcessW
- 2.9 Appels à LeadBytes
- 2.10 Appels à TMemoryStream
- 2.11 Appels à MultiByteToWideChar
- 2.12 Appels à SysUtils.AppendStr
- 2.13 Utilisation des threads nommés
- 2.14 Utilisation des conversions PChar pour activer l'arithmétique de pointeur
- 2.15 Paramètres tableau ouvert variant
- 2.16 Code supplémentaire à considérer
- 3 Voir aussi
Configuration de votre environnement pour la migration vers Unicode
Consultez tout code qui :
- Suppose que
SizeOf(Char)
est 1. - Suppose que la
longueur
d'une chaîne est égale au nombre d'octets de la chaîne. - Manipule directement les chaînes ou
PChars
. - Ecrit et lit des chaînes dans ou depuis un stockage persistant.
Les deux premières hypothèses listées ici ne sont pas vraies pour Unicode, puisque pour Unicode, SizeOf(Char)
est supérieur à 1 octet, et la longueur (Length
) d'une chaîne est deux fois le nombre d'octets. De plus, le code écrivant vers ou lisant dans un stockage persistant a besoin de garantir que le nombre correct d'octets est écrit ou lu, puisqu'un caractère pourrait ne plus être capable d'être représenté sous la forme d'un octet unique.
Indicateurs du compilateur
Des indicateurs ont été fournis afin que vous puissiez déterminer si la chaîne (string
) est UnicodeString
ou AnsiString
. Ils permettent de conserver un code prenant en charge les anciennes versions de Delphi et C++Builder dans le même source. Lorsque le code effectue des opérations standard sur les chaînes, il n'est généralement pas nécessaire d'avoir des sections de code UnicodeString
et AnsiString
distinctes. Toutefois, si une procédure effectue des opérations qui sont dépendantes de la structure interne des données chaîne ou qui interagissent avec les bibliothèques externes, il peut être nécessaire d'utiliser des chemins de code distincts pour UnicodeString
et AnsiString
.
Delphi
{$IFDEF UNICODE}
C++
#ifdef _DELPHI_STRING_UNICODE
Avertissements du compilateur
Le compilateur Delphi comporte des avertissements associés aux erreurs de transtypage (par exemple, de UnicodeString
ou WideString
en AnsiString
ou AnsiChar
). Lorsque vous convertissez une application en Unicode, vous devez activer les avertissements 1057 et 1058 pour trouver les zones de problème dans votre code.
Avertissement n° | Texte de l'avertissement/Nom |
---|---|
Transtypage de chaîne implicite de '%s' en '%s' (IMPLICIT_STRING_CAST) | |
Transtypage de chaîne implicite avec perte de données potentielle de '%s' en '%s' (IMPLICIT_STRING_CAST_LOSS) | |
Transtypage de chaîne explicite de '%s' en '%s' (EXPLICIT_STRING_CAST) | |
Transtypage de chaîne explicite avec perte de données potentielle de '%s' en '%s' (EXPLICIT_STRING_CAST_LOSS) |
- Pour activer les avertissements du compilateur Delphi, sélectionnez Projet > Options > Messages du compilateur.
- Pour activer les avertissements du compilateur C++, sélectionnez Projet > Options > Compilateur C++ > Avertissements.
Zones spécifiques à examiner dans le code
Appels à SizeOf
Examinez les appels à SizeOf
sur les tableaux de caractères à des fins de correction. Dans l'exemple suivant :
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
renvoie la taille du tableau en octets, mais GetWindowText
s'attend à ce que Count
soit en caractères. Dans ce cas, Length
doit être utilisé au lieu de SizeOf
. Length
fonctionne de manière similaire avec les tableaux et les chaînes. Length
appliqué à un tableau renvoie le nombre d'éléments de tableau alloués au tableau ; avec les types chaîne, Length
renvoie le nombre d'éléments de la chaîne.
Pour trouver le nombre de caractères contenus dans une chaîne à zéro terminal (PAnsiChar
ou PWideChar
), utilisez la fonction StrLen
.
Appels à FillChar
Examinez les appels à FillChar
lors de l'utilisation conjointe avec les chaînes ou Char
. Considérons le code suivant :
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
renvoie la taille en éléments, mais FillChar
s'attend à ce que Count
soit en octets. Dans cet exemple, vous devez multiplier Length
par la taille de Char
. De plus, puisque la taille par défaut d'un Char est maintenant 2, FillChar
remplit la chaîne avec des octets, pas Char
comme précédemment. Par exemple :
var Buf: array[0..32] of Char; begin FillChar(Buf, Length(Buf), #9); end;
Toutefois, ce code ne remplit pas le tableau avec le point de code $09, mais avec le point de code $0909. Pour obtenir le résultat attendu, le code nécessite d'être changé en :
var Buf: array[0..32] of Char; begin StrPCopy(Buf, StringOfChar(#9, Length(Buf))); ... end;
Appels à Move
Examinez les appels à Move
avec des chaînes ou des tableaux de caractères, comme dans l'exemple suivant :
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
renvoie la taille en éléments, mais Move
s'attend à ce que Count
soit en octets. Dans ce cas, vous devez multiplier Length
par la taille de Char
.
Appels aux méthodes Read/ReadBuffer de TStream
Examinez les appels à TStream.Read/ReadBuffer
quand les chaînes ou les tableaux de caractères sont utilisés. Dans l'exemple suivant :
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;
La solution dépend du format des données lues. Utilisez la classe TEncoding afin de vous aider à coder correctement le texte du flux.
Appels aux méthodes Write/WriteBuffer de TStream
Examinez les appels à TStream.Write/WriteBuffer
quand les chaînes ou les tableaux de caractères sont utilisés. Dans l'exemple suivant :
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;
Le code correct dépend du format des données écrites. Utilisez la classe TEncoding
afin de vous aider à coder correctement le texte du flux.
Appels à GetProcAddress
Les appels à la fonction API Windows GetProcAddress
doivent toujours utiliser PAnsiChar
, puisqu'il n'y a pas de fonction étendue analogue dans l'API Windows. Cet exemple montre l'usage correct :
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;
Appels à RegQueryValueEx
Dans RegQueryValueEx
, le paramètre Len reçoit et renvoie le nombre d'octets, pas de caractères. La version Unicode nécessite ainsi deux fois une large valeur pour le paramètre Len.
Voici un exemple d'appel à RegQueryValueEx
:
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;
Doit être changé en :
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;
Appels à CreateProcessW
La version Unicode de la fonction de l'API Windows CreateProcess
, CreateProcessW
, a un comportement légèrement différent de celui de la version ANSI. Pour citer MSDN dans la référence au paramètre lpCommandLine
:
"La version Unicode de cette fonction, CreateProcessW, peut modifier le contenu de cette chaîne. Par conséquent, ce paramètre ne peut pas être un pointeur sur une mémoire en lecture seule (comme une variable const ou une chaîne littérale). Si ce paramètre est une chaîne constante, la fonction peut causer une violation d'accès."
A cause de ce problème, le code existant qui appelle CreateProcess
peut causer des violations d'accès.
Voici quelques exemples d'un tel code problématique :
// 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);
Appels à LeadBytes
Auparavant, LeadBytes
listait toutes les valeurs qui pouvaient être le premier octet d'un caractère de deux octets sur le système local. Remplacez le code :
if Str[I] in LeadBytes then
par un appel à la fonction IsLeadChar
:
if IsLeadChar(Str[I]) then
Appels à TMemoryStream
Dans les cas où un TMemoryStream
est utilisé pour écrire un fichier texte, il est utile d'écrire un BOM (Byte Order Mark) avant d'écrire autre chose dans le fichier. Voici un exemple d'écriture du BOM dans un fichier :
var Bom: TBytes; begin tms: TMemoryStream; ... Bom := TEncoding.UTF8.GetPreamble; tms.Write(Bom[0], Length(Bom));
Tout code qui écrit dans un fichier nécessite d'être modifié pour coder en UTF-8 la chaîne Unicode :
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
Appels à MultiByteToWideChar
Les appels à la fonction MultiByteToWideChar
de l'API Windows peuvent simplement être remplacés par une assignation. Un exemple d'utilisation de 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;
Après le changement à Unicode, cet appel a été changé pour prendre en charge la compilation à la fois sous ANSI et Unicode :
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;
Appels à SysUtils.AppendStr
AppendStr
est obsolète et est codé en dur afin d'utiliser AnsiString
, et aucune surcharge de UnicodeString
n'est disponible. Remplacez les appels comme ceci :
AppendStr(String1, String2);
avec du code comme ceci :
String1 := String1 + String2;
Vous pouvez également utiliser la nouvelle classe TStringBuilder
.
Utilisation des threads nommés
Le code Delphi existant qui utilise les threads nommés doit changer. Dans les versions précédentes, quand vous utilisiez le nouvel élément Objet Thread dans la galerie pour créer un nouveau thread, il créait la déclaration de type suivante dans l'unité du nouveau thread :
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;
Le gestionnaire du thread nommé du débogueur s'attend à ce que le membre FName
soit des données ANSI, pas Unicode, ainsi la déclaration ci-dessus doit être changée en :
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;
Les nouveaux threads nommés sont créés avec la déclaration de type mise à jour. Seul le code qui a été créé dans une version précédente de Delphi doit être mis à jour manuellement.
Si vous voulez utiliser des chaînes ou des caractères Unicode dans un nom de thread, vous devez coder la chaîne en UTF-8 pour que le débogueur la gère correctement. Par exemple :
ThreadNameInfo.FName := UTF8String('UnicodeThread_фис');
Remarque : Les objets thread de C++Builder ont toujours utilisé le type correct. Ainsi, il n'y a pas de problème dans le code C++Builder.
Utilisation des conversions PChar pour activer l'arithmétique de pointeur
Dans les versions antérieures à 2009, les types de pointeur ne supportent pas tous l'arithmétique de pointeur. A cause de cela, la pratique de transtypage de divers pointeurs non-char en PChar
était utilisée pour permettre l'arithmétique de pointeur. Activez maintenant l'arithmétique de pointeur en utilisant la nouvelle directive $POINTERMATH
du compilateur, qui est spécifiquement activée pour le type PByte
.
Voici un exemple de code qui transtype les données de pointeur en PChar
à des fins d'exécution de l'arithmétique de pointeur :
function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer; begin if (Node = FRoot) or (Node = nil) then Result := nil else Result := PChar(Node) + FInternalDataOffset; end;
Vous devriez changer cela afin d'utiliser PByte
au lieu de PChar
:
function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer; begin if (Node = FRoot) or (Node = nil) then Result := nil else Result := PByte(Node) + FInternalDataOffset; end;
Dans l'exemple ci-dessus, Node
ne contient pas réellement des données caractères. Il est transtypé en un PChar
pour utiliser l'arithmétique de pointeur afin d'accéder aux données qui sont un certain nombre d'octets après Node
. Cela fonctionnait auparavant, car SizeOf(Char)
égale Sizeof(Byte)
. Ce n'est plus vrai, un tel code doit être changé pour utiliser PByte
au lieu de PChar
. Sans cette modification, Result
pointe sur des données incorrectes.
Paramètres tableau ouvert variant
Si du code utilise TVarRec
pour gérer les paramètres tableau ouvert variant, vous aurez probablement besoin de l'augmenter afin de gérer UnicodeString
. Un nouveau type vtUnicodeString
est défini pour UnicodeString
. Les données UnicodeString
sont au type vtUnicodeString
. L'exemple suivant présente un cas où du nouveau code a été ajouté pour gérer le type UnicodeString
.
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;
Code supplémentaire à considérer
Recherchez les constructions de code supplémentaires suivantes pour localiser les problèmes d'activation d'Unicode :
AllocMem
AnsiChar
of AnsiChar
AnsiString
of Char
Copy
GetMem
Length
PAnsiChar
Pointer
Seek
ShortString
string
Le code contenant de telles constructions doit être changé afin de prendre en charge correctement le type UnicodeString
.