Cet article a été initialement publié en septembre 2009.

Le support d'Unicode dans Delphi 2009 et après

Après avoir parlé avec de nombreux développeurs Delphi, j’ai l’impression que les changements et les enjeux liés à la nouvelle version du produit de CodeGear sont largement sous-estimés. Ce petit article vise à rectifier le tir et présenter les changements fondamentaux apportés par Delphi 2009 ainsi que les solutions aux problèmes potentiels pouvant survenir lors de la conversion de projets réalisés avec des versions antérieures du produit.

Qu’est-ce que Unicode ? Cette fonctionnalité ne semble pas révolutionnaire.

Détrompez-vous, sans parler de révolution, il s’agit d’un grand pas qui a été franchi par CodeGear. Depuis de nombreuses années, il fallait se baser sur des solutions tierces et/ou effectuer de nombreux bricolages pour manipuler des chaînes représentant des mots non-européens (russes, arabes, japonais, coréens, etc.). Désormais, toute application Delphi sera capable de traiter ces jeux de caractères tant à un niveau visuel (composants de la VCL) que non-visuel (toutes les classes de la RTL). A l’heure actuelle, l’IDE de Delphi 2009 bénéficie lui-même du portage Unicode, tout comme les plug-ins et les divers assistants qui le composent.

Quel est l’encodage utilisé par Delphi ?

Delphi exploite UTF-16 pour représenter les chaînes Unicode. En conséquence, chaque caractère exploite maintenant deux octets en mémoire au lieu d’un.

Comment ca marchait avant ?

Le noyau de Windows NT exploite déjà des chaînes Unicode (d’abord codés en UCS-2, puis UTF-16). Néanmoins, il proposait à des fins de compatibilité deux versions, en général, à chaque API qui prenait des chaînes en entrée. La version ANSI prenait un « A » final (par exemple, « EncryptFileA », et la version Unicode un « W » (par exemple, « EncryptFileW »). Delphi exposait la version neutre, sans décoration finale (« EncryptFile ») et reroutait l’appel vers « EncryptFileA ». Windows convertissait la chaîne ANSI en Unicode et appelait la version Unicode de la fonction, puis convertissait de nouveau le résultat en ANSI et rendait la main au programme. Maintenant, Delphi exploite directement les API Unicode. Les mêmes conversions ont été effectuées pour les types et les structures de données, par exemple TClassInfo était mappé sur la structure CLASSINFOA, c’est désormais un alias sur CLASSINFOW. Au passage, cela signifie qu’il n’est plus possible de produire des applications pour Windows 95, 98 et Me, même en exploitant Microsoft Layer for Unicode (unicows.dll).

Corollaire : tout ira plus vite ?

La conversion ANSI/Unicode n’étant réalisée qu’à l’interface programme/système, il y a peu de chances qu’une amélioration en terme de vitesse soit remarquable. Par contre, vos chaînes étant désormais codées sur la base de deux octets par caractère, il est probable que la mémoire exploitée par vos programmes soit un peu plus importante avec Delphi 2009.

Quel nouveau type de données a été introduit pour gérer les chaînes Unicode ?

Le nouveau type de données destiné à stocker des chaînes Unicode a été nommé UnicodeString. Chaque caractère de la chaîne est de type UnicodeChar, et est maintenant stocké sur 2 octets.

En interne, UnicodeString et AnsiString partagent désormais une nouvelle structure mémoire qui possède dans l’ordre :

  • La page de code (utilisé dans le cas de chaînes ANSI uniquement),
  • La taille d’un élément stocké,
  • Un compteur de référence,
  • La longueur de la chaîne,
  • La chaîne en elle-même,
  • Le terminateur nul final permettant le transtypage vers des chaînes C.

Différentes fonctions de la RTL permettent de contrôler la valeur de ces champs à l’exécution.

Comment exploiter ce nouveau type dans mes applications ?

Concernant les chaînes de caractères, Delphi a toujours exploité un système de mapping de types qui masquera le changement de type effectif, et vous évitera par la même occasion de modifier trop de code. Sous Delphi 2007 et antérieur, les chaînes Pascal longues étaient stockées via le type AnsiString, et le type générique « string » était un simple alias vers ce dernier. Logiquement, le nouveau type de données UnicodeString est désormais dédié au stockage des chaînes Unicode, « string » étant désormais aliasé dessus. Par conséquent, une application n’utilisant que des types « string » sera automatiquement capable de traiter des chaînes Unicode à la recompilation sous Delphi 2009. AnsiString reste toujours exploitable si vous tenez à stocker des chaînes non Unicode dans vos applications.

Qu’en est-il des PChar ?

Le type PChar a été modifié de manière similaire. PChar était mappé sur le type PAnsiChar sous Delphi 2007 et antérieur, PChar est maintenant mappé sur PWideChar.

WideString est toujours exploitable ? Quelle est la différence entre WideString et UnicodeString ?

WideString est le type « historique » permettant le stockage des chaînes Unicode sous Delphi, compatible avec le type COM « BSTR ». Il reste toujours disponible sous Delphi 2009, et est compatible à l’assignement avec UnicodeString. La différence entre les deux types est que WideString est alloué via l’allocateur COM et ne bénéficie pas comme UnicodeString du système purement Delphi de comptage de références permettant le partage et la non-recopie systématique de la mémoire associée à la chaîne. UnicodeString est vraiment le pendant d’AnsiString, il s’agit d’un type dont le cycle de vie est géré par Delphi, censé être rapide et performant à exploiter.

La sémantique s[x] a-t-elle été modifiée ? Quid de Copy, Pos, Delete, et des autres fonctions de manipulation de chaînes ?

Tout a été fait pour que la migration se fasse sans trop de douleurs. Toutes les fonctions usuelles que vous utilisiez possèdent des surcharges offrant le même comportement qu’auparavant. Même les fonctions de manipulation des chaînes AnsiXXX possèdent des surcharges Unicode pour vous éviter de modifier votre code. Quand à l’accès indexé, il se comporte comme auparavant : s[x] renvoie le xième caractère de la chaîne (sauf si le caractère est représenté par plus de deux points de code Unicode, mais dans ce cas c'est une autre histoire...)

Que se passera t-il lorsque j’appellerais des API Windows prenant en entrée des chaînes de caractères ?

Les API Windows dont la « traduction » était effectuée directement dans la VCL ont été mises à jour. En surface, vous ne remarquerez certainement pas de changement. Néanmoins, en interne, les stubs ont été mis à jour vers la version « W », autrement dit, « CreateFile » ne pointe plus vers « CreateFileA » mais plutôt vers « CreateFileW ». Pour le reste - PChar étant mappé sur PWideChar - si vous n’avez pas casté explicitement vos chaines en PAnsiChar, vous ne devriez pas rencontrer de problèmes lors de leur utilisation.

Si je déclare moi-même des procédures externes exploitant des chaînes de caractères implémentés dans des DLLs, que dois-je faire ?

C’est là ou ca se complique. Vous devez être certain que la fonction déclarée supporte des chaînes Unicode pour conserver dans la déclaration des types comme Char ou PChar. Le cas contraire, vous devrez modifier la déclaration de vos procédures en utilisant des types ANSI comme AnsiChar ou PAnsiChar, et effectuer les casts vous-même à l’appel de fonction. Attention, si vous migrez un projet vers Delphi 2009, il n’y a pas de raison pour que des fonctions qui attendaient des PAnsiChar puissent fonctionner sans changement avec des PWideChar. Vous devrez donc reprendre vos déclarations comme décrit précédemment pour éviter que vos librairies tierces se retrouvent par erreur à manipuler des chaînes Unicode. Si vous référencez des API Windows vous-même, n’oubliez pas de changer le « A » final vers « W » pour être lié avec la version Unicode de la librairie.

Quid de l’interopérabilité avec d’autres programmes via des IPC quelconques ?

C’est exactement le même souci : si vous utilisez des chaînes de caractères via PChar, la recompilation de votre programme avec Delphi 2009 supposera que les programmes ou les librairies tierces supportent implicitement Unicode, ce qui n’est certainement pas le cas (du moins, sans changement de nom ou de méthode). Dès que vous « sortez » de Delphi via diverses technologies, soyez sûr que celles-ci permettent d’exploiter des chaînes de caractères Unicode : dans le cas contraire, modifiez les méthodes utilisées ou castez explicitement vos chaînes.

Quels points clés dois-je vérifier avant de passer à Unicode ?

  • Examinez votre code et corrigez tous les fragments qui supposent explicitement que la taille d’un caractère est de 1 octet.
  • Vérifiez que vous ne supposez pas que la longueur d’une chaîne est égale au nombre d’octets qu’elle occupe en mémoire.
  • Lorsque vous écrivez ou vous lisez des chaînes dans des entités de stockage, soyez sûr que celles-ci sont compatibles Unicode, et que les deux premières conditions sont respectées lorsque vous envoyez et récupérez des chaines sous la forme d’un flux d’octets.
  • Toutes les méthodes SaveToFile et LoadFromFile possédent des surcharges prenant en paramètre le type d'encodage à exploiter (via l'objet TEncoding). Utilisez-les pour tirer partie du support d'Unicode (personnellement j'utilise désormais quasiment toujours l'encodage TEncoding.UTF8) !.

Plus votre code est « haut niveau », plus vous serez tranquille. Je ne pense pas par exemple que les développeurs de base de données soient particulièrement touchés par les soucis de migration Unicode. Par contre, ceux d’entre nous qui utilisent Delphi pour réaliser des modules s’interfaçant finement avec le système d’exploitation auront certainement plus de travail à réaliser pour convertir leurs applications sous Delphi 2009.

Je n’ai pas le temps actuellement de migrer mon application vers Unicode mais je souhaiterais bénéficier de certaines fonctionnalités de Delphi 2009. Puis-je « rester en ANSI » temporairement ?

Non. Si vous achetez Delphi 2009 (et les versions ultérieures), vous devrez exploiter Unicode. Il n’y a pas d’option du compilateur ou de directive particulière pour que les chaînes de caractères restent ANSI. Il y a de nombreuses raisons techniques derrière ce choix, décrites sur les blogs des employés de CodeGear (la principale étant que cela obligeait à maintenir deux versions de la VCL, une ANSI et l'autre Unicode). Une solution est de forcer le stockage ANSI en remplacant partout dans votre code « string » par « AnsiString ». Néanmoins, c’est autant fastidieux que susceptible d’introduire de nouveaux bugs.

Quid des composants tierce partie ?

C’est là que le bât blesse. Puisqu’il est acquis que peu de composants fonctionneront correctement sans modifications, un travail inhabituel d’adaptation est demandé à leurs développeurs. Pour certains, celui-ci est déjà achevé (JCL, JVCL…), pour d’autres, il est en cours (DevExpress, à l’heure ou j’écris ces lignes), mais lorsque ceux-ci ont abandonné leur projets, il ne sera pas réalisé. C’est ce dernier cas qui reste problématique et qui risque de faire grincer les dents ceux qui dépendent de librairies non actuellement maintenues par leurs déveleppeurs originaux.