我目前正致力于跨平台应用程序,并且对于其他人如何解决以下问题感到好奇:
显然,这是针对像c / c ++这样的语言,它们不会抽象大部分内容(不像java或c#,很多系统都不支持)。
如果你很好奇,我正在开发的系统是Nintendo DS,Wii,PS3,XBox360和PC。
<小时/> 修改
以下是解决问题的方法(如果您没有从上面的系统列表中猜到,我正在开发控制台/ Windows游戏)。请记住,我所使用的系统通常没有为他们编写的跨平台库(索尼实际上建议您从头开始编写自己的渲染引擎,并使用他们的OpenGL实现,这并不完全遵循无论如何,作为参考标准。
字节序
我们所有的资产都可以为每个系统定制。我们所有的原始数据(纹理除外)都存储在XML中,我们在构建项目时将其转换为系统特定的二进制格式。看看我们如何开发游戏机,我们不需要担心在具有不同端序格式的平台之间传输数据(只有PC允许用户这样做,因此,它也与其他系统隔离)
浮点支持
大多数现代系统的浮点值都很好,例外的是Nintendo DS(和GBA,但这对我们来说几乎是一个死的平台)。我们通过2个不同的类来处理它。第一个是“固定点”类(模板化,可以指定要使用的整数类型以及十进制值的多少位),它实现所有算术运算符(处理位移)并自动执行类型转换。第二个是“浮点”类,它基本上只是浮点数的一个包装器,大多数情况下,唯一的区别是它还实现了移位运算符。通过实现移位运算符,我们可以在DS上使用位移进行快速乘法/除法,然后无缝转换到更适合浮点运算的平台(如XBox360)。
I / O系统
这对我们来说可能是最棘手的问题,因为每个系统都有自己的控制器输入方法,图形(XBox360使用DirectX9的变体,PS3有OpenGL,或者你可以从头编写自己的,DS和Wii有自己的专有系统),声音和网络(实际上只有DS在协议上有很多不同,但是它们每个都有自己的服务器系统,你必须使用它)。
我们最终解决这个问题的方法是简单地为每个系统编写相当高级的包装器(例如图形网格,控制器的键映射系统等),并让所有系统使用相同的头文件进行访问。这只是为每个平台编写特定的cpp文件(因此形成“引擎”)。
编译器差异
这是一件无法轻易解决的问题,因为我们遇到了编译器的问题,我们通常会在本地维基上记录信息(因此其他人可以查看要注意的内容以及使用它的变通方法)以及是否可能,写一个宏来处理我们的情况。虽然它不是最优雅的解决方案,它可以工作并看到某些编译器在某些地方被简单地破坏,但更优雅的解决方案往往会破坏编译器。 (我只是希望所有编译器都实现了Microsoft的“#pragma once”命令,比在#ifdef中包装所有内容容易得多)
答案 0 :(得分:8)
大量的这种复杂性通常由您使用的第三方库(提升是最着名的)解决。人们很少从头开始写一切......
答案 1 :(得分:6)
对于从文件加载的数据中的endian问题, 在文件头中嵌入了一个0x12345678等值。
加载数据的对象,查看此值,如果它与值的内部表示匹配,则该文件包含本机endian值。那里的负载很简单。
如果值不匹配,则它是外部字符串,因此加载器需要在存储它们之前翻转它们。
答案 2 :(得分:4)
我通常将系统特定的调用封装在一个类中。如果您决定将应用程序移植到新平台,则只需移植一个文件...
答案 3 :(得分:3)
我通常使用多平台库,比如boost或Qt,它们解决了我处理平台特定代码的95%的问题(我承认我唯一处理的平台是win-xp和linux)。对于剩下的5%,我通常使用工厂模式或通用编程将平台特定代码封装在一个或多个类中,以减少#ifdef / #endif部分
答案 4 :(得分:3)
我认为其他答案在处理除了字节序之外的所有问题方面做得很好,所以我会添加一些内容......它应该只是你外部世界的接口问题。所有内部数据处理都应以本机字节顺序完成。通过TCP / IP(或任何其他套接字协议)进行通信时,应始终使用一些函数将值转换为网络字节顺序。 IIRC,功能是htons()
和htonl()
,(主机网络短,主机到网络长)和它们的反转,我不记得了......也许像ntohl()
,等等?
您应该与具有错误字节顺序的数据交互的唯一其他地方是从本地硬盘驱动器读取文件,因此使您的文件加载器和编写器使用类似的功能(也许您甚至可以使用网络功能)。
通过使用这些库提供的函数始终处理字节序(即使在你从不打算移植的代码中也使用它们,除非你有令人信服的理由不这样做 - 以后当你决定移动时它会变得更容易) ,你可以在任何平台上运行代码,它将“正常工作”,无论本机字节顺序如何。
答案 5 :(得分:2)
通常,这种可移植性问题留给构建系统(在我的情况下是autotools或cmake),它检测特定的系统。最后,我从这个构建系统得到一个config.h然后我只需要在这个头文件中使用常量定义(使用IF DEFINED)。
例如,这是一个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
然后代码看起来像这样(例如对于time.h):
#ifdef (HAVE_TIME_H)
//you can use some function from time.h
#else
//find another solution :)
#endif
答案 6 :(得分:1)
对于数据格式 - 对所有内容使用纯文本。对于编译器差异,请注意C ++标准并使用编译器开关,例如g ++ -pedantic,它会警告您可移植性问题。
答案 7 :(得分:1)
这取决于你正在做的事情。有一点几乎总是正确的选择是将基本内容移植到任何目标平台,然后使用通用API处理它。
例如,我做了很多数值计算编码,有些平台有很多破坏/非标准代码:解决它的方法是重新实现这些功能,然后在代码中的任何地方使用这些新功能(对于有效的平台,新功能只调用旧功能。)
但这只适用于低级别的东西。对于GUI,高级IO,使用现有的库几乎每次都是更好的选择。
答案 8 :(得分:1)
对于没有本机浮点支持的平台,我们使用了一些自己的固定点类型和一些typedef。像这样:
// native floating points
typedef float Real;
或固定点类似:
typedef FixedPoint_16_16 Real;
然后数学函数可能看起来像这样:
Real OurMath::ourSin(const Real& value);
实际执行可能是:
float OurMath::ourSin(const float& value)
{
return sin(value);
}
// for fixed points something more or less trickery stuff
答案 9 :(得分:0)
对于像使用不同函数或类的字节序这样的东西有点过分。尝试使用预处理器,如:
#ifdef INTEL_LINUX:
code here
#endif
#ifdef SOLARIS_POWERPC
code here
#endif