Types de données Windows… pourquoi si redondant / non descriptif?

Quelqu’un pourrait-il s’il vous plaît exactement pourquoi les typedef s / #define s suivants ont été définis? Quelle est leur valeur par rapport aux originaux?

 typedef char CHAR; #define CONST const typedef float FLOAT; typedef unsigned __int64 DWORD64; //A 64-bit "double"-word?! typedef ULONGLONG DWORDLONG; //What's the difference? typedef ULONG_PTR DWORD_PTR; //What's the difference? typedef long LONG_PTR; //Wasn't INT_PTR enough? typedef signed int LONG32; //Why not "signed long"? typedef unsigned int UINT; //Wait.. UINT is "int", "LONG" is also int? typedef unsigned long ULONG; //ULONG is "long", but LONG32 is "int"? what? typedef void *PVOID; //Why not just say void*? typedef void *LPVOID; //What?! typedef ULONG_PTR SIZE_T; //Why not just size_t? 

Et le meilleur de tous:

 #define VOID void //Assuming this is useful (?), why not typedef? 

Quel est le raisonnement derrière ceux-ci? Est-ce une sorte d’abstraction que je ne comprends pas?


Modifier :

Pour les personnes mentionnant la compatibilité croisée du compilateur:

Ma question ne concerne pas la raison pour laquelle ils n’ont pas utilisé unsigned long long au lieu de, disons, DWORD64 . Ma question concerne pourquoi quelqu’un utiliserait-il DWORD64 au lieu de ULONG64 (ou vice-versa)? Les deux typedef n’ont-ils pas une largeur de 64 bits?

Autre exemple: même dans un compilateur “hypothétique” censé nous tromper à tous les égards, quelle serait la différence entre ULONG_PTR et UINT_PTR et DWORD_PTR ? Ces types de données abstraits ne représentent-ils pas uniquement la même chose – SIZE_T ?

Cependant, je demande pourquoi ils ont utilisé ULONGLONG au lieu de long long – existe-t-il une différence de sens potentielle, couverte par ni long long ni DWORDLONG ?

La plupart de ces noms redondants existent principalement pour deux raisons:

  • ils sont des types historiques préservés pour la compatibilité ascendante
  • Il s’agit de noms différents pour le même type, issus d’équipes de développeurs différentes (il peut être étonnamment difficile pour les équipes de restr cohérentes dans un projet aussi gigantesque que Windows).

 typedef char CHAR; 

La signature de char peut varier selon les plates-formes et les compilateurs, ce qui en fait une des raisons. Les développeurs d’origine ont peut-être également gardé cette possibilité ouverte pour toute modification future du codage des caractères, mais bien entendu, cela n’est plus pertinent puisque nous utilisons maintenant TCHAR à cette fin.


 typedef unsigned __int64 DWORD64; //A 64-bit "double"-word?! 

Lors du passage à la version 64 bits, ils ont probablement découvert que certains de leurs arguments DWORD devaient en réalité être longs de 64 bits et ils l’ont probablement renommé DWORD64 afin que les utilisateurs existants de ces API ne soient pas confus.


 typedef void *PVOID; //Why not just say void*? typedef void *LPVOID; //What?! 

Celui-ci remonte aux jours 16 bits, quand il y avait des pointeurs “proches” réguliers qui étaient des pointeurs 16 bits et des points “lointains” qui étaient 32 bits. Le préfixe L types signifie “long” ou “far”, ce qui n’a plus de sens maintenant, mais à l’époque, ceux-ci étaient probablement définis comme suit:

 typedef void near *PVOID; typedef void far *LPVOID; 

Mise à jour: Quant à FLOAT , UINT et ULONG , ce ne sont que des exemples de “plus l’abstraction, c’est bien”, compte tenu des changements à venir. N’oubliez pas que Windows fonctionne également sur des plates-formes autres que x86 – vous pouvez imaginer une architecture dans laquelle les nombres à virgule flottante étaient représentés dans un format non standard et les fonctions de l’API optimisées pour utiliser cette représentation. Cela pourrait alors être en conflit avec le type de données float de C.

Lorsque les fichiers d’en-tête de l’API Windows ont été créés, il y a 25 ans, un int était de 16 bits et un long 32 bits. Les fichiers d’en-tête ont évolué au fil du temps pour refléter les modifications apscopes aux compilateurs et au matériel.

En outre, Microsoft C ++ n’est pas le seul compilateur C ++ qui fonctionne avec les fichiers d’en-tête Windows. Lorsque Microsoft a ajouté le mot clé size_t , tous les compilateurs ne l’ont pas pris en charge. Mais ils pourraient facilement créer une macro, SIZE_T , pour l’exprimer.

En outre, il existe (ou existait) des outils automatisés qui convertissent les fichiers d’en-tête de l’API de C / C ++ vers d’autres langages. Beaucoup de ces outils ont été écrits à l’origine pour fonctionner avec les définitions d’en-tête actuelles (à l’époque). Si Microsoft devait simplement modifier les fichiers d’en-tête pour les rationaliser comme vous le suggérez, nombre de ces outils cesseraient de fonctionner.

Fondamentalement, les fichiers d’en-tête mappent les types Windows à un dénominateur commun afin que plusieurs outils puissent les utiliser. Cela semble parfois être un gâchis, et je suppose que si Microsoft était disposé à éliminer tout semblant de compatibilité ascendante, il pourrait réduire une grande partie du gâchis. Mais cela casserait beaucoup d’outils (sans parler de beaucoup de documentation).

Donc, oui, les fichiers d’en-tête Windows sont parfois un désordre. C’est le prix que nous payons pour l’évolution, la compatibilité ascendante et la capacité de travailler avec plusieurs langues.

Information additionnelle:

Je conviens que, à première vue, toutes ces définitions semblent folles. Mais comme quelqu’un qui a vu les fichiers d’en-tête Windows évoluer au fil du temps, je comprends comment ils sont apparus. La plupart de ces définitions étaient parfaitement logiques lorsqu’elles ont été introduites, même si elles ont maintenant l’air folles. En ce qui concerne les cas ULONGLONG et DWORD64 , j’imagine qu’ils ont été ajoutés pour des DWORD64 de cohérence, car les anciens fichiers d’entête contenaient ULONG et DWORD , les programmeurs s’attendaient donc aux deux autres. En ce qui concerne les raisons pour lesquelles ULONG et DWORD ont été définis alors qu’ils sont identiques, je peux penser à plusieurs possibilités, dont deux sont:

  • Une équipe d’API a utilisé ULONG et une autre, DWORD , et lorsque les fichiers d’en-tête ont été consolidés, ils ont simplement conservé les deux plutôt que de casser le code en convertissant l’un ou l’autre.
  • Certains programmeurs sont plus à l’aise avec ULONG qu’avec DWORD . ULONG implique un type entier sur lequel vous pouvez effectuer des calculs, alors que DWORD implique simplement une valeur générique à 32 bits d’une sorte, généralement une clé, un descripteur ou une autre valeur que vous ne souhaitez pas modifier.

Votre question initiale était de savoir s’il y avait un raisonnement derrière les définitions apparemment folles, ou s’il y a une abstraction qui ne vous manque pas. La réponse simple est que les définitions ont évolué, les changements ayant un sens à l’époque. Il n’y a pas d’abstraction particulière, bien que l’intention soit que si vous écrivez votre code pour utiliser les types définis dans les en-têtes, vous devriez pouvoir le transférer sans problème de 32 bits à 64 bits. En d’autres termes, DWORD sera identique dans les deux environnements. Mais si vous utilisez DWORD pour une valeur de retour lorsque l’API indique que la valeur de retour est HANDLE , vous aurez des problèmes.

Une des raisons est de maintenir une sorte de portabilité entre les compilateurs C.

En particulier DWORD64, en théorie, il vous suffit de modifier la définition de DWORD64 pour que le code soit compilé sur d’autres compilateurs.