Introduction au développement sur Windows 64 Bits
Dorénavant, lorsque vous achetez un ordinateur de bureau et ou portable, la version préinstallée de Windows 7 est sans aucun doute la version 64 bits. D’autant plus que les pilotes de périphériques sont désormais également disponibles dans cette version, ce qui n’a pas toujours été le cas.
Alors, en tant que développeur, faut-il faire le pas ?
Mais avant de pouvoir répondre à cette question, il est important de comprendre ce que peux apporter un système 64-bits.
APPORTS ET CONTRAINTES DE WINDOWS 64-BITS
L’apport le plus visible que nous pouvons constater dans la figure suivante est la gestion d’une plus grande quantité de mémoire.
Figure 1 : Différence entre Windows 32 et 64-bits
Avec Windows 64-bits, on dépasse largement la limite des 4GO avec 16 Téra octets de mémoire virtuelle. Si nous devions faire une analogie avec des Km² la figure suivante illustre bien la différence.
Mais attention, 64-bits ne veux pas dire 2 fois plus rapide. C’est l’erreur que tout un chacun pourrait commettre. Il est possible de constater des améliorations de performances avec les entrées/sorties de fichiers car elles gèrent de plus gros blocs de données, de constater une meilleure montée en charge d’un serveur. Mais en fin de compte c’est relativement transparent pour l’utilisateur.
Cependant, si votre application est un peu étriquée avec ses 4GO, alors, il n’y a pas à hésiter, oui vous devez passer au 64-bits.
En tant qu’utilisateur, si vous avez un ordinateur qui possède 4 GO ou plus, pour en profiter il vous faut un Windows 64-bits. Mais rassurez-vous, les applications 32-bits (dont les derniers jeux à la mode) fonctionneront normalement, à l’aide de la technologie Windows On Windows le WOW64.
Cette couche logicielle, isole l’exécution d’applications 32-bits des applications 64-bits.
Figure 3 : Windows on Windows 64-bits
Si vous souhaitez migrer en douceur, il est possible de porter son application de manière incrémentale. C’est utile lorsqu’on a perdu du code source, ou si on utilise des composants tiers qui ne sont pas encore passés au 64-bits. L’application peut alors être composée de modules 32 et 64-bits.
Comme il est impossible de mixer dans le même processus du 32 et 64-bits, il faudra avoir recours à des mécanismes dit "Interprocessus", comme les RPC (Remote procedure call) ou alors un composant COM de type serveur Out-of-process.
Figure 4 : Impossible de mixer des modules 32/64 dans le même processus
INSTALLER LES COMPOSANTS 64 BITS AVEC VISUAL STUDIO
Avant de pouvoir développer en 64-bits il est important de s’assurer que les composants soient installés, comme sur la figure suivante :
Figure 5 : Installation du compilateur Itanium et X64 pour C/C++
La bonne nouvelle pour les développeurs .NET, c’est que rien n’est à installer de spécifique. Lors de l’installation de Visual Studio sur une plate-forme 64-bits, les moteurs d’exécutions .NET (CLR) 32 et 64 seront disponibles par défaut.
Pour les développeurs C/C++ :
- Si vous êtes sur un Windows 32-bits, mais souhaitez compiler des applications 64-bits. Un compilateur et un éditeur de liens 32-bits, seront installés pour cibler du 64-bits.
Si vous êtes déjà sur un Windows 64-bits, un compilateur et un éditeur de liens 64-bits seront installés pour cibler des applications 64-bits, aussi bien qu’un compilateur 32-bits pour cibler du 32-bits.
DEVELOPPER SA PREMIERE APPLICATION 64-BITS
Développement pour la plate-forme .NET
Pour les développeurs .NET, c’est assez simple. En effet, par défaut, lorsque vous créez un nouveau projet, la cible de la plate-forme est configurée à Any CPU comme sur la figure suivante :
Figure 6 : Compilation sur n’importe quelle CPU
Any CPU, implique que l’application fonctionnera indifféremment sur du Windows 32 ou 64-bits. La force de .Net ici, c’est que l’application s’adaptera à la plate-forme.
Mais attention, il existe quelques petits pièges à éviter.
Imaginez par exemple, que vous souhaitiez faire appel à des APIs externes qui ne soient pas développées en .NET, mais en Win32 traditionnel ou en COM. Si vous appelez des API du système d’exploitation, il y a de fortes chances pour qu’elles soient disponibles en 32 et 64-bits. Dans ce cas-là vous pouvez laisser la configuration à Any CPU.
Par contre si vous faites appel à une API tierce, vous devez posséder la version 32 et 64-bits et installer la bonne version sur la bonne plate-forme.
Si vous ne possédez que la version 32-bits, et que vous souhaitiez la déployer indifféremment sur du 32 ou 64-bits, vous devez choisir X86 comme cible, afin de forcer l’utilisation du WOW64.
Figure 7 : Compilation en X86 (32 Bits)
Si vous laissez la compilation à Any CPU et que vous installez l’application sur un Windows 64-bits, celle-ci va sans doute fonctionner correctement dans un premier temps, mais échouera lors de l’appel à l’API, avec un message d’erreur du type :
Figure 8 : Erreur de format d’image
Note :
Si vous ne possédez que la version 64-bits d’une DLL, il faudra choisir X64 à la place de X86, et votre application ne fonctionnera pas sur un Windows 32-bits naturellement.
Comme nous venons de le voir, le code .NET est facilement portable, ce qui change du tout au tout lorsqu’on développe des applications natives en C/C++ comme nous allons le vérifier dans la section suivante.
Développement Natif (C/C++)
La bonne nouvelle, c’est qu’il n’y a aucun besoin de réinventer la roue, lorsqu’on doit tirer profit d’une architecture Windows 64-bits, car c’est la même base de code entre du X86, du X64 et de l’Itanium.
Développer sa première application C/C++ en 64-bits, avec Visual Studio, ce fait de la même manière qu’en 32-bits, il faut tout simplement choisir à la compilation sa plateforme cible. Toutes les librairies (Runtime C, MFC, ATL) sont disponibles dans les deux versions.
Figure 9 : Choix de la plate-forme X64
Mais une application correctement écrite, doit fonctionner de la même manière sur les deux systèmes d’exploitation, exception faite des applications qui requièrent plus de 4GO de mémoire.
En d’autres termes, le même code source, à quelques détails près doit pouvoir se compiler en 32 ou en 64-bits sans effet de bord.
En 32-bits, un modèle d’abstraction de données spécifique nommé ILP32, définit, que les types int, long, float et les pointeurs, ont une longueur de 32-bits. Sur Windows 64-bits cette parité n’existe plus et le modèle appelé LLP64 (ou P64), définit ; pour gagner de la place ; que la taille des types de base reste en 32 bits, alors que la taille des pointeurs grimpe à 64-bits.
Pour aider le développeur dans sa démarche d’avoir un seul code pour plusieurs plates-formes, Microsoft a introduit dans le fichier d’entête nommé BaseTsd.h, de nouveaux types de données dérivés du langage C (DWORD32, DWORD64, DWORD_PTR, LONG_PTR, INT_PTR, POINTER_64 et bien d’autres encore) et de nouvelles fonctions d’aide pour la conversion de pointeur (PtrToInt(), PtrToPtr64() etc..),
Ces nouveautés, doivent pouvoir aider les développeurs, à créer des applications à partir de zéro, plus portables en 32 et 64-bits, à partir du moment où ils suivent un certain nombre de règles simples d’utilisation.
Cependant les problèmes surviennent plus d’un portage d’une application 32-bits existante vers du 64-bits.
Pointeur tronqué :
int pti=42;int *i=&pti;short*w=(short*)((int)i+2);
ou
int *i=&pti;short*w=(short*)((long)i+2);
En 32-bits, ce type de conversion ne pose pas de problème, mais c’est une autre histoire en 64-bits, car ce type de conversion, peut causer des problèmes de pointeur tronqué. Dans le fichier BaseTsd.h, est fourni un ensemble de nouveaux types de données dit "pointer-precision" ou polymorphique, qui adapte sa taille à la plate-forme cible.
Type
DWORD_PTR
HALF_PTR
INT_PTR
LONG_PTR
SIZE_T
SSIZE_T
UHALF_PTR
UINT_PTR
ULONG_PTR
Type de données polymorphe
Pour régler le problème c’est assez simple, il suffit d’utiliser le bon type INT_PTR.
int pti=42;int *i=&pti;short*w=(short*)((INT_PTR)i+2);
En règle générale il faudra regarder de plus près toutes les manipulations et opérations sur les pointeurs. Par exemple, si vous aviez l’habitude d’utiliser des nombres "magiques" comme le nombre 4.
size_t values[TAILLE_TABLEAU]; memset(values, TAILLE_TABLEAU * 4, 0);
Il faudra désormais faire appel à sizeof.
size_t values[TAILLE_TABLEAU]; memset(values, TAILLE_TABLEAU * sizeof(size_t), 0);
Certaines API Windows ont été également redéfinit pour prendre en compte ses changements. Par exemple la fonction SetWindowLongPtr est à utiliser plutôt que sa grande sœur SetWindowLong. Pour vous aider à les utiliser, certains indexes comme GWL_WNDPROC, GWL_HWNDPARENT et autres ne sont plus présent dans l’entête Winuser.h.
Le code suivant ne compile plus (erreur C2025 GWL_USERDATE n’est pas définit) :
LONG mesDonnees = donnees; LONG v = SetWindowLong( hWnd, GWL_USERDATA, mesDonnees );
A la place il faut utiliser l’API SetWindowLongPtr avec le nouvel index GWLP_USERDATA :
LONG_PTR mesDonnees=donnees; LONG_PTR v = SetWindowLongPtr(hWnd, GWLP_USERDATA, mesDonnees);
Il existe sans doute également d’autres emplacements dans votre code ou il est possible d’utiliser les types polymorphes à la place des LONG, ULONG, et consort. Par exemple, sous Windows 64-bits, WPARAM, LPARAM, LRESULT, HWND et size_t passent à 64-bits. Dans le fichier d’entête WinDef.h, ils sont désormais déclarés avec des types polymorphes.
/* Types use for passing & returning polymorphic values */ typedef UINT_PTR WPARAM; typedef LONG_PTR LPARAM; typedef LONG_PTR LRESULT;
Et très souvent on convertit size_t en INT ce qui peut engendrer des problèmes dans le mode 64-bits.
Espace d’adressage Virtuel (Virtual Address Space)
VAS (pour faire court) est également une option que vous pouvez explorer lors du passage à du 64 bits. C’est en effet une solution pratique pour réduire le temps de portage de votre application puisque l’on a plus à faire face des problèmes de pointeurs tronqués.
Figure 10 : Réduire l’espace d’adressage virtuel pour une application 64-bits
C’est pratique lorsque, lors de la compilation, vous avez de nombreux warning de type "pointeur tronqué", et que le temps vous manque pour les corriger. Que vous estimez que la complexité de "dés imbriquer" les entiers, des pointeurs, risque de multiplier les défis. Que 2 GO de mémoires c’est suffisant pour l’application. Après tout, les pointeurs auront toujours une taille de 64-bits, mais seuls les 32 premiers bits seront utiles. L’intérêt de cette solution réside également dans le faite que l’application profitera intrinsèquement des améliorations du compilateur 64 bits qui utilise plus de registres et des améliorations de Windows 64-bits lui-même.
Alignement en mémoire
Il est important également de noter que l’alignement des types en mémoire diffère d’une plate-forme à l’autre.
Examinons le code suivant :
struct MaStructure { DWORD count; PVOID ptrs[1]; }; char cb[]="ABCDEF"; MaStructure *s=(MaStructure*)malloc(sizeof(DWORD) + 2*sizeof(PVOID)); s->count =2; s->ptrs[1] =&cb; free(s) ;
en 32 bits
0x00A29F70 cdcdcdcd cdcdcdcd cdcdcdcd fdfdfdfd abababab abababab
|______| |______| |______|
count ptrs[0] ptrs[1]
Après affectation
0x00A29F70 00000002 cdcdcdcd 0043fd04 fdfdfdfd abababab abababab
|______| |______| |______|
count ptrs[0] ptrs[1]
en 64 bits
0x00000000006F7F00 cdcdcdcd cdcdcdcd cdcdcdcd cdcdcdcd cdcdcdcd fdfdfdfd abababab abababab
|_______________| |_______________| |_______________|
count ptrs[0] ptrs[1]
Après affectation
0x00000000006F7F00 00000002 cdcdcdcd cdcdcdcd cdcdcdcd 002ef6b4 00000000 abababab abababab
|_______________| |_______________| |_______________|
count ptrs[0] ptrs[1]
On s’aperçoit ici que l’alignement en 64 bits se fait sur 8 octets, que le membre count de la structure prend 8 octets (et non plus 4) donc que ptrs[1] va empiéter au-delà de la taille réelle de la structure, et donc générer une corruption de la HEAP.
Pour résoudre ce problème, il faut retrouver la position de décalage (offset) ou commencera le membre ptrs de la structure.
MaStructure *s=(MaStructure*)malloc(offsetof(struct MaStructure,ptrs) + 2*sizeof(PVOID))
Note :
Nous pourrions également résoudre le problème en forçant un alignement à 4 à l’aide de #pragma pack(4)
D’autre part il est possible également de gagner de la place en mémoire en ordonnant correctement les éléments d’une structure.
Par exemple la structure suivante aura toujours une taille de 12 octets sur Windows 32-bits quel que soit l’ordre de ses champs.
struct MaStructure { INT A LONG_PTR B INT C } ;
Il en va différemment avec Windows 64-bits. Dans cet ordre la taille de la structure sera de 24 octets, alors qu’en la réordonnant, le champ le plus grand en premier, la taille chute à 16 octets.
struct MaStructure { LONG_PTR B INT A INT C } ;
CONCLUSION
Il est clair que , nous n’avons fait qu’effleurer le passage de 32 à 64-bits. Mais vous l’aurez noté, pour les développeurs .NET, écrire du code qui soit portable sur Windows 32 et 64-bits c’est assez simple.
C’est un peu plus épineux pour les développeurs C/C++, mais tout à fait réalisable rapidement si on suit les recommandations d’utilisation des nouveaux type de données disponibles dans le fichier BaseTsd.h.
C’est plus critique lorsqu’on doit migrer du code 32 vers du 64-bits, spécialement lorsqu’on a utilisé des pratiques courantes, mais pas forcément recommandées. Mais si vous développez une application de zéro, alors il n’y a pas à hésiter.
Pour en savoir plus :
https://msdn.microsoft.com/fr-fr/library/ms241064.aspx
Et je vous recommande la lecture de cet article. “20 issues of porting C++ code on the 64-bit platform”,
Eric Vernié
Comments
- Anonymous
October 10, 2011
Très bon article ! Le modèle mémoire est un choix assez tordu au début et en fait, pour supporter les MFC en 64, MS a choisi un modèle bien précis. Le retour au développement natif. On l'attendait, c'est fait ! Depuis la BUILD, c'est magique. Windows 8, le XAML en natif, le nouveau modèle COM (IInspectable -new!), Metro Style Apps. Et maintenant WinRT, le /CX et toutes ces dlls qui se nommes Windows.GlopGlop.xxxx dans System32... moi je dis viva el C++ renaissance !!!! Bises aux copines de DPE. Bises aux zEric.