menu

Migration Unicode Delphi - John COLIBRI.


1 - Migration vers Delphi 2009, Migration Delphi 2010, Migration Delphi XE

Delphi 2009 a introduit une utilisation syst駑atique d'Unicode, au niveau de la librairie, des composants, de l'Interface.

Ce basculement offre des avantages certains:

  • facilit? d'internationalisation des applications
  • meilleure ad駲uation avec Windows
Mais il a aussi introduit des ruptures dans l'騅olution d'une version de Delphi vers la suivante. C'est la premi鑽e fois qu'un changement de version est "breaking": du code ant駻ieur ? Delphi 2009 peut ne pas compiler, ou, une fois compil?, ne pas fonctionner comme escompt?



Compte tenu des missions de migration que nous ont confi? nos clients, nous pouvons consid駻er trois cas:

  • votre code ne comporte pas d'instructions qui sont trait馥s diff駻emment avant et apr鑚 Unicode. Une simple compilation suffit. C'est le cas de 70 % des applications
  • votre code utilise des instructions qui ne sont plus correctes en unicode. En particulier toutes les instructions qui supposent qu'un caract鑽e occupe un octet. Des modifications sont alors ? pr騅oir. Environ 25 % des cas.
  • votre code est d駛? internationalis?, et utilise les fonctionnalit駸 Unicode qui 騁aient disponibles avec les versions Delphi ant駻ieures ? Delphi 2009. Il faudra alors changer est adapter toutes ces instructions pour qu'elles fonctionnent correctement sous Delphi 2009 ou suivant. 5 % des cas
Et quand nous parlons de "votre code", il s'agit bien s?r des lignes que vous avez 馗rites, mais concerne aussi les composants que vous avez sous-trait駸, achet駸, r馗up駻駸 sur le Web, en source, voire en Open Source.



En r駸um?

  • si, apr鑚 avoir sauvegard? vos sources, vous les compilez et qu'il passent les tests de validation, bravo, cet article ne vous concerne gu鑽e.
  • si ce n'est pas le cas, nous vous proposons
    • tout d'abord d'expliquer ce qu'est Unicode. Une connaissance minimale est obligatoire, d'autant qu'un jargon particulier a 騁? d馭ini
    • nous pr駸enterons alors les types Delphi tels qu'ils existaient avant Delphi 2009
    • suivent les types Unicode Delphi 2009, ainsi que les types maintenus pour des raisons de compatibilit? arri鑽e
    • puis un descriptif des principaux points ? examiner
    • et enfin les utilitaires et les strat馮ies pour effectuer de telles migrations


Bien entendu, nous restons ? votre disposition pour assurer les missions de migrations, qu'elles concernent Unicode, ou d'autres 騅olutions des vos projets (type base de donn馥s ou composant d'acc鑚, architecture l馮鑽e, Internet, m馗aniques de plugin etc)


2 - D馭inition UNICODE

2.1 - Les caract鑽es sous DOS

Sous Dos, nous utilisions tous le jeu de caract鑽es ASCII: 26 lettres minuscules, 26 majuscules, les chiffres, quelques ponctuations: en moins de 127 caract鑽es, tout texte anglais standard pouvait 黎re repr駸ent?
  • chaque caract鑽e avait un code Ascii: 65 pour le "A", 66 pour le "B", 97 pour le "a", 48 pour le "0" etc
  • les 32 premiers codes 騁aient utilis駸 pour des caract鑽es "de contr?le": retour chariot, la sonnette etc
ascii.png



Comme l'ensemble tenait en moins de 128 codes, IBM d馗ida d'utiliser les codes restants, entre 128 et 255 pour repr駸enter

  • la plupart des accents europ馥ns
  • des caract鑽es "semi-graphiques", comme des barres horizontales ou verticales, pour dessiner des tableaux
  • des symboles divers, comme sigma, le tr鑁le etc
ce qui donna naissance au code OEM :

oem.png



Toutefois, les pays non latins (les grecs, les arrabes, les russes, sans parler des japonais ou des chinois) souhaitaient utiliser les 128 caract鑽es disponibles pour coder leurs jeux de caract鑽es ? eux.

De ce fait

  • les 128 premiers codes ont 騁? fig駸 par la norme ANSI
  • les 128 codes suivants ont 騁? d馭inis par chaque pays. Chaque codification a re輹 un num駻o appel? code page. Ainsi, les Grecs utilisaient le code page 737, les Fran軋is le code page 850 (OEM Multilingual Latin 1; Western European)
La page Msdn - Code Page Identifiers d馗rit les diff駻ents codes utilis駸, et, pour info, en voici quelques uns;
  • 737 OEM Greek (formerly 437G); Greek (DOS)
  • 850 OEM Multilingual Latin 1; Western European (DOS)
  • 855 OEM Cyrillic (primarily Russian)
  • 1251 ANSI Cyrillic; Cyrillic (Windows)
  • 1252 ANSI Latin 1; Western European (Windows)
  • 1253 ANSI Greek; Greek (Windows)
  • 1254 ANSI Turkish; Turkish (Windows)
  • 1255 ANSI Hebrew; Hebrew (Windows)
  • 1256 ANSI Arabic; Arabic (Windows)
Mais, ? part quelques tentatives, il n'騁ait pas possible de pr駸enter ? l'馗ran ? la fois du cyrillique et de l'h饕reu.



2.2 - Unicode

Pour permettre l'affichage de TOUS les caract鑽es existant, in the world, plus quelques autres, la norme UNICODE fut cr鳬e:
  • fondamentalement, chaque caract鑽es se voit attribuer un num駻o unique, cod? sur 4 octets, appel? code point. Ainsi, la lettre "A" (a majuscule) se voit attribuer le code 0041,ドル qui est le m麥e pour cette lettre quelle que soit la police de caract鑽e, la taille ou le style :

    A A A A

    En revanche, le "a minuscule", "? accent grave", "? circonflexe" etc ont chacun leur propre code point

    • 00ドルE0 pour "?"
    • 00ドルE2 pour "?"
    etc

    Le code est officiellement not? avec un pr馭ixe "U+" suivi de la valeur en hexad馗imal

    U+0041

  • chaque code point a un nom officiel. Pour le A c'est

    LATIN CAPITAL LETTER A

    et pour le "a" (code point U+0061) :

    LATIN SMALL LETTER A

    La page Nom des code points vous donnera une id馥 des codes et des noms utilis駸



Chaque caract鑽e a donc un code point sur 4 octets. Bien s?r, les premiers codes tiennent sur un octet. Mais certains caract鑽es on besoin de 2 ou plus de 2 octets. Et donc, fondamentallement, le code point est d馭ini sur 4 octets.

Et le dessin (appel? script) de chaque caract鑽e est pr駸ent? dans des fichier .PDF, r駱artis en langue, type de symbole etc.



2.3 - Les cat馮ories de code points

Le code Ascii contenait ? la fois des lettres et des chiffres, mais aussi des caract鑽es de contr?le, des semi-graphiques etc.

De la m麥e fa輟n, les codes points Unicode ont 騁? class駸 en 7 cat馮ories fondamentales: Graphic, Format, Control, Private-Use, Surrogate, Noncharacter, Reserved.

Les cat馮ories autres que les lettres 騁endent Unicode pour 馗rire des documents scientifiques, des pages de musique, des illustrations symboliques (POLICE OFFICER U+1F46E, PRINCESS U+1F478, RAT U+1F400 !). Plus le Braille, les dominos, le mahjong, l'alchimie ...



2.4 - Basic Multilingual Plane - BMP

La plage des valeurs possibles a 騁? organis馥 par zones. Des documents pr馗isent ce que contiennent chaque zones, les valeurs utilis馥s, r駸erv馥s, garanties vides etc. La table Code Point Names montre que sur les $FFFF.FFFF valeurs possibles, environ seuls les 0002ドル.FFFF premi鑽es valeurs sont utilis馥s (plus quelques rares valeurs en fin de table).

Pour la plage des 64 K premi鑽es valeurs la d馗omposition se pr駸ente ainsi:

the_unicode_range.png



Vous constaterez que la plupart des caract鑽es que nous utilisons couramment (lettres latines et des principaux alphabets) sont au d饕ut de la plage des valeurs.

Et les codes dont la valeur est inf駻ieure ? 64 K est appel? le Basic Multilingual Plane (plan multilingue de base), ou BMP. Les code points que nous utiliserons couremment seront dans la plage du BMP. Mais il n'en demeure pas moins que les code points sont d馭inis sur 4 octets.



Les autres "plans" dont nous parlerons fort peu, contiennent, par exemple

  • le plan 1, Supplementary Multilingual Plane pour les caract鑽es historiques (gothique etc) et musicaux
  • le plan 2, Supplementary Ideographic Plane, pour des caract鑽es asiatiques
et ceci a naturellement vari? au fil de l'騅olution de la norme Unicode.

Nous nous limiterons essentiellement au plan 0, BMP.



2.5 - Caract鑽es composites

Certains caract鑽es, parmi lesquels nos caract鑽es accentu?, ont une double repr駸entation
  • une version "pr?-compos馥" avec un seul code point :

    U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX (donc "?")

  • une version comportant le code point du "a" et un code point pour le circonflexe

    0061 0302

    C'est la version "d馗ompos馥" ou "composite"



Ceci peut se repr駸enter ainsi:

composite.png

Et cela peut se corser avec des compositions multiples:

composite.png

qui est, comme chacun sait, diff駻ent de

composite



Le probl鑪e se pose alors pour g駻er les cha?nes contenant ces caract鑽es composites

  • sont-ils affich駸 comme un caract鑽e pr馗ompos?, ou comme une suite de code points ?
  • lorsque nous comparons dex cha?nes, U+00E2 est-il 馮al ? 061 0302 ?


Windows a introduit des proc馘ures de normalisation utilisables
  • pour XP seulement si nous avons install? IDN (Microsoft International Domain Names Migration API 1.1)
  • directement pour Vista, Windows Server 2008 et Windows 7


2.6 - Les repr駸entations compactes Unicode

L'utilisation de 4 octets pour repr駸enter chaque caract鑽e a 騁? jug馥 excessive. Et de ce fait il a 騁? mis sur pied plusieurs syst鑪es permettant de compacter le stockage des code points.

Les 3 m騁hodes de codage le plus fr駲uemment utilis馥s sont UTF-8, UTF-16 et UTF-32 (UTF: Unicode Transformation Standard). Il en existe d'autres tels que UCS-2 ou UCS-4 (Universal Character Set), moins utilis馥s actuellement.



2.6.1 - UTF-8

Dans cette repr駸entation, les code points sont stock駸 en utilisant 1, 2 ou 4 octets :
  • les caract鑽es les plus utilis駸 (les lettres non accentu馥s) sont cod馥s sur les premiers 7 bits d'un octet.
  • au del? de 7 bits, UTF-8 utilise 2 ou 4 octets
Cette repr駸entation
  • est naturellement plus compacte que les 4 octets des code points
  • a l'inconv駭ient que la ne nombre de caract鑽es d'une cha?ne ne peut se d馘uire de la taille de la cha?ne en octets
  • est n饌nmoins tr鑚 utilis馥 pour les pages .HTML et beaucoup de fichiers .XML ou certains protocoles TCP/IP.


2.6.2 - UTF-16

Lorsque la vitesse de traitement est plus importante que la place m駑oire, c'est la repr駸entation UTF-16 qui est utilis馥

Dans cette repr駸entation :

  • tous les caract鑽es de la BMP (les caract鑽es les plus utilis駸) sont cod駸 sur 2 octets. Ces deux octets sont appel駸 une code unit
  • pour les caract鑽es d駱assant un code point de 64 K, et qui ne peuvent donc pas 黎re repr駸ent駸 sur 2 octets, UTF 16 utilise deux "code units". Donc 4 octets aussi. Et ces 4 octets, deux code units sont appel駸 une surrogate pair (paires de substitution)
Les surrogate pairs sont cantonn馥s :
  • dans la plage U+D800 ? U+DBFF pour la premi鑽e code unit ("high surrogate" ou "leading surrogate")
  • et la plage U+DC00 ? U+DFFF pour la seconde ("low surrogate")
Cette partie r駸erv馥 est visible sur l'image pr駸entant le d馗oupage des code points pr駸ent馥 ci dessus.

A titre d'exemple

  • les valeurs autour de $D840 $DC01 sont des id駮grammes d'extr麥e orient
  • les caract鑽es gothiques, comme la lettre KU sont cod駸 au voisinage de $D800 $DF12


Le format UTF-16 est particuli鑽ement int駻essant
  • car il correspond au format du syst鑪e d'exploitation sous-jacent. Il permet donc de meilleures performances lors de l'appel de l'API Windows.
  • dans la vaste majorit? des cas, les caract鑽es que nous utilisons sont dans la BMP, et nous n'avons pas besoin de codage sur 2 code units (4 octets)


2.6.3 - UTF-32

UTF-32 utilise 4 octets pour repr駸enter chaque code point



2.6.4 - Quelques Exemples

Un point important est que m麥e si vous avez en m駑oire un caract鑽e Unicode, vous le verrez pas n馗essairement son image correcte ? l'馗ran. Ansi je n'ai pu apercevoir de symboles gothiques, faute de police Windows sur mon PC.

En revanche, j'ai pu afficher:

  • des caract鑽es chinois, dans la plage des 20000ドル (CJK Unified Ideographs Extension B). Par exemple
    • U+20000, "GKX075" etc
    • en UTF-16, surrogate $D840, $DC01
    • qui se pr駸ente ainsi :

      surrogates.png
  • des caract鑽es grecs:
    • un om馮a avec des accents
      • U+1FA7 : greek small letter omega with dasia and perispomeni and ypojegrammeni
      • UTF-16, composite 1F67 0345
    • un epsilon avec quelques accents
      • U+1F13 greek letter epsilon with dasia and varia
      • UTF-16 : composite 1F11 0300


2.6.5 - UCS-2

UCS-2 est toujours cod? sur 2 octets, et ne peut repr駸enter que les caract鑽es de la BMP. De plus les "surrogate pairs" de UTF-16 ne sont pas reconnus.



2.6.6 - UCS-4

UCS-4 est toujours cod? sur 4 octets, et permet de repr駸enter les m麥es caract鑽es que UTF-32, mais a 騁? supplant? par UTF-32.



2.6.7 - Big Endian, Little Endian - Byte Order Mark - BOM

Pour les repr駸entations utilisant plus de 1 octet, il est aussi possible de pr馗iser dans quel ordre les octets sont organis駸: poids le plus faible au d饕ut ou ? la fin:
  • en LE, 1234ドル est stock? 34ドル 12ドル
  • en BE, 1234ドル est stock? 12ドル 34ドル
Pour Windows, c'est le mode LE qui est utilis?

La sp馗ification de cet ordre est importante pour stocker les donn馥s dans des fichiers. En fait les fichiers peuvent optionnellement comporter une signature, en d饕ut de fichier, qui pr馗ise cet ordre. Cette signature, et est appell馥 BOM (Byte Order Mark) et a les valeurs suivantes:

  • FF FE : UTF-16, little endian

  • FE FF : UTF-16, big endian
  • FF FE 00 00 : UTF-32 little endian
  • 00 00 FE FF : UTF-32 big endian
  • EF BB BF : UTF-8



3 - Les caract鑽es avant Delphi 2009

De Delphi 2 ou 3 ? Delphi 2007, nous pouvions utiliser les types suivants:
  • les caract鑽es Char ont une taille de 1 octet :

    Var my_char: Char;

  • les cha?nes compatibles Apple ][, d馭inies par

    Var my_string= String[5];
    my_string_max: ShortString; // 駲uivalent ?

    Et

    • la taille occup馥 par la variable est toujours la m麥e, l'octet 0 est utilis? pour g駻er la taille actuelle des caract鑽es utilis駸 ('joe': 3 caract鑽es), le premier indice commence ? 1
  • les cha?nes par d馭aut, de type String, correspondent ? AnsiString, ont les propri騁駸 suivantes
    • chaque variable est un pointeur vers un prologue suivi des caract鑽es
    • elles comportent un prologue avec
      • sur 4 octets le nombre de r馭駻ences vers cette valeur
      • sur 4 octets la taille, en caract鑽es (donc en octets)
      Le sch駑a est donc le suivant:

      string_reference_count

    • elle utilise la s駑antique "copy on write":
      • si plusieurs variables String ont la m麥e valeur, elles pointent vers la m麥e adresse
      • si nous modifions la valeur d'une des variables, une copie de la valeur pr馗馘ente est effectu馥s, et la nouvelle zone m駑oire est modifi馥
      Donc, si nous changeons le surnom 'JOE' en 'JOEL' :

      string_copy_on_write

    • finalement, pour des raisons historiques, le premier caract鑽e ? l'indice 1 (et pas 0)
  • WideChar sont des caract鑽es Unicode sur 2 octets
  • WideString sont des pointeurs de cha?nes de caract鑽es Unicode sur 2 octets, compatibles avec le type BSTR COM, donc utilisable pour les appels OLE

  • pChar est un pointeur vers un tableau de caract鑽es (Char : 1 octet par caract鑽e).

    Lorsque le pChar pointe vers une "StringZ" Windows, la suite des caract鑽es est termin馥 par un z駻o. Mais nous pouvons faire pointer un pChar vers n'importe quoi (c'est un pointeur de type ^Char, avec des fonctionalit駸 de traitement de cha?nes, comme la longueur, utilisables si nous pointons vers une v駻itable "StringZ", et dot? d'une s駑antique de pointeur C, donc avec possibilit? de recalculer d'adresse par "+")

    Ce type 騁ait ? l'origine utilis?

    • pour de nombreux appels des API Windows
    • pour effectuer de "l'arithm騁ique des pointeurs" :
      • une variable pChar 騁ait initialis馥 pour pointer vers une zone m駑oire
      • une addition permettait de d駱lacer le pointeur (si p pointe vers 1234,ドル p +3 pointe vers 1237ドル)


Voici un bout de programme Delphi 6 qui affiche le contenu d'une String:

Type t_delphi_6_string_header=
Packed Record
m_reference_count: LongInt;
m_byte_count: LongInt;
m_bytes: Array[0..0] Of Byte;
End;
t_pt_delphi_6_string_header= ^ t_delphi_6_string_header;

Procedure TForm1.String_Click(Sender: TObject);
Var l_string: String;
l_index: integer;
l_result: String;
Begin
display('String (=AnsiString) avec e aigu et i traema'
l_string:= 'abc 鴆';

With t_pt_delphi_6_string_header(Integer(l_string)- 8) ^ Do
Begin
display('ref '+ Format('%4x', [m_reference_count]));
display('byte count '+ Format('%4x', [m_byte_count]));
l_result:= '';
For l_index:= 0 To m_byte_count- 1 Do
(*$r-*)
l_result:= l_result+ Format('%1x ', [m_bytes[l_index]]);
(*$r+*)
display(l_result);
End;
End; // String_Click

soit (la r馭駻ence d'usage est -1 car la String est locale)

01_string_delphi_6



Et voici un exemple d'affichage Unicode en Delphi 6

Type t_widestring_delphi_6=
Packed Record
m_byte_length: Integer;
m_bytes: Array[0..0] Of Byte;
End;
t_pt_widestring_delphi_6= ^ t_widestring_delphi_6;

Procedure TForm1.WideString_Click(Sender: TObject);
Const k_u_e_grave= $E8;
k_u_heart= 2665ドル;
Var l_wide_string: WideString;
l_index: Integer;
l_result: String;
Begin
display('WideString e grave, chr(4), e aigu, A');
SetLength(l_wide_string, 5);
l_wide_string[1]:= WideChar(k_u_e_grave);
l_wide_string[2]:= WideChar(k_u_heart);
// -- try surrogates for ?
l_wide_string[3]:= WideChar(0065ドル);
l_wide_string[4]:= WideChar(0301ドル);
l_wide_string[5]:= WideChar(0041ドル);

// -- affiche le contenu de la WideString
With t_pt_widestring_delphi_6(Integer(l_wide_string)- 4)^ Do
Begin
display(' byte_count '+ IntToStr(m_byte_length));
display(' Length '+ IntToStr(Length(l_wide_string)));
l_result:= '';
For l_index:= 0 To m_byte_length+ 2- 1 Do
(*$r-*)
l_result:= l_result+ Format('%1x ', [m_bytes[l_index]]);
(*$r+*)
display(' '+ l_result);
End;
End; // widesstring_Click

et le r駸ultat:

02_widestring_delphi_6



Les librairie d'importation des API Windows (comme WINDOWS.PAS) comportaient en g駭駻al 3 versions pour chaque proc馘ure ou fonction

  • une version Unicode avec un suffixe W
  • une version Ansi, avec un suffixe A
  • une version sans suffixe, qui correspondait exactement ? la version Ansi
Par exemple

Function GetModuleHandle(lpModuleName: PChar): HMODULE; Stdcall;
Function GetModuleHandleA(lpModuleName: PAnsiChar): HMODULE; Stdcall;
Function GetModuleHandleW(lpModuleName: PWideChar): HMODULE; Stdcall;

Dans cet exemple, comme pChar et pAnsiChar sont des alias, la version par d馭aut est la m麥e que la version Ansi.



Finalement, les types String et WideString sont compatibles en affectation, mais l'affectation d'une WideString ? une String peut provoquer une perte d'information.



En r駸um?, nous constatons donc

  • String est l'alias qui d駸igne le type string pr馭駻?, correspondant ? AnsiString
  • pour AnsiString, 1 caract鑽e = 1 octet
  • les WideString sont cod駸 sur 2 octets (ce n'est pas de l'UTF-8 ou de l'UTF-16, il n'y a pas de surrogates). La RTL nous fournissait de nombreuses proc馘ures pour g駻er peu ou prou manuellement Unicode, en particulier vers UTF-8 ou pour la gestion des caract鑽es Multi-Byte. Pour les caract鑽es utilisant plusieurs Octets (MBCS), les librairies comme SYSUTILS.PAS contenaient des proc馘ures et fonctions sp馗ifiques, comme IsLeadByte.



4 - Les types String Delphi 2009 et suivants

4.1 - R駸um? de la gestion des caract鑽es

Pour les versions Delphi 2009 et suivantes, l'accent est mis sur Unicode:
  • pour la partie Unicode
    • l'alias String correspond ? un une cha?ne Unicode cod馥 en UTF-16
    • l'alias Char correspond ? un caract鑽e Unicode cod? sur 2 octets, et correspond au type WideChar
    • pChar correspond ? un pWideChar
    • WideString se comporte comme auparavant, et correspond ? une BSTR COM
    • le type Usc4Char est un LongInt, correspondant ? un code point (4 octets)
  • pour la partie Ansi:
    • il existe toujours les types AnsiChar et AnsiString, o? un caract鑽e occupe un octet, ansi que pAnsiChar qui pointe vers un tableau de caract鑽es Ansi (1 caract鑽es= 1 octet)
    • AnsiString g鑽e le code page
    • String[nn] ou ShortString g鑽ent des cha?nes de taille m駑oire fixe, avec des caract鑽es Ansi sur 1 octet
  • les proc馘ures et fonctions des librairies (RTL) contiennent toujours des versions suffix馥s "W" et "A", mais les versions sans suffixe correspondent ? pr駸ent ? des cha?nes Unicode
  • des nouvelles unit駸 (CHARACTER.PAS, ANSISTRING.PAS) et de nombreuses proc馘ures ont 騁? ajout馥s (dans SYSTEM.PAS et SYSUTILS.PAS) pour faciliter la gestion Unicode et les conversions


4.2 - String et UnicodeString

Le type String correspond maintenant ? un une cha?ne UnicodeString qui est une cha?ne Unicode cod馥 en UTF-16

Donc

  • les caract鑽es non composites utilisent une "code unit", donc 2 octets
  • les caract鑽es composites et les surrogates utilisent 2 "code units", donc 4 octets


4.2.1 - Format m駑oire des String (UnicodeString)

En m駑oire, les cha?nes utilisent ? pr駸ent un prologue de 12 octets:
  • 2 octets pour le code page
  • 2 octets pour la taille de l'駘駑ent (appel? "駘駑ent size" par Delphi)
  • 4 octets pour le compte de r馭駻ence
  • 4 octets pour la le nombre de code units (appel駸 "駘駑ent count" par Delphi)
  • le tableau des code units, donc de 2 octets par "駘駑ent", index? ? partir de 1
unocdestring_memory_format



4.2.2 - Longueur de la Cha?ne

Tr鑚 important:
  • la longueur de la cha?ne retourn馥 par Length est le nombre de code units (donc le nombre de mots de 2 octets, ou encore la taille en octets divis馥 par 2).
  • En revanche, le nombre de caract鑽es doit 黎re diminu? du nombre de caract鑽es surrogates et de composites.
  • ma_cha?ne[indice] retournera la code unit ? cette position. Cette valeur sera donc une valeur sur 2 octets, qui PEUT NE PAS pas 黎re un code point


Prenons un premier exemple simple.

Nous avons tout d'abord cr鳬 une toute petite unit? qui nous permet d'analyser le contenu m駑oire et disque:

Unit u_display_unicode;
Interface
Uses Classes;

Const k_new_line= #13#10;
Type t_byte_array= Array[0..0] Of Byte;

t_new_string_header=
Packed Record
m_code_page: Word;
m_element_size: Word;
m_reference_count: Longint;
m_element_count: Longint;
m_bytes: t_byte_array;
End;
t_pt_new_string_header= ^t_new_string_header;

Function f_byte_array_to_hex(Var pv_byte_array: t_byte_array;
p_count: Integer): String;
Function f_display_unicode_string(Var pv_unicode_string: String): String;
Function f_display_ansi_string(Var pv_ansi_string: AnsiString): String;

Function f_stream_hex_dump(p_c_stream: tStream): String;
Function f_file_hex_dump(p_file_name: String): String;

Implementation
Uses SysUtils
;

Function f_byte_array_to_hex(Var pv_byte_array: t_byte_array; p_count: Integer): String;
Var l_index: integer;
Begin
Result:= '';
For l_index := 0 To p_count- 1 Do
(*$r-*)
Result:= Result + Format('%1x ', [pv_byte_array[l_index]]);
(*$r+*)
End; // f_byte_array_to_hex

Function f_display_unicode_string(Var pv_unicode_string: String): String;
Begin
With t_pt_new_string_header(Integer(pv_unicode_string)- 12)^ Do
Begin
Result:= ' string : >'+ pv_unicode_string+ '< Length: '
+ IntToStr(Length(pv_unicode_string))+ k_new_line
+ Format(' code page %d', [m_code_page])+ k_new_line
+ Format(' element size %d', [m_element_size]) + k_new_line
+ Format(' ref_count %d', [m_reference_count]) + k_new_line
+ Format(' byte count %d', [m_element_count]) + k_new_line
+ ' bytes $ '+ f_byte_array_to_hex(m_bytes,
m_element_count* m_element_size)
End; // with t_pt_new_string_header^
End; // f_display_unicode_string

Function f_display_ansi_string(Var pv_ansi_string: AnsiString): String;
Begin
With t_pt_new_string_header(Integer(pv_ansi_string)- 12)^ Do
Begin
Result:= ' string : >'+ pv_ansi_string+ '<Length: '
+ IntToStr(Length(pv_ansi_string))+ k_new_line
+ Format(' code page %d', [m_code_page])+ k_new_line
+ Format(' element size %d', [m_element_size]) + k_new_line
+ Format(' ref_count %d', [m_reference_count]) + k_new_line
+ Format(' byte count %d', [m_element_count]) + k_new_line
+ ' bytes $ '+ f_byte_array_to_hex(m_bytes,
m_element_count* m_element_size)
End; // with t_pt_new_string_header^
End; // f_display_ansi_string

Function f_stream_hex_dump(p_c_stream: tStream): String;
Var l_index: integer;
l_byte: Byte;
Begin
Result:= '';
p_c_stream.Position:= 0;
For l_index:= 0 To p_c_stream.Size- 1 Do
Begin
p_c_stream.Read(l_byte, 1);
Result:= Result + Format('%1x ', [l_byte]);
End; // for l_index
End; // f_byte_array_to_hex

Function f_file_hex_dump(p_file_name: String): String;
Var l_c_file_stream: tFileStream;
Begin
l_c_file_stream:= tFileStream.Create(p_file_name, fmOpenRead);
Result:= f_stream_hex_dump(l_c_file_stream);
l_c_file_stream.Free;
End; // f_file_hex_dump

End. //



Et voici un programme qui manipule des String (donc UnicodeString)

  • nous avons plac駸 diff駻ents contr?les de visualisation
    • notre tMemo, en police "Courrier New" pour les explications
    • un tRichEdit, une tListbox, un tEdit et un tMemo, qui, par d馭aut, sont tous en police "Tahoma"
  • deux proc馘ures nous permettent d'afficher le contenu de cha?nes :

    Procedure display(p_text: String);
    Begin
    Form1.Memo1.Lines.Add(p_text);
    End; // display

    Procedure do_display(p_string: String);
    Var l_index: Integer;
    Begin
    With Form1 Do
    Begin
    display('[len='+ IntToStr(Length(p_string))+ '] '+ p_string);
    ListBox1.Items.Add(p_string);
    RichEdit1.Text:= RichEdit1.Text+ p_string;
    Memo2.Lines.Add(p_string);
    Edit1.Text:= Edit1.Text+ p_string;

    display(p_string+ ' '+ f_display_unicode_string(p_string));
    For l_index:= 1 To Length(p_string) Do
    display(Format('%2d ', [l_index])+ p_string[l_index]);
    End;
    End; // do_display

  • et, ? titre d'exemple, un tButton qui affiche la cha?ne "abc 鴃" (e aigu, i circonflexe) :

    Procedure TForm1.display_unicode_string_Click(Sender: TObject);
    Var l_string: String;
    Begin
    do_display('abc 鴃');
    End; // display_unicode_string_Click

  • voici le r駸ultat:

    10_display_unicode_string

Comme notre cha?ne ne comporte que des "code unit"s normaux
  • Length est 馮al au nombre d'駘駑ents
  • la taille en octet est 馮ale ? 2* Length
  • chaque indice retourne bien un caract鑽e ? la position attendue
  • le code page est de 1200, qui est un code attribu? arbitrairement aux cha?nes UnicodeString
Notez aussi que SYSTEM.PAS contient des m騁hodes qui vous permettent de r馗up駻er les informations du prologue des String :

Function StringElementSize(Const S: UnicodeString): Word; overload; Inline;
Function StringCodePage(Const S: UnicodeString): Word; overload; Inline;
Function StringRefCount(Const S: UnicodeString): Longint; overload; Inline;



4.2.3 - Cha?nes avec surrogates

Les cha?nes contenant des surrogates repr駸entent donc des caract鑽es dont le code point est au del? de 64 K.

Y figure, par exemple

U+1D6C0 MATHEMATICAL BOLD CAPITAL OMEGA

qui est, semble-t-il une sorte d'alias de

U+03A9 GREEK CAPITAL LETTER OMEGA

Pour afficher ce symbole, nous pouvons utiliser plusieurs techniques:

  • appeler une fonction qui convertit le code point en String. Nous utilisons une fonction situ馥 dans CHARACTER.PAS:

    Type UCS4Char = Type LongWord;
    Function ConvertFromUtf32(C: UCS4Char): String; Inline

    et

    • UCS4Char est d馭ini dans SYSTEM.PAS comme un entier sur 4 octets
    • ConvertFromUtf32 convertit le code point en 2 code units
  • nous pouvons directement affecter directement #1ドルD6C0 ? une String. Delphi fait automatiquement la conversion
  • nous pouvons stocker les deux code units s駱ar駑ent par stocker #$D835#$DEC0 (nous avons trouv? ces deux valeurs par nos routines d'affichage pr駸ent馥s plus haut)
  • nous pouvons initialiser la taille de la cha?ne ? 2 par SetLength et y stocker s駱ar駑ent #$D835 et #$DEC0
Voici le code correspondant:

Var l_string: String;

// 1D6C0 MATHEMATICAL BOLD CAPITAL OMEGA
do_display('omega ConvertFromUtf32 ', ConvertFromUtf32(1ドルD6C0));

do_display('omega #USC4 ', #1ドルD6C0);

do_display('surrogate ## ', #$D835#$DEC0);

l_string:= #$D835#$DEC0;
do_display('surrogate, via string, # ', l_string);

SetLength(l_string, 2);
l_string[1]:= #$D835;
l_string[2]:= #$DEC0;
do_display('surrogate, via string[n], ', l_string);

// -- the NON SURROGATE omega
do_display('omega, no surrogate, ', #3ドルA9);

Et

11_surrogate_omega

En fait:

  • les analyse m駑oire sont conformes ? nos attentes :
    • Length retourne le nombre de code units (2), qui est aussi la valeur du nombre d'駘駑ents
    • les deux codes units sont $D835 et $DEC0 (c'est par ce dump que nous les avons trouv馥s), et, en m駑oire, le code est bien stock? en "little endian"
    • la taille est 4 octets
  • MAIS nous ne voyons pas le "omega" attendu, ? part le dernier qui a 騁? affich? par la valeur non-surrogate U+O3A9
La raison est que sur ce PC, l'ancienne m馗anique XP n'est pas capable, avec les polices sur mon PC d'afficher le surrogate.

Le m麥e .EXE a 騁? envoy? sur Windows 7 qui est sur le PC que nous avions du acheter fin 2009 pour g駻er notre site internet, et voici le r駸ultat :

surrogage_omega_windows_7.png

et:

  • le surrogate om馮a appara?t correctement (en Tahoma, et m麥e en Courrier New), sauf dans le tRichEdit, qui ne consent ? afficher que le om馮a non-surrogate
  • nous constatons de plus que le dessin du "omega surrogate math駑atique" est l馮鑽ement diff駻ent du "omega non surrogage"


Pour conna?tre la le nombre de "glyphes", nous pouvons utiliser (SYSUTILS.PAS):

Function ElementToCharLen(Const S: UnicodeString; MaxLen: Integer): Integer; overload

qui retournera bien 1 pour notre surrogate omega.



Pour le fun, voici l'affichage des surrogates "CJK" pr駸ent駸 plus haut :

  • le code:

    Procedure TForm1.cjk_extension_b_Click(Sender: TObject);
    Var l_string: String;
    l_index: Integer;
    Begin
    // 20000 CJK Unified Ideographs Extension B
    l_string:= '';
    For l_index := 0 To 9 Do
    Begin
    l_low_surrogate:= $DC00+ l_index;
    l_string:= l_string+ #$D840+ Chr(l_low_surrogate);
    End;
    do_display('10 cjk ', l_string);
    End; // cjk_extension_b_Click

  • le r駸ultat (Windows 7):

    surrogage_cjk_windows_7.png



CHARACTER.PAS contient toute une s駻ie de fonctions pour tester si un caract鑽e est un surrogate, la partie haute ou basse du surrogate etc.



4.2.4 - Cha?nes avec des composites

Rappelons que les composites sont des caract鑽es "assembl駸" avec des code points s駱ar駸. Par exemple le "e" et l'accent aigu, pour afficher un "?".

Voici un projet qui affiche la version composite et la version non composite de quelques caract鑽es :

Nous constatons que :

  • sur XP, selon les contr?les, l'affichage de composite est plus ou moins r騏ssi
  • le m麥e .EXE sous Windows 7 affiche bien, dans notre exemple, les m麥es caract鑽es


Pour les cha?nes avec des composites
  • ElementToCharLen ne calculera PAS le nombre de caract鑽es affich駸, mais le nombre de code points. Et pour notre caract鑽e composite, la fonction retournera 2, le nombre de code points
  • la RTL ne g鑽e PAS actuellement les caract鑽es composites (pas de version Delphi de NormalizeString).
Sur XP (avec l'API IDN install馥) ou Vista, Windows 7, nous pouvons appeler les API Windows directement.



Voici un exemple l'affichage de "?" et "?" en caract鑽e compos? ou s駱ar?:

  • le code:

    Var g_string: String= '';

    Procedure TForm1.display_composite_Click(Sender: TObject);
    Begin
    g_string:= '|鴃|'+ '|'+ #0065ドル+ #0301ドル+ #0069ドル+#0302ドル+ '|';
    do_display(g_string);
    End; // display_composite_Click

  • le r駸ultat (Windows XP) o? vous noterez que le "^" est affich? A COTE du "i":

    39_composite_xp

  • le r駸ultat (Windows 7) o? l'affichage de "?" est correct:

    composite_windows_7



Comme pour les surrogate,
  • le r駸ultat au niveau de l'affichage d駱end donc ici aussi de la version de Windows (.DLLs de conversions, taille de la police: ? titre de comparaison, la .DLL TAHOMA fait environ 340 K sur XP 2003 et plus de 700 K sur Windows 7)


Mentionnons que ces caract鑽es composites sont peu fr駲uents dans nos textes usuels. Seules les personnes int駻ess馥s par traiter les accents s駱ar駑ent du "e" qu'il y a en dessous seront concern馥s. Bref, tr鑚 peu de gens.



4.2.5 - Conclusion String (== UnicodeString)

UnicodeString offre les avantages suivants :
  • utilise le comptage de r馭駻ences et la s駑antique "copy on write"
  • ce type correspond au type de Windows.


4.3 - Char, WideChar

Le type Char est un alias de WideChar, et correspond ? un caract鑽e Unicode cod? sur 2 octets. Il contient donc une seule code unit UTF-16. Un Char ne peut pas contenir de surrogate (le om馮a ou le CFJ extension B). Il peut contenir de composites ("?", soit U+00C9, mais pas #0065ドル+ #0301ドル)

En gros, le type se comporte comme le WideChar sous les versions ant駻ieures ? Delphi 2009.

Un Char est donc toujours une valeur ordinale, qui peut 黎re utilis馥 dans For, Inc, Dec, High(Char) etc



Pour cr馥r un caract鑽e ? partir de son code unit, nous pouvons utiliser :

  • la fonction Chr qui accepte un entier, et retourne le caract鑽e (contrairement ? ce que dit l'aide, le param鑼re n'est pas un Byte mais in Integer)
  • l'op駻ateur #, qui est une abr騅iation pour Chr
  • un surtypage par Char qui provoque la conversion
Var l_caractere: Char;

l_caractere:= Chr(1234ドル);
l_caractere:= #1234ドル;
l_caractere:= Char(1234ドル);



En revanche nous ne pouvons PLUS utiliser un Char dans des ensembles (Set Of), puisque la valeur d'un caract鑽e est cod馥 sur 2 octets, et d駱asse donc 255 qui est la limite Pascal pour un 駘駑ent d'un Set Of. Nous devrons modifier les instructions utilisant IN.



4.4 - pChar et pWideChar

  • pChar est un alias pour un pWideChar, et pointe donc sur un tableau de WideChar (2 octets chacun)


4.5 - Usc4Char

Ce type correspond ? un LongInt pour pouvoir g駻er directement des code points. Il est utilis? dans des primitives de conversion code point <-> Utf-16, comme le montre nos exemples pr馗馘ents

Pour m駑oire, UCS4String est d馭ini comme un Array of UCS4Char (Length retourne donc le nombre de caract鑽es Usc4Char).



4.6 - AnsiString

Si nous souhaitons utiliser les anciens types pour lesquels 1 caract鑽e occupe 1 octet, nous pouvons utiliser AnsiString.

Le type AnsiString

  • utilise le m麥e format que les String
    • 2 octets pour le code page
    • 2 octets pour la taille de l'駘駑ent (appel? "駘駑ent size" par Delphi)
    • 4 octets pour le compte de r馭駻ence
    • 4 octets pour la le nombre de code units (appel駸 "駘駑ent count" par Delphi)
    • le tableau des caract鑽es Ansi suit
  • naturellement
    • le code page d騁ermine ? pr駸ent le v駻itable code page Ansi (et pas le code 1200 attribu? ? Unicode).
    • la taille de chaque 駘駑ent est de 1
    • le tableau est un tableau de caract鑽es cod駸 chacun sur 1 octet


Comme le prologue a la m麥e structure, nous utiliserons le m麥e type t_new_string_header pour afficher le contenu m駑oire :
  • le code pour explorer une AnsiString

    Procedure do_display_ansi_string(p_ansi_string: AnsiString);
    Var l_index: Integer;
    Begin
    With Form1 Do
    Begin
    ListBox1.Items.Add(p_ansi_string);
    RichEdit1.Text:= RichEdit1.Text+ p_ansi_string;
    Memo2.Lines.Add(p_ansi_string);
    Edit1.Text:= Edit1.Text+ p_ansi_string;

    display(p_ansi_string+ ' '+ f_display_ansi_string(p_ansi_string));
    For l_index:= 1 To Length(p_ansi_string) Do
    display(Format('%2d ', [l_index])+ p_ansi_string[l_index]);
    End; // with Form1
    End; // do_display_ansi_string

    Var g_ansi_string: AnsiString;

    Procedure TForm1.ansistring_Click(Sender: TObject);
    Begin
    g_ansi_string:= 'abc 鴃';
    do_display_ansi_string(g_ansi_string);
    End; // ansistring_Click

  • et le r駸ultat:

    31_ansistring



4.6.1 - Code page

Par d馭aut, le code page est celui d馭ini dans le panneau de configuration. Dans notre cas, c'est le code 1252.



Nous pouvons imposer un autre code page, en le sp馗ifiant comme param鑼re de AnsiString:

  • le code est le suivant:

    Type t_cyrillic_string= Type Ansistring(1251);

    Procedure TForm1.generate_cyrillic_Click(Sender: TObject);
    Var l_cyrillic_string: t_cyrillic_string;
    l_index: Integer;
    l_string: String;
    Begin
    l_cyrillic_string:= '';
    For l_index := 127+ 65 To 127+ 65+ 7 Do
    l_cyrillic_string := l_cyrillic_string + t_cyrillic_string(AnsiChar(l_index));

    ListBox1.Items.Add(l_cyrillic_string);
    RichEdit1.Text:= RichEdit1.Text+ l_cyrillic_string;
    Memo2.Lines.Add(l_cyrillic_string);
    Edit1.Text:= Edit1.Text+ l_cyrillic_string;
    Memo1.Lines.Add(l_cyrillic_string);

    display('');
    display(f_display_ansi_string(AnsiString(l_cyrillic_string)));
    For l_index:= 1 To Length(l_cyrillic_string) Do
    display(
    Format('%2d %1x ', [l_index, Ord(l_cyrillic_string[l_index])])
    + l_cyrillic_string[l_index]);
    End; // generate_cyrillic_Click

  • et le r駸ultat:

    32_ansi_cyrillic

et:
  • les tables Unicode.Org indiquent que les majuscules cyrilliques commencent ? 127+ 65
  • notre cha?ne cyrillique a 騁? construite. C'est une cha?ne de 8 caract鑽es, de 1 octet chacun
  • cette cha?ne est affich馥 correctement en cyrillique dans les tMemo, tEdit, tRichEdit, car Delphi effectue une conversion implicite en UTF-16 lorsque nous affectons notre AnsiString aux propri騁駸 Text ou Lines
  • notre analyse du contenu de cette AnsiString montre bien les donn馥s m駑oire attendues (code page 1251, taille de l'駘駑ent 1, 8 駘駑ents, et les codes attendus)
    En revanche
    • l'affichage de la cha?ne dans la fonction f_display_ansi_string ne pr駸ente pas les caract鑽es escompt?: comme le param鑼re est un param鑼re VAR, la conversion implicite n'a pu avoir lieu
    • l'affichage caract鑽e ? caract鑽e pr駸ente bien le code escompt?, mais le caract鑽e affich? n'est pas cyrillique, car l_cha?ne_cyrillique[indice] est un AnsiChar et son affichage n'est pas converti en Char
  • nous avons du surtyper le param鑼re de f_display_ansi_string par AnsiString, sinon le contenu des octets aurait 騁? diff駻ent


En fait, pour 黎re 黎re absolument certains que nous affichons les caract鑽es m駑oire, il est plus simple de surtype par des types non String, comme un Byte^, ce qui 騅ite toute conversion implicite. Par exemple, nous pouvons afficher le contenu de la AnsiString par:

Procedure TForm1.display_hex_Click(Sender: TObject);
Var l_cyrillic_string: t_cyrillic_string;
l_index: Integer;
l_pt_byte_array: t_pt_byte_array;
Begin
l_cyrillic_string := '';
For l_index := 127+ 65 To 127+ 65+ 7 Do
l_cyrillic_string:= l_cyrillic_string + t_cyrillic_string(AnsiChar(l_index));
l_pt_byte_array:= t_pt_byte_array(Integer(l_cyrillic_string)- 12);
display(f_byte_array_to_hex(l_pt_byte_array^, 12+ 8));
End; // display_hex_Click

qui fournit:

33_ansi_cyrillic_hex



Et si nous souhaitons afficher ou g駻er la cha?ne cyrillique, il aurait 騁? plus simple de la convertire explicitement par une affectation ? une String :

Procedure TForm1.convert_cyrillic_Click(Sender: TObject);
Var l_cyrillic_string: t_cyrillic_string;
l_index: Integer;
l_string: String;
Begin
l_cyrillic_string := '';
For l_index := 127+ 65 To 127+ 65+ 7 Do
l_cyrillic_string:= l_cyrillic_string + t_cyrillic_string(AnsiChar(l_index));
l_string:= l_cyrillic_string;
do_display(l_string);
End; // convert_cyrillic_Click

qui fournit:

34_ansi_cyrillic_convert

Ici, nous avons construit la cha?ne en utilisant un string AnsiString ayant le code page cyrillique, puis l'avons convertie en UnicodeString pour analyse et affichage



Notez que les conversions entre code page peuvent provoquer des pertes d'information. Voici un exemple o? nous convertissons entre le code page 437 et 1252:

Type t_ansi_ibm_oem= Type AnsiString(437);

Procedure TForm1.convert_code_page_Click(Sender: TObject);
Var l_save_default_system_codepage: Word;
l_ansi_ibm: t_ansi_ibm_oem;
l_ansi_string: AnsiString;
Begin
display('current default Ansi code page is '+ IntToStr(DefaultSystemCodePage));
display('');
l_save_default_system_codepage:= DefaultSystemCodePage;
DefaultSystemCodePage:= 1252;

l_ansi_ibm:= #$C6;
display('ansi_ibm $C6 :'+ l_ansi_ibm+ Format(' code_page %4d Ord $%2x ',
[ StringCodePage(l_ansi_ibm), Ord(l_ansi_ibm[1])]));

l_ansi_string:= l_ansi_ibm;
display('ansi := ibm :'+ l_ansi_string+ Format(' code_page %4d Ord $%2x ',
[ StringCodePage(l_ansi_string), Ord(l_ansi_string[1])]));

l_ansi_ibm:= l_ansi_string;
display('ibm := ansi :'+ l_ansi_ibm+ Format(' code_page %4d Ord $%2x ',
[ StringCodePage(l_ansi_ibm), Ord(l_ansi_ibm[1])]));

DefaultSystemCodePage:= l_save_default_system_codepage;
End; // ansistring_Click

et voici le r駸ultat, qui d駑ontre que 437 -> 1252 -> 427 provoque une perte d'information:

35_convert_code_pages



Si nous avions utilis? une cha?ne UnicodeString, cela ne serait pas arriv?:

Procedure TForm1.convert_unicode_ansi_Click(Sender: TObject);
Var l_ansi_ibm: t_ansi_ibm_oem;
l_unicode_string: String;
Begin
DefaultSystemCodePage:= 1252;

l_ansi_ibm:= #$C6;
display('ansi_ibm $C6 :'+ l_ansi_ibm + Format(' code_page %4d Ord $%2x ',
[StringCodePage(l_ansi_ibm), Ord(l_ansi_ibm[1])]));

l_unicode_string:= l_ansi_ibm;
display('uni:= ibm :'+ l_unicode_string+ Format(' code_page %4d Ord $%2x ',
[ StringCodePage(l_ansi_ibm), f_utf_16_at(l_unicode_string, 1)]));

l_ansi_ibm:= l_unicode_string;
display('ansi := uni :'+ l_ansi_ibm + Format(' code_page %4d Ord $%2x ',
[StringCodePage(l_ansi_ibm), Ord(l_ansi_ibm[1])]));
End; // convert_unicode_ansi_Click

et voici le r駸ultat, qui d駑ontre que 437 -> 1252 -> 427 provoque une perte d'information:

36_convert_code_pages_unicode



4.6.2 - RawByteString

Delphi a d馭ini un type RawByteString dont le code page initial est $FFFF:

Type RawByteString = Type AnsiString($ffff);

Ce type a la propri騁? de prendre le code page de la cha?ne Ansi qui lui est affect?:

Procedure TForm1.raw_byte_string_Click(Sender: TObject);
Var l_ansi_ibm: t_ansi_ibm_oem;
l_ansi_string: AnsiString;
l_raw_byte_string: RawByteString;
Begin
DefaultSystemCodePage := 1252;

l_raw_byte_string:= #$C6;
display('raw:= #$C6 :'+ l_raw_byte_string + Format(' code_page %4d Ord $%2x ',
[StringCodePage(l_raw_byte_string), Ord(l_raw_byte_string[1])]));

l_ansi_ibm := #$C6;
display('ansi_ibm:= $C6 :'+ l_ansi_ibm + Format(' code_page %4d Ord $%2x ',
[StringCodePage(l_ansi_ibm), Ord(l_ansi_ibm[1])]));

l_raw_byte_string:= l_ansi_ibm;
display('raw:= ibm :'+ l_raw_byte_string + Format(' code_page %4d Ord $%2x ',
[StringCodePage(l_raw_byte_string), Ord(l_raw_byte_string[1])]));

l_ansi_string := #$C6;
display('ansi := $C6 :'+ l_ansi_string + Format(' code_page %4d Ord $%2x ',
[StringCodePage(l_ansi_string), Ord(l_ansi_string[1])]));

l_raw_byte_string:= l_ansi_string;
display('raw:= ansi :'+ l_raw_byte_string + Format(' code_page %4d Ord $%2x ',
[StringCodePage(l_raw_byte_string), Ord(l_raw_byte_string[1])]));
End; // raw_byte_string_Click

et voici le r駸ultat, qui d駑ontre que 437 -> 1252 -> 427 provoque une perte d'information:

38_convert_code_pages_raw_byte



4.6.3 - Utf8String

Ce type est d馭ini par

Type UTF8String = Type AnsiString(65001);



Une nouvelle unit? ANSISTRINGS.PAS contient la plupart des proc馘ures et fonctions que nous utilisions jadis pour manipuler les cha?nes Ansi, comportant les noms utilis駸 avant 2009, ainsi qu'une version sp馗ifiant le pr馭ixe "ansi" explicitement:

Function UpperCase(Const S: AnsiString): AnsiString; overload;
Function CompareStr(Const S1, S2: AnsiString): Integer; overload;

Function AnsiUpperCase(Const S: AnsiString): AnsiString; overload;
Function AnsiCompareStr(Const S1, S2: AnsiString): Integer; Inline; overload;

Notez que SYSUTILS.PAS comportait aussi des fonctions Ansi_xxx, comme AnsiUpperCase:

Function AnsiUpperCase(Const S: String): String; overload;

toutefois

  • les m騁hodes de SYSUTILS utilisent un param鑼re String, et pourront 黎re utilis馥 sur des String ou des AnsiString (portage plus facile)
  • ANSISTRING.AnsiUpperCase en revanche force une valeur AnsiString, et est plus efficace sur les cha?nes AnsiString, car elle n'effectue pas de conversion implicite :


4.7 - AnsiChar

Ce type correspond ? un caract鑽e sur 1 octet



4.8 - pAnsiChar

Ce type correspond ? un pointeur ^AnsiChar



4.9 - String[nn] et ShortString

De m麥e les anciens types Pascal String[nn] ou ShortString sont toujours disponibles, et correspondent ? des cha?nes dont la taille maximale est fig馥 par la d馗laration, l'octet 0 contient la taille, et les caract鑽es sont des caract鑽es Ansi



4.10 - WideString

WideString 騁ait auparavant utilis? pour les donn馥s caract鑽es Unicode. Son format est essentiellement le m麥e qu'un BSTR Windows.
WideString est toujours appropri? dans les applications COM.



4.11 - Nouvelles Unit駸, nouvelles primitives

4.11.1 - WINDOWS.PAS et les autres Api Windows

Cette unit? contient toujours les versions "A", et "W", mais ? pr駸ent la version non suffix馥 correspond ? de String, donc des UnicodeString, ou encore les pointeurs de caract鑽es par d馭aut sont des pWideChar:

Function GetModuleHandle(lpModuleName: PWideChar): HMODULE; Stdcall;
Function GetModuleHandleA(lpModuleName: PAnsiChar): HMODULE; Stdcall;
Function GetModuleHandleW(lpModuleName: PWideChar): HMODULE; Stdcall;



4.11.2 - L'Unit? CHARACTER.PAS

Cette unit? contient de nombreuses proc馘ures et fonctions de gestion des cha?nes UnicodeString ansi que des fonctions de conversion (dont nous avons d駛? utilis? quelques unes ci-dessus).

Cette unit? offre deux versions

  • une version sous forme de m騁hode de classe de la classe tCharacter

    Type tCharacter=
    Class sealed
    Public
    Class Function ConvertFromUtf32(C: UCS4Char): String; Static;
    End; // tCharacter

    Var ma_chaine: String;
    ma_chaine:= tCharacter.ConvertFromUtf32(#1ドルD6C0);

  • une version globale.

    Function ConvertFromUtf32(C: UCS4Char): String; Inline;

    Var ma_chaine: String;
    ma_chaine:= ConvertFromUtf32(#1ドルD6C0);




Cette unit? comporte des proc馘ure extr鑪ement importantes pour la gestion des cha?nes unicode, comme par exemple (liste partielle):
  • des fonctions de conversion:

    Function ConvertFromUtf32(C: UCS4Char): String; Inline;
    Function ConvertToUtf32(Const S: String; Index: Integer): UCS4Char; overload; Inline;
    Function ToLower(C: Char): Char; overload; Inline;

    Function GetNumericValue(C: Char): Double; overload; Inline

  • des fonctions de test:

    Function GetUnicodeCategory(C: Char): TUnicodeCategory; overload; Inline;

    Function IsControl(C: Char): Boolean; overload; Inline;
    Function IsDigit(C: Char): Boolean; overload; Inline;
    Function IsLetter(C: Char): Boolean; overload; Inline;
    Function IsLetter(Const S: String; Index: Integer): Boolean; overload; Inline;
    Function IsLetterOrDigit(C: Char): Boolean; overload; Inline;
    Function IsLetterOrDigit(Const S: String; Index: Integer): Boolean; overload; Inline;
    Function IsLower(C: Char): Boolean; overload; Inline;
    Function IsLowSurrogate(C: Char): Boolean; overload; Inline;
    Function IsNumber(C: Char): Boolean; overload; Inline;
    Function IsPunctuation(C: Char): Boolean; overload; Inline;
    Function IsSeparator(C: Char): Boolean; overload; Inline;
    Function IsSymbol(C: Char): Boolean; overload; Inline;
    Function IsWhiteSpace(C: Char): Boolean; overload; Inline;
    Function IsUpper(C: Char): Boolean; overload; Inline;

    Function IsSurrogate(Surrogate: Char): Boolean; overload; Inline;
    Function IsHighSurrogate(C: Char): Boolean; overload; Inline;


Nous avons pr駸ent? les versions avec un param鑼re Char, mais il existe les versions String de ces proc馘ures et fonctions.



4.12 - La Biblioth鑷ue d'ex馗ution (la RTL)

Quelques pr馗isions sur le comportement de la Rtl
  • tStrings : stocke des UnicodeString en interne (reste d馗lar? comme string).
  • tWideStrings est inchang? et utilise WideString (BSTR) en interne. Est donc moins efficace que tStrings
  • tStringStream
    • a 騁? r鳬crit
    • stocke les cha?nes en interne avec un encodage ANSI par d馭aut
    • cet encodage peut 黎re chang?
    • peut 黎re supplant? par tStringBuilder pour la construction de cha?nes
  • gestion des textes .DFM:
    • Delphi 2009 lit toutes les versions de fichiers .DFM pr馗馘entes
    • le texte pass au format UTF-8 SEULEMENT SI les noms de type des composants, des propri騁駸 ou des composants contiennent des caract鑽es non-ASCII-7
    • les cha?nes litt駻ales sont cod馥s (comme auparavant) en utilisant #nnn pour les caract鑽es non-ASCII-7. Par exemple ? est stock? come #233
    • les valeurs litt駻ales pourraient 騅entuellement 黎re cod馥s en UTF-8
    • le format binaire du .DFM pourrait passer en UTF-8 pour les noms de type des composants, des propri騁駸 ou des composants
  • gestion des textes .PAS
    • les sources peuvent rester en ANSI, tant qu'ils ne contiennent pas (identificateur ou cha?nes litt駻ales) de caract鑽es qui ne peuvent 黎re utiliser le code page courant
    • l'馘iteur g鑽e le BOM. Si vous utilisez un gestionnaire de version, assurez-vous qu'il g鑽e aussi UTF-8 et le BOM


4.13 - Conclusion

Pour r駸umer la situation
  • c'est le type UnicodeString, cod? en Utf-16 qui est ? pr駸ent le type String par d馭aut
  • pour ce type, dans l'馗rasante majorit? des cas, 1 caract鑽e= 2 octets. Les cas de caract鑽es surrogates ou composite semble plus relever de l'anecdote que de l'industrie. Mais, naturellement, nous pouvons rencontrer ces cas
  • les principaux probl鑪es surviendront plus au niveau des conversions entre les diff駻ents types de caract鑽es o? des incompatibilit駸 ou des pertes d'informations pourront survenir
  • les autres probl鑪es provenant de l'utilisation erronn馥 de pChar au lieu de pByte rel钁ent plus de code mal 馗rit que de probl鑪es s駑antiques



5 - Migration Unicode en Delphi

5.1 - Migrer vers Delphi 2009, 2010, XE

Les explications pr馗馘entes montrent que la compilation de projets ant駻ieurs ? Delphi 2009 peuvent poser quelques probl鑪es lorsque nous les compilerons et ex馗uterons avec les versions Delphi 2009 et suivantes.

Nous pouvons rencontrer des probl鑪es partout o?, par exemple

  • du code suppose qu'un caract鑽es occupe 1 octet
  • des valeurs caract鑽e litt駻ales utilisent un code page pr馗is
  • des instructions utilisent des primitives applicables que pour les scalaires dont la valeur ne d駱asse pas 255 (Set Of)
Comme la RTL et la VCL ont 騁? enti鑽ement converties un Unicode, il est h駘as impossible de demander au compilateur, par une option, un $IFDEF, une directive, un dialogue de l'IDE, de fonctionner "comme avant". Unicode est enracin? trop profond駑ent dans les librairies pour pouvoir 黎re remplac? ? pied lev? pour un projet particulier.

Nous sommes donc forc駸 d'utiliser Unicode.

Si votre code n'est pas en porte ? faux par rapport aux sp馗ificit駸 Unicode, tout fonctionnera comme avant. C'est, d'apr鑚 notre exp駻ience, le cas pour 70 % des applications. Dans les autre cas, il faudra

  • localiser les parties ? modifier
  • 騅aluer l'ampleur des modifications ? apporter
  • adopter une strat馮ie g駭駻al de migration
  • s駘ectionner les techniques d'adaptation
Nous allons d'abord
  • pr駸enter les points isol駸 qui posent probl鑪e
  • d馗rire les strat馮ies de migrations possibles


5.2 - Les probl鑪es potentiels

Les points ? surveiller
  • les parties du code qui supposent qu'un caract鑽e occupe 1 octet
  • l'utilisation de Set Of Char
  • l'utilisation de pChar
  • les fichiers et flux
  • les conversions implicites et explicites
  • les appels de Dll, les interfaces d'unit駸


5.3 - L'hypoth鑚e 1 caract鑽e= 1 octet

Les traitements usuels n'ont pas besoin de faire une hypoth鑚e sur la taille m駑oire des caract鑽es. Les concat駭ation, insertions, suppressions, indexations, les comparaisons, les fonctions Length, Pos, CompareStr() fonctionnent parfaitement bien quelle que soit la taille. C'est le compilateur que se pr駮ccupe de ce type de consid駻ations.



En revanche, notre code pouvait utiliser, pour acc駘駻er certains traitements, de primitives qui travaillaient fondamentalement sur des caract鑽es de 1 octet. En r饌lit?, ces primitives nous viennent de l'Apple ][. Pour fournir un traitement de texte pleine page sur un ordinateur avec 30 K de m駑oire, une horloge ? 4 KHz et un langage interpr騁er, pour initialiser le tampon du traitement de texte, le Pascal UCSD avait introduit la primitive FillChar :

Fillchar(Var pv; p_nombre: Integer; p_valeur: Byte);

depuis une adresse m駑oire (param鑼re Var sans type) nous remplissions une zone de taille donn馥 avec un octet. De fa輟n similaires, nous avions Move pour d駱lacer des octets plus rapidement que ne l'aurait fait une boucle For.

Nous pouvions utiliser ces primitives sous Delphi 6:

SetLength(g_text, 1000);
FillChar(g_text[1], Length(g_text), ' ');

ou encore, pour extraire une cha?ne ne contenant que des lettres minuscules (Delphi 6) :

Var g_text: String;
l_start_index, l_index, l_length: Integer;
l_copy_count: Integer;
l_result: String;

l_start_index:= l_index;
While (l_index<= Length(g_text)) And (g_text[l_index] In ['a'..'z']) Do
Inc(l_Index);
l_copy_count:= l_index+ 1- l_start_index;
SetLength(l_result, l_copy_count);
If l_copy_count> 0
Then Move(g_text[l_start_index], l_result[1], l_copy_count);



Comme les param鑼res "nombre" de FillChar et Move sont des octets, si nous faisons fonctionner ce code sous Delphi 2009

  • pour FillChar
    • nous n'initialiserons que la moiti? du tampon (Length fournit le nombre de code units Utf-16, pas le nombre d'octets
    • si nous ajustons Length en multipliant par 2 (ou ElementSize), nous remplirons bien le tampon, mais avec des octets ' ', donc 32,ドル et chaque code unit contiendra le code unit 3232,ドル ce qui n'est PAS l'espace (il faudrait remplir avec des mots 馮aux ? 0032,ドル ce que ne sait pas faire FillChar)
    • la fonction StringOfChar retourne une String remplie avec un caract鑽e donn?, et devrait remplacer FillChar pour les String
  • pour Move
    • les indices, le nombre de code units ? copier, la taille finale de la cha?ne r駸ultat sont corrects, mais le Move ne copie ici aussi que la moiti? des caract鑽es
    • si nous multiplions par 2 le nombre d'octets ? copier, le r駸ultat sera ici correct
    • en revanche IN sera refus?, parce que les caract鑽es WideString peuvent d駱asser 255.


Mentionnons au passage la fonction SizeOf qui retourne la taille en OCTETS d'un type ou d'une variable. Pour les cha?nes Unicode, le r駸ultat ne sera naturellement pas le nombre de caract鑽es. Il faudrait pour cela utiliser Length.

Et pour cr馥r une cha?ne ayant une longueur donn馥, c'est SetLength qu'il faut utiliser (depuis longtemps d駛?), plut?t que GetMem qui ne g鑽e naturellement pas le prologue des String.



En r駸um?, seul l'emploi de FillChar ou Move devront 黎re examin駸 de pr鑚 lorsque ces primitives sont utilis駸 sur des String.



5.4 - Utilisation de Set Of Char

Ces instructions sont obsol鑼es, du fait de la limite ? 255 des valeurs autoris馥s ? un Set Of.

Si les caract鑽es que nous testons ont un code inf駻ieur ? 128 (ou que leur code unit est la m麥e que le code de notre code page), le test fonctionnera. 'A', ou '?' seront test駸 correctement. En revanche le test de l'euro ne sera pas correct, car son code unit est 20ドルAC, alors que sont code ansi est 80ドル:

Procedure TForm1.set_of_char_Click(Sender: TObject);
Type t_set_of_ansi_char= Set Of AnsiChar;

Procedure check(p_text: String; p_char: Char; p_ansi_char: AnsiChar; p_set_of_ansi_char: t_set_of_ansi_char);
Var l_result: String;
Begin
If p_char In p_set_of_ansi_char
Then l_result:= ' ok'
Else l_result:= ' no';

l_result:= Format('%-15s $%2x %4x ', [p_text, Word(p_char), Byte(p_ansi_Char)])+ l_result;
display(l_result);
End; // check

Begin // ansistring_Click
check('A in [''A'', ''B'']', 'A', 'A', ['A', 'B']);
check('? in [''?'']', '?', '?', ['?']);
check('? in [''?'']', '?', '?', ['?']);
End; // ansistring_Click

fournira:

56_set_of_char



Plusieures solution

  • utiliser des comparaisons, qui, elles, ne sont pas limit馥s du tout
  • si les tests Set Of sont destin駸 ? un analyseur lexical, la solution est bien souvent la refonte de la m馗anique de test, en utilisant, par exemple
    • un automate d'騁at
    • des expressions r馮uli鑽es (pour lesquelles une librairie a 騁? inclue dans Delphi XE)
  • utiliser des Set Of AnsiChar
  • utiliser des primitives telles que CharInSet :

    Type TSysCharSet = Set Of AnsiChar;

    Function CharInSet(C: AnsiChar; Const CharSet: TSysCharSet): Boolean;
    overload; Inline;
    Function CharInSet(C: WideChar; Const CharSet: TSysCharSet): Boolean;
    overload; Inline;

L'aide sp馗ifie que nous ne pouvons pas utiliser tSysCharSet pour cr馥r un ensemble de caract鑽es Unicode. Et pour cause, la version Unicode convertit le caract鑽e dans sa version Ansi, et compare cette valeur ? l'ensemble Set of AnsiChar que vous avez pass? en param鑼re

Voici un exemple:

Procedure TForm1.char_in_set_Click(Sender: TObject);

Procedure check(p_text: String; p_result: Boolean);
Var l_result: String;
Begin
If p_result
Then l_result:= ' OK'
Else l_result:= ' NO';
display(Format('%-30s ', [p_text])+ l_result);
End; // check

Var l_char: Char;
l_ansi_char: AnsiChar;
l_string: String;
l_ansi_string: AnsiString;

Begin // char_in_set_Click
check('CharInSet(''?'', [''?''])', CharInSet('?', ['?']));
check('l_char:= ''?; ''CharInSet(l_char, [''?''])', CharInSet(l_char, ['?']));

l_ansi_char:= AnsiChar(l_char);
check('l_ansi_char:= AnsiChar(l_char); ''CharInSet(l_ansi_char, [''?''])',
CharInSet(l_ansi_char, ['?']));

l_string:= '?';
l_ansi_string:= l_string;
check('l_string:= ''?''; l_ansi_string:= l_string; ''CharInSet(l_ansi_string[1], [''?''])',
CharInSet(l_ansi_string[1], ['?']));
End; // generate_cyrillic_Click

dont voici le r駸ultat :

57_char_in_set

Notez que:

  • la valeur litt駻ale et la valeur convertie en AnsiString[1] fonctionnent
  • l'utilisation d'un param鑼re Char 馗houe : la fonction a une version avec un param鑼re WideChar, et aucune conversion implicite n'est effectu馥
  • la conversion d'un Char par AnsiChar(mon_char) ne fonctionne pas non plus. En fait, le surtypage par AnsiChar se contente de tronquer la valeur ? 1 octet (l'euro a un unit code de 20ドルAC, AnsiChar(euro) a la valeur $AC, et non pas la valeur 80ドル)
Pour 騅iter les avertissements, nous pouvons forcer l'utilisation d'AnsiChar:

Var l_set_of_char: Set Of AnsiChar; // pas de warning
l_set_of_char:= ['x', 'y', 'z'];
If AnsiChar('y') In charSet // pas de warning
Then //



Ces remarques montrent un probl鑪e qui nous poursuivra dans toutes ces op駻ations de migration:

Il existe aussi une globale LeadBytes qui permettait de tester les valeurs multicaract鑽es MBCS ANSI (test mon_mbcs IN LeadBytes). Ces tests devraient 黎re remplac駸 par la fonction IsLeadChar.



5.5 - Utilisation de pChar

5.5.1 - Les API Windows

Les appels de primitives Windows n馗essitant des caract鑽es se font toutes par des pChar (WINDOWS.PAS ne contient aucune String).

Nous avons d駛? vu que les API Windows traduites en Delphi ont 騁? adapt馥s en Unicode (les versions sans "W" sont Unicode, et il existe encore les versions "A" au besoin)



5.5.2 - pChar et arithm騁ique des pointeurs

En C, les pointeurs permettaient d'acc馘er aux cellules d'un tableau, et, au lieu d'indexer le tableau, le C utilise un pointeur vers le d饕ut du tableau, et incr駑ente ce pointeur pour visiter les cellules successives. Le compilateur C tient compte de la taille de la cellule, pour incr駑enter le pointeur du nombre d'octets correspondant ? la cellule

struct t_compute
{
int total;
float average;
};

t_compute my_compute;
t_compute * my_pt_compute;

my_compute= calloc ( ...) ;
my_pt_compute = & my_compute ;
my_pt_compute = my_pt_compute + 3;



A cause de cette facilit? ? d駸igner n'importe quelle emplacement m駑oire en se d駱la軋nt par "+", certains logiciels utilisent beaucoup les pChar. Or, actuellement, il s'agit de pointeurs de WideChar. Et donc toute l'arithm騁ique est fauss馥.

La solution est 騅idente:



5.5.3 - POINTERMATH

Il existe bien directive $POINTERMATH ON qui autorise le compilateur ? incr駑enter un pointeur typ? quelconque:

Procedure TForm1.pointermath_Click(Sender: TObject);
Var l_integer_array: Array Of Integer;
l_pt_integer: ^Integer;
Begin
SetLength(l_integer_array, 3);
l_integer_array[0]:= 11111111ドル;
l_integer_array[1]:= 22222222ドル;
l_integer_array[2]:= 33333333ドル;
(*$Pointermath On*)
l_pt_integer:= @ l_integer_array[0];
display(IntToHex(l_pt_integer^, 4));
Inc(l_pt_integer);
display(IntToHex(l_pt_integer^, 4));
l_pt_integer:= l_pt_integer+ 1;
display(IntToHex(l_pt_integer^, 4));
(*$Pointermath Off*)
// -- not accepted outside $POINTERMATH l_pt_integer:= l_pt_integer+ 1;
End;

Dans notre exemple, l'incr駑entation de 1 augmente l'adresse du pointeur de la taille de la cellule, ici 4 octets.

Pour les nostalgiques de l'arithm騁ique des pChar, l'unit? SYSTEM.PAS d馭init

{$POINTERMATH ON}
pByte = ^Byte;
{$POINTERMATH OFF}

Nous pr馭駻ons n饌nmoins utiliser des pointeurs vers des types bien d馭inis, et, au besoin, utiliser des calculs d'adresse, comme nous l'avons fait pour analyser le contenu m駑oire dans les exemples ci-dessus.



5.6 - Les fichiers et les flux

Les fichiers peuvent comporter une signature BOM pr馗isant le type de caract鑽e utilis?.

Delphi propose plusieurs primitives pour 馗rire ou lire cette signature



Tout d'abord, les fonctions tStrings.SaveToFile et tStrings.LoadFromFile on un param鑼re optionnel de type tEncoding.

tEncoding



Voici un petit projet qui 馗rit et lit un cha?ne 'A'+ #0045ドル+ #0301ドル+ 'Z' en utilisant les 4 types de codages possibles:

Procedure TForm1.save_to_file_Click(Sender: TObject);
Var l_encoding: String;
l_save_file_name: String;
Begin
With encoding_listbox_ Do
l_encoding:= Items[ItemIndex];
l_save_file_name:= ExtractFilePath(Application.ExeName)+ '\'+ l_encoding+ '.txt';

With TStringList.Create Do
Begin
Text:= 'A'+ #0045ドル+ #0301ドル+ 'Z';

If l_encoding= 'ASCII'
Then SaveToFile(l_save_file_name, TEncoding.ASCII) Else
If l_encoding= 'UTF-8'
Then SaveToFile(l_save_file_name, TEncoding.UTF8) Else
If l_encoding= 'UTF-16 LE'
Then SaveToFile(l_save_file_name, TEncoding.Unicode) Else
If l_encoding= 'UTF-16 BE'
Then SaveToFile(l_save_file_name, TEncoding.BigEndianUnicode);

display(Format('%-10s ', [l_encoding])+ f_file_hex_dump(l_save_file_name));

Free;
End; // with TStringList
End; // save_to_file_Click

et voici le r駸ultat :

50_tencoding

Les nouvelles classes tStreamWrite, tStreamReader ont aussi un param鑼re tEncoding.



Notez que :



Les anciennes primitives Write / Writeln ainsi que Read / et Readln

5.7 - Conversions implicites et explicites

5.7.1 - Conversions Implicites

Les diff駻ents types de cha?nes sont compatibles en affectation. Nous pouvons donc utiliser n'importe quel type, et Delphi effectuera les conversions impos馥 par la syntaxe.

La simple affectation, ou un appel de proc馘ure permet donc de provoquer la conversion.

Rappelons que



5.7.2 - Surtypage

Mentionnons que lorsque nous utilisons des surtypages, Delphi effectue aussi des CONVERSIONS. Cela a exist? depuis Turbo Pascal.

C'est un peu choquant, car le surtypage consiste normalement uniquement ? demander au Compilateur de consid駻er une zone m駑oire avec le format que nous sp馗ifions par le surtypage, et non le type de la d馗laration.

Voici un exemple ou nous surtypons un Integer par un tableau de 4 octets:

Type t_byte_array= Packed Array[0..3] Of Byte;
t_pt_byte_array= ^t_byte_array;

Procedure TForm1.surtypage_Click(Sender: TObject);
Var l_integer: Integer;
l_pt_byte_array: t_pt_byte_array;
l_result: String;
l_index: Integer;
Begin
l_integer:= 11223344ドル;
display('@addr $'+ IntToHex(Integer(@l_integer), 4)+ ' value $'+ IntToHex(l_integer, 4));

l_pt_byte_array:= t_pt_byte_array(@l_integer);
l_result:= '@addr $'+ IntToHex(Integer(l_pt_byte_array), 4)+ ' value $';
For l_index:= 0 To 3 Do
l_result:= l_result+ ' '+ IntToHex(l_pt_byte_array^[l_index], 2);
display(l_result);
End; // surtypage_Click

qui fournit le r駸ultat suivant:

52_casting

donc, nous avons bien demand? au compilateur de consid駻er les 4 octets de l'entier comme un tableau de 4 octets (m麥e adresse, m麥e contenu, mais analys? avec un autre format)



5.7.3 - Surtypage / conversion de String

Lorsque nous utilisons le nom de type UnicodeString pour surtyper une cha?ne Ansi, le compilateur effectuera une conversion. Le nom de type joue le m麥e r?le qu'une fonction de conversion. Voici un exemple o? nous cr駮ns une AnsiString, puis affichons le r駸ultat surtyp? par UnicodeString:

Procedure TForm1.string_cast_Click(Sender: TObject);
Var l_ansi_string: AnsiString; pbytes
Begin
l_ansi_string:= 'abc';
do_display(UnicodeString(l_ansi_string));

With t_pt_new_string_header(Integer(l_ansi_string)- 12)^ Do
display(IntToStr(m_code_page));
With t_pt_new_string_header(Integer(UnicodeString(l_ansi_string))- 12)^ Do
display(IntToStr(m_code_page));
End; // ansistring_Click

qui fournit :

53_string_casting

et qui d駑onter que notre AinsiString a bien 騁? transform馥 en une autre structure, tant au point de vue taille que prologue (le code page).



En revanche, le surtypage par pChar et pAnsiChar se comportent comme un "surtypage pur", sans conversion. Donc

Voici un exemple de test pChar et String :

Function f_test_pchar(p_pchar: pChar; Var pv_pchar: pchar): pchar;
Var l_string, l_pv_string, l_result_string: String;
Begin
l_string:= String(p_pchar);
display(l_string);

l_pv_string:= 'pv '+ l_string;
display('param_v '+ String(l_pv_string));
pv_pchar:= pChar(l_pv_string);

l_result_string:= 'resu '+ l_string;
Result:= pChar(l_result_string);
End; // f_test_pchar

Procedure TForm1.pchar_Click(Sender: TObject);
Var l_string: String;
l_pchar, l_pv_pchar, l_result_pchar: pChar;
l_result_string: String;
Begin
l_string:= 'abc';
// OK l_result_pchar:= f_test_pchar(@l_string[1], l_pv_pchar);
l_result_pchar:= f_test_pchar(pChar(l_string), l_pv_pchar);
display(String(l_result_pchar)+ ' param_v '+ String(l_pv_pchar)+ '<');

display(String(f_test_pchar(pChar('abc'), l_pv_pchar))
+ ' param_v '+ String(l_pv_pchar)+ '<');
End; // pchar_Click

dont voici le r駸ultat :

55_pchar_casting

Et voici un exemple similaire avec des pAnsiChar:

Function f_test_p_ansi_char(p_p_ansi_char: pAnsiChar; Var pv_p_ansi_char: pAnsichar): pAnsichar;
Var l_string, l_pv_string, l_result_string: String;
l_pv_ansi_string, l_result_ansi_string: AnsiString;
Begin
l_string:= String(p_p_ansi_char);
display('param '+ l_string);

l_pv_string:= 'pv '+ l_string;
display('param_v '+ String(l_pv_string));
// NO 1stChar pv_p_ansi_char:= pAnsiChar(l_pv_string);
l_pv_ansi_string:= 'pv '+ l_string;
pv_p_ansi_char:= pAnsiChar(l_pv_ansi_string);

l_result_string:= 'resu '+ l_string;
// NO 1st char Result:= pAnsiChar(l_result_string);
l_result_ansi_string:= 'resu '+ l_string;
Result:= pAnsiChar(l_result_ansi_string);
End; // f_test_p_ansi_char

Procedure TForm1.p_ansi_char_call_Click(Sender: TObject);
Var l_string: String;
l_p_ansi_char, l_pv_p_ansi_char, l_result_p_ansi_char: pAnsiChar;
l_result_string: String;
Begin
l_result_p_ansi_char:= f_test_p_ansi_char('abc', l_pv_p_ansi_char);
display(String(l_result_p_ansi_char)+ ' param_v '+ String(l_pv_p_ansi_char)+ '<');

display(String(f_test_p_ansi_char('abc', l_pv_p_ansi_char))+ ' param_v '+ String(l_pv_p_ansi_char)+ '<');
End; // p_ansi_char_call_Click

qui fournit :

56_pansichar_casting



Par cons駲uent, nous devrons donc v駻ifier si le projet ? modifier contient des surtypages, et qu'ils sont corrects en unicode.



5.7.4 - Surtypages obscurs

Les surtypages restent ? utiliser uniquement dans les cas o? ils ne peuvent 黎re 騅it駸.

Il faut 騅iter :



5.8 - Appels de DLL

Fondamentallement les .DLL sont des modules C. Nous pouvons utiliser les types compatibles C, tels que les entiers, les Doubles, le pChar ou les pAnsiChar.

Les String, avant ou apr鑚 Delphi 2009, sont des structures Delphi, et n馗essitent un gestionnaire de m駑oire sp馗ifique. Celui ci peut 黎re import? dans le .DLL en important, comme premi鑽e unit? de la Library, de .DPR et de l'unit? qui utilise la DLL l'unit? SHAREMEM.

Voici une .DLL que nous allons compiler avec Delphi 6:

Library d_d6_echo;
Uses ShareMem, SysUtils;

Function f_d6_echo(p_string_z: pChar): pChar; Stdcall;
Var l_string: String;
Begin
l_string:= p_string_z;
l_string:= IntToStr(Length(l_string))+ ' echo '+ l_string;
l_string:= l_string+ ' '+ IntToStr(Length(l_string));
Result:= pChar(l_string);
End; // f_echo_d6

Function f_d6_string_echo(p_string: String): String; Stdcall;
Var l_string: String;
Begin
l_string:= IntToStr(Length(p_string))+ ' echo '+ p_string;
Result:= l_string+ ' '+ IntToStr(Length(l_string));
End; // f_d6_string_echo

Exports f_d6_echo, f_d6_string_echo;

End



Appeler la .DLL Delphi 6 depuis Delphi 2010 ne pose aucun probl鑪e pour la fonction utilisant des pChar:

Function f_d6_echo(p_string_z: pAnsiChar): pAnsiChar; Stdcall;
External 'd_d6_echo.dll';

Procedure TForm1.static_import_pchar_Click(Sender: TObject);
Var l_ansi_sring: AnsiString;
l_string, l_result: String;
l_p_ansichar, l_pt_result: pAnsiChar;
Begin
display(f_d6_echo('abc'));

l_ansi_sring:= 'abc';
// NO incompatible l_p_ansichar:= pAnsiString(l_ansi_sring);
l_p_ansichar:= @l_ansi_sring[1];
l_pt_result:= f_d6_echo(l_p_ansichar);
l_result:= String(l_pt_result);
display(l_result);
End; // static_import_Click



En revanche, utiliser la fonction avec des param鑼res, des param鑼res variable ou des r駸ultat de fonction String ne semble pas possible



Maintenant, si nous compilons la .DLL avec Delphi 2010, la fonction qui utilise et retourne une String fonctionne correctement



En r駸um?



5.9 - Bases de donn馥s

Avant Delphi 2009, Depuis Delphi 2009


6 - Strat馮ies de migration

Globalement, nous pouvons

6.1 - Evaluation de la situation

La pr駸entation ci-dessus devrait permettre de se faire une id馥 des zones qui peuvent n馗essiter des adaptation, et 騅aluer l'ampleur de ces modifications.

Il est possible d'utiliser des utilitaires qui d騁ectent les zones ? modifier et en pr駸ente la list. Pour cela, il suffit d'analyser le source et recherchent



Un tel utilitaire d'analyse des sources est disponible sur Code Central. En fait il utiliser le Parser de Martin Waldenburg pour rechercher les mots cit駸 ci dessus. Mais cet utilitaire ne fournit pas de diagnostic

Nous utilisons un analyseur d駻iv? de notre analyseur lexical Delphi (non publi?) ? qui nous avons ajout駸 des couches logiques pour d騁ecter les probl鑪es potentiels



6.2 - Essais r馘uits

Une fois que le bilan a 騁? 騁abli, il faut s'assurer que nous savons comment traiter chaque cas. En cas de doute, le mieux est de faire des essais sur des petits programmes de test.

A ce niveau, il est recommand? de brancher tous les avertissements. Nous modifions ces avertissements par "projet | options | compilateur | avertissements":

58_unicode_warnings

Tir? de l'aide, voici Les warnings concernant les cha?nes de caract鑽es:



6.3 - Strat馮ie

6.3.1 - Tout AnsiString

Pour pr駸erver le status quo, il est tentant de tout basculer en AnsiString et AnsiChar. Il est m麥e possible d'utiliser un outil qui le fait automatiquement.

Toutefois



6.3.2 - G駻er les points d'entr馥

Strat馮ie diam騁ralement oppos馥, nous pouvons d馗ider de tout g駻er en Unicode. Si nous fournissons des composants ou des unit駸 ? des utilisateurs qui souhaitent rester en ansi, nous pouvons ajouter des interfaces o? les cha?nes sont en ansi. Un peu la m馗anique utilis馥 par les unit駸 telles que ANSISTRING.PAS de Delphi

Il faudra, de toutes les fa輟ns g駻er en Ansi



6.3.3 - Strat馮ie Mixte

En g駭駻al, ce sera une strat馮ie mixte qui sera adopt馥, en fonction des traitements effectu駸 par les unit駸




7 - T駘馗harger le code source Delphi

Vous pouvez t駘馗harger les projets suivants: Ce .ZIP qui comprend: Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par cons駲uent: Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le r駱ertoire.

La notation utilis馥 est la notation alsacienne qui consiste ? pr馭ixer les identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse. Elle est pr駸ent馥 plus en d騁ail dans l'article La Notation Alsacienne



Comme d'habitude:



8 - Glossaire Unicode



9 - R馭駻ences Unicode




10 - L'auteur

John COLIBRI est passionn? par le d騅eloppement Delphi et les applications de Bases de Donn馥s. Il a 馗rit de nombreux livres et articles, et partage son temps entre le d騅eloppement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de donn馥s, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, ? Paris, en province ou sur site client.
Created: jan-04. Last updated: mar-2020 - 250 articles, 620 .ZIP sources, 3303 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri http://www.jcolibri.com - 2001 - 2020
Retour: Home Articles Formations D騅eloppement Delphi Livres Pascalissime Liens Download
l'Institut Pascal

John COLIBRI

+ Home
+ livres

contacts
plan_du_site
– chercher :

Expert Delphi R駸olution de probl鑪es ponctuels, optimisation, TMA Delphi, audit, migration, r饌lisation de projets, transfert de technologie - T駘 01.42.83.69.36
Formation Delphi xe3 complete L'outil de d騅elopppement, le langage de programmation, les composants, les bases de donn馥s et la programmation Internet - 5 jours
Formation Ecriture de Composants Delphi Creation de composants : 騁apes, packages, composants, distribution - 3 jours
Formation UML et Design Patterns Delphi Analyse et Conception Delphi en utilisant UML et les Design Patterns - 3 jours

AltStyle によって変換されたページ (->オリジナル) /