Activation des applications pour Unicode

De RAD Studio
Aller à : navigation, rechercher

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.

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

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
  // 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.

Voir aussi