Afficher : Delphi C++
Préférences d'affichage

Activation des applications pour Unicode

De RAD Studio XE2

Remonter à Compilation, construction et exécution d'applications - Index

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

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
  • écrit 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) peut être 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 le type de chaîne par défaut est UnicodeString ou AnsiString. Cela peut être utilisé pour maintenir du code qui supporte les anciennes versions de Delphi et C++Builder dans le même source. Pour la plupart du code qui effectue des opérations standard sur les chaînes, il ne devrait pas être 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 pourrait être nécessaire d'avoir 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, (comme de UnicodeString ou WideString vers 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 de votre code.



Avertissement n° Texte de l'avertissement/Nom

Erreur 1057

Transtypage de chaîne implicite de '%s' en '%s' (IMPLICIT_STRING_CAST)

Erreur 1058

Transtypage de chaîne implicite avec perte de données potentielle de '%s' en '%s' (IMPLICIT_STRING_CAST_LOSS)

Erreur 1059

Transtypage de chaîne explicite de '%s' en '%s' (EXPLICIT_STRING_CAST)

Erreur 1060

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
  // Code existant - incorrect quand string = UnicodeString
  Count := SizeOf(Buffer);
  GetWindowText(Handle, Buffer, Count);

  // Correct pour Unicode
  Count := Length(Buffer); // <<-- Comptage en caractères, pas en octets
  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 façon différente lors de son utilisation avec les tableaux et celle avec les chaînes Pascal. Length appliqué à un tableau renvoie le nombre d'éléments de tableau alloués au tableau ; avec les types de chaîne Pascal, Length renvoie le nombre de caractères de la chaîne.

Dans l'exemple ci-dessus, une chaîne à zéro terminal est contenue dans un tableau, ainsi Length renvoie le nombre d'éléments contenus dans le tableau, et pas le nombre de caractères de la chaîne. Pour trouver le nombre de caractères contenus dans une chaîne à zéro terminal, utilisez la fonction StrLen.

Appels à FillChar

Examinez les appels à FillChar lors de l'utilisation conjointement avec les chaînes ou Char. Considérez le code suivant :

var
  Count: Integer;
  Buffer: array[0..255] of Char;
begin
   // Code existant - incorrect quand string = UnicodeString (quand char = 2 octets)
   Count := Length(Buffer);
   FillChar(Buffer, Count, 0);

   // Correct pour Unicode
   Count := SizeOf(Buffer);                // <<-- Spécifie la taille du tampon en octets
   Count := Length(Buffer) * SizeOf(Char); // <<-- Spécifie la taille du tampon en octets
   FillChar(Buffer, Count, 0);
end;

Length renvoie la taille en caractères, mais FillChar s'attend à ce que Count soit en octets. Dans cet exemple, SizeOf doit être utilisé au lieu de Length (ou Length multiplié 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
  // Code existant - incorrect quand string = UnicodeString (quand char = 2 octets)
  Count := Length(Buf1);
  Move(Buf1, Buf2, Count);

  // Correct pour Unicode
  Count := SizeOf(Buf1);                // <<-- Spécifie la taille du tampon en octets
  Count := Length(Buf1) * SizeOf(Char); // <<-- Spécifie la taille du tampon en octets
  Move(Buf1, Buf2, Count);
end;

Length renvoie la taille en caractères, mais Move s'attend à ce que Count soit en octets. Dans ce cas, SizeOf doit être utilisé au lieu de Length (ou Length multiplié 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
  // Code existant - incorrect quand string = UnicodeString
  Stream.Read(L, SizeOf(Integer));
  SetLength(S, L);
  Stream.Read(Pointer(S)^, L);

  // Correct pour les données chaîne Unicode
  Stream.Read(L, SizeOf(Integer));
  SetLength(S, L);
  Stream.Read(Pointer(S)^, L * SizeOf(Char));
                          // <<-- Spécifie la taille du tampon en octets

  // Correct pour les données chaîne Ansi
  Stream.Read(L, SizeOf(Integer));
  SetLength(Temp, L);              // <<-- Utilise AnsiString temporaire
  Stream.Read(Pointer(Temp)^, L * SizeOf(AnsiChar));  
                          // <<-- Spécifie la taille du tampon en octets
  S := Temp;                       // <<-- Chaîne élargie en 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);
  // Code existant 
  // Incorrect quand string = UnicodeString
  Stream.Write(L, SizeOf(Integer)); // Ecrit la longueur de la chaîne
  Stream.Write(Pointer(S)^, Length(S));

  // Correct pour les données Unicode
  Stream.Write(L, SizeOf(Integer));
  Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // <<-- Spécifie la taille du tampon en octets

  // Correct pour les données Ansi
  Stream.Write(L, SizeOf(Integer));
  Temp := S;          // <<-- Utilise AnsiString temporaire
  Stream.Write(Pointer(Temp)^, Length(Temp) * SizeOf(AnsiChar)); // <<-- Spécifie la taille du tampon en octets
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, puiqu'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 inclut #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 inclut #0, Len contient le nombre d'octets
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 :


// Passage dans une chaîne constante
CreateProcess(nil, 'foo.exe', nil, nil, False, 0,
  nil, nil, StartupInfo, ProcessInfo);
// Passage dans une expression constante
  const
    cMyExe = 'foo.exe'
  CreateProcess(nil, cMyExe, nil, nil, False, 0,
    nil, nil, StartupInfo, ProcessInfo);
// Passage dans une chaîne dont refcount est -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
  ...
  Bom := TEncoding.UTF8.GetPreamble;
  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
  ...
  Temp := Utf8Encode(Str); // Str est la chaîne écrite dans le fichier
  Write(Pointer(Temp)^, Length(Temp));
 //Write(Pointer(Str)^, Length(Str)); appel original pour écrire la chaîne dans le fichier

Appels à MultiByteToWideChar

Les appels à la fonction MultiByteToWideChar de l'API Windows peuvent simplement être remplacés par une affectation. Un exemple d'utilisation de 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;

Après le changement à Unicode, cet appel a été changé pour supporter 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 déprécié 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; // doit être 0x1000
  FName: PChar; // pointeur sur le nom (dans l'espace adresse de l'utilisateur)
  FThreadID: LongWord; // ID de thread (-1 indique le thread appelant)
  FFlags: LongWord; // réservé à un usage ultérieur, doit être à zéro
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; // doit être 0x1000
  FName: PAnsiChar; // pointeur sur le nom (dans l'espace adresse de l'utilisateur)
  FThreadID: LongWord; // ID de thread (-1 indique le thread appelant)
  FFlags: LongWord; // réservé à un usage ultérieur, doit être à zéro
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) == 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 supporter correctement le type UnicodeString.

Voir aussi

Versions précédentes
Autres langues