2009-02-25 8 views
10

Je travaille actuellement sur des applications multi-plateforme et était tout simplement curieux de savoir comment les autres abordent des problèmes tels que:Quelle est votre façon préférée de gérer le développement multiplateforme?

  • endianess
  • soutien à virgule flottante (certains systèmes émulent dans le logiciel, très lent)
  • I/O systèmes (par exemple l'affichage, le son, l'accès aux fichiers, réseau, etc.)
  • Et bien sûr, la pléthore des différences de compilateur

Il est évident que cela est ciblé sur des langages comme c/C++ qui ne font pas abstraction de la plupart de ces choses (contrairement à java ou C#, qui ne sont pas supportés par beaucoup de systèmes).

Et si vous étiez curieux, les systèmes sur lesquels je travaille sont la Nintendo DS, la Wii, la PS3, la XBox360 et le PC.


EDIT
Il y a eu beaucoup de réponses vraiment bien ici, allant de la façon de gérer les différences vous-même, aux suggestions de la bibliothèque (même la suggestion de simplement donner et à l'aide du vin). Je ne cherche pas vraiment de solution (j'en ai déjà une), mais j'étais curieux de savoir comment les autres s'attaquaient à cette situation car il est toujours bon de voir comment les autres pensent/codent pour continuer à évoluer et à évoluer.

Voici comment j'ai abordé le problème (et, si vous ne l'avez pas deviné dans cette liste de systèmes ci-dessus, je développe des jeux console/windows). Gardez à l'esprit que les systèmes sur lesquels je travaille n'ont généralement pas de bibliothèques multi-plateformes déjà écrites pour eux (Sony recommande en fait d'écrire son propre moteur de rendu à partir de zéro et d'utiliser simplement leur implémentation OpenGL qui ne suit pas normes de toute façon, comme référence).

endianess
Tous nos actifs peuvent être fabriqués sur mesure pour chaque système. Toutes nos données brutes (à l'exception des textures) sont stockées en XML que nous convertissons en un format binaire spécifique au système lors de la construction du projet. Étant donné que nous développons des consoles de jeu, nous n'avons pas besoin de nous soucier du transfert de données entre plates-formes avec des formats différents (seul le PC permet aux utilisateurs de le faire, donc il est également isolé des autres systèmes) .

flottantes
La plupart des systèmes modernes prennent en charge des points ne valeurs à virgule flottante fin, l'exception est la Nintendo DS (et GBA, mais c'est à peu près une plate-forme morte pour nous ces jours-ci). Nous traitons cela à travers 2 classes différentes. La première est une classe de "point fixe" (modèle, peut spécifier quel type d'entier à utiliser et combien de bits pour la valeur décimale) qui implémente tous les opérateurs arithmétiques (en prenant soin des changements de bits) et automatise les conversions de type. La seconde est une classe de "virgule flottante", qui est essentiellement une enveloppe autour du flotteur pour la plupart, la seule différence est qu'elle implémente également les opérateurs de décalage. En implémentant les opérateurs de décalage, nous pouvons ensuite utiliser des changements de bits pour des multiplications/divisions rapides sur la DS, puis passer en toute transparence à des plates-formes qui fonctionnent mieux avec des flottants (comme le XBox360).

E/S Systèmes
Ceci est probablement le problème le plus délicat pour nous, parce que chaque système a leur propre méthode d'entrée de commande, graphiques (XBox360 utilise une variante de DirectX9, PS3 a OpenGL ou vous pouvez écrire votre propre à partir de zéro et la DS et Wii ont leurs propres systèmes propriétaires), le son et la mise en réseau (en réalité, seul le protocole DS diffère de beaucoup, mais ils ont chacun leur propre système de serveur que vous devez utiliser). La façon dont nous avons fini par aborder ceci était simplement d'écrire des wrappers de haut niveau pour chacun des systèmes (par exemple des maillages pour les graphiques, des systèmes de mappage de touches pour les contrôleurs, etc.) et tous les systèmes utilisent les mêmes fichiers d'en-tête. accès. Il suffit ensuite d'écrire des fichiers cpp spécifiques pour chaque plate-forme (formant ainsi «le moteur»).

Différences de compilation
C'est une chose qui ne peut pas être abordé trop facilement, car nous rencontrons des problèmes avec les compilateurs, nous enregistrons habituellement les informations sur un wiki local (pour que les autres peuvent voir ce qu'il faut pour et les solutions de contournement pour aller avec elle) et si possible, écrivez une macro qui va gérer la situation pour nous. Bien que ce ne soit pas la solution la plus élégante, cela fonctionne et vu que certains compilateurs sont simplement cassés à certains endroits, les solutions les plus élégantes ont tendance à casser les compilateurs de toute façon. (Je souhaite juste que tous les compilateurs implémentent la commande "#pragma once" de Microsoft, beaucoup plus facile que de tout empaqueter dans # ifdef)

Répondre

8

Une grande partie de cette complexité est généralement résolue par les bibliothèques tierces (boost étant le plus célèbre) que vous utilisez. On écrit rarement tout à partir de zéro ...

+0

je suis d'accord avec vous, l'utilisation de bibliothèques multi-plateformes est un très bon moyen de résoudre ce problème – thrantir

4

Habituellement, j'encapsule des appels spécifiques au système dans une seule classe. Si vous décidez de porter votre application sur une nouvelle plate-forme, vous n'avez qu'à porter un fichier ...

0

Pour des choses comme l'endianness en utilisant différentes fonctions ou classes, c'est un peu plus lourd. essayez d'utiliser le pré-processeur comme:

#ifdef INTEL_LINUX: 
    code here 
#endif 

#ifdef SOLARIS_POWERPC 
    code here 
#endif 
+0

Faire beaucoup de codage Solaris PowerPC? :) – postfuturist

+0

-1: test pour les fonctionnalités, pas de plates-formes spécifiques – dwc

+0

@ steveth45: Non, je viens de citer un exemple et power pc est big endian par rapport à x86 – Xolve

2

Habituellement, ce genre de problème de portabilité sont laissés au système de construction (autotools ou CMake dans mon cas) qui détectent spécifique du système. Enfin, j'obtiens un config.h de ce système de construction et ensuite je dois juste utiliser la constante définie dans cet entête (en utilisant IF DEFINED).

Par exemple ici est un config.h:

/* Define to 1 if you have the <math.h> header file. */ 
#define HAVE_MATH_H 

/* Define to 1 if you have the <sys/time.h> header file. */ 
#define HAVE_SYS_TIME_H 

/* Define to 1 if you have the <errno.h> header file. */ 
#define HAVE_ERRNO_H 

/* Define to 1 if you have the <time.h> header file. */ 
#define HAVE_TIME_H 

Ensuite, le code ressemblera à ceci (pour time.h par exemple):

#ifdef (HAVE_TIME_H) 
//you can use some function from time.h 
#else 
//find another solution :) 
#endif 
3

J'utilise normalement les bibliothèques multi-plateformes Comme boost ou Qt, ils résolvent environ 95% de mes problèmes avec les codes spécifiques à la plate-forme (j'admets que la seule plate-forme que je gère est win-xp et linux). Pour les 5% restants, j'encapsule habituellement le code spécifique à la plate-forme dans une ou plusieurs classes, en utilisant le modèle d'usine ou la programmation générique pour réduire les sections # ifdef/# endif

1

Pour les formats de données - utilisez du texte brut pour tout. Pour les différences de compilateur, soyez conscient de la norme C++ et faites usage de commutateurs de compilation tels que g ++ -pedantic, qui vous avertira des problèmes de portabilité.

1

Cela dépend du type de choses que vous faites. Une chose qui est presque toujours le bon choix est de porter le contenu de base sur n'importe quelle plate-forme cible, puis de le traiter avec une API commune.Par exemple, je fais beaucoup de codage de calcul numérique, et certaines plateformes ont beaucoup de code cassé/non standard: la façon de le résoudre est de réimplémenter ces fonctions, puis d'utiliser ces nouvelles fonctions partout dans votre code (pour les plateformes qui fonctionnent, la nouvelle fonction appelle simplement l'ancienne).

Mais cela ne fonctionne vraiment que pour les bas niveaux. Pour l'interface graphique, IO de haut niveau, en utilisant une bibliothèque déjà existante est certainement une meilleure option presque à chaque fois.

6

Pour les problèmes endian dans les données chargées à partir de fichiers, intègre une valeur telle que 0x12345678 dans l'en-tête du fichier.

L'objet qui charge les données, examine cette valeur et, s'il correspond à sa représentation interne de la valeur, le fichier contient des valeurs endian natives. La charge est simple à partir de là.

Si la valeur ne correspond pas, alors il s'agit d'un endianisme étranger, le chargeur doit retourner les valeurs avant de les stocker.

3

Je pense que les autres réponses ont fait un excellent travail pour répondre à toutes vos préoccupations, sauf pour l'endianness, donc je vais ajouter quelque chose à ce sujet ... cela devrait seulement être un problème à vos interfaces avec le monde extérieur. Tout votre traitement de données interne devrait être fait dans l'endianness indigène. Lorsque vous communiquez via TCP/IP (ou tout autre protocole de socket), vous devez toujours utiliser des fonctions pour convertir vos valeurs vers et depuis l'ordre des octets du réseau. IIRC, les fonctions sont htons() et htonl(), (hôte de réseau court, hôte de réseau long) et leurs inverses, dont je ne me souviens pas ... peut-être quelque chose comme ntohl(), etc? Le seul autre endroit où vous devez interagir avec des données dont l'ordre des octets est incorrect est la lecture de fichiers à partir de votre disque dur local. Faites en sorte que vos chargeurs de fichiers utilisent des fonctions similaires (vous pouvez même utiliser le réseau les fonctions). En utilisant ces fonctions fournies par la bibliothèque pour gérer l'endianness toujours (utilisez-les même dans du code que vous n'allez jamais mettre en communication, sauf si vous avez une raison impérieuse de ne pas le faire - cela vous facilitera la tâche plus tard.), vous pouvez exécuter le code sur n'importe quelle plate-forme et il "fonctionnera", indépendamment de l'endianness natif.

1

Pour les plates-formes sans support de virgule flottante native, nous avons utilisé un type de point fixe propre et quelques typedefs. Comme ceci:

// native floating points 
typedef float Real; 

ou pour les points fixes quelque chose comme:

typedef FixedPoint_16_16 Real; 

ensuite les fonctions mathématiques peuvent regarder ceci:

Real OurMath::ourSin(const Real& value); 

mise en œuvre réelle peut ofcourse être:

float OurMath::ourSin(const float& value) 
{ 
    return sin(value); 
} 
// for fixed points something more or less trickery stuff 
Questions connexes