您最喜欢的处理跨平台开发的方式是什么?

时间:2022-10-31 12:28:07

I'm currently working on cross-platform applications and was just curious as to how other people tackle problems such as:

我目前正致力于跨平台应用程序,并对其他人如何解决以下问题感到好奇:

  • Endianess
  • Floating point support (some systems emulate in software, VERY slow)
  • 浮点支持(某些系统在软件中模拟,非常慢)

  • I/O systems (i.e. display, sound, file access, networking, etc. )
  • I / O系统(即显示,声音,文件访问,网络等)

  • And of course, the plethora of compiler differences
  • 当然,过多的编译器差异

Obviously this is targeted at languages like c/c++ which don't abstract most of this stuff (unlike java or c#, which aren't supported on a lot of systems).

显然,这是针对像c / c ++这样的语言,它们不会抽象大部分内容(不像java或c#,很多系统都不支持)。

And if you were curious, the systems I'm developing on are the Nintendo DS, Wii, PS3, XBox360 and PC.

如果你很好奇,我正在开发的系统是Nintendo DS,Wii,PS3,XBox360和PC。


EDIT
There have been a lot of really good answers on here, ranging from how to handle the differences yourself, to library suggestions (even the suggestion of just giving in and using wine). I'm not actually looking for a solution (already have one), but was just curious as to how others tackle this situation as it is always good to see how others think/code so you can continue to evolve and grow.

Here's the way I've tackled the problem (and, if you haven't guessed from this list of systems above, I'm developing console/windows games). Please keep in mind that the systems I work on generally don't have cross-platform libraries already written for them (Sony actually recommends that you write your own rendering engine from scratch and just use their OpenGL implementation, which doesn't quite follow the standards anyway, as a reference).

这是我解决问题的方式(如果你没有从上面的系统列表中猜到,我正在开发控制台/ Windows游戏)。请记住,我工作的系统通常没有为他们编写的跨平台库(索尼实际上建议您从头开始编写自己的渲染引擎,并使用他们的OpenGL实现,这并不完全遵循无论如何,作为参考标准)。

Endianess
All of our assets can be custom made for each system. All of our raw data (except for textures) is stored in XML which we convert to a system specific binary format when the project is built. Seeing as how we are developing for game consoles, we don't need to worry about data being transfered between platforms with different endian formats (only the PC allows the users to do this, thus, it is insulated from the other systems as well).

Endianess我们的所有资产都可以为每个系统定制。我们所有的原始数据(纹理除外)都存储在XML中,我们在构建项目时将其转换为系统特定的二进制格式。看看我们如何开发游戏机,我们不需要担心在具有不同端格式的平台之间传输数据(只有PC允许用户这样做,因此,它也与其他系统隔离) 。

Floating point support
Most modern systems do floating point values fine, the exception to this is the Nintendo DS (and GBA, but thats pretty much a dead platform for us these days). We handle this through 2 different classes. The first is a "fixed point" class (templated, can specify what integer type to use and how many bits for the decimal value) which implements all arithmetic operators (taking care of bit-shifts) and automates type conversions. The second is a "floating point" class, which is a basically just a wrapper around the float for the most part, the only difference is that it also implements the shift operators. By implementing the shift operators, we can then use bit shifts for fast multiplications/divisions on the DS and then seamlessly transition to platforms that work better with floats (like the XBox360).

浮点支持大多数现代系统的浮点值都很好,例外的是Nintendo DS(和GBA,但这对我们来说几乎是一个死的平台)。我们通过2个不同的类来处理它。第一个是“固定点”类(模板化,可以指定要使用的整数类型以及十进制值的多少位),它实现所有算术运算符(处理位移)并自动执行类型转换。第二个是“浮点”类,它基本上只是浮点数的一个包装器,大多数情况下,唯一的区别是它还实现了移位运算符。通过实现移位运算符,我们可以在DS上使用位移进行快速乘法/除法,然后无缝转换到更适合浮点运算的平台(如XBox360)。

I/O Systems
This is probably the trickiest problem for us, because every system has there own method for controller input, graphics (XBox360 uses a variant of DirectX9, PS3 has OpenGL or you can write your own from scratch and the DS and Wii have thier own proprietary systems), sound and networking (really only the DS differs in protocol by much, but then they each have their own server system that you have to use).

I / O系统这对我们来说可能是最棘手的问题,因为每个系统都有自己的控制器输入方法,图形(XBox360使用DirectX9的变种,PS3有OpenGL,或者你可以从头编写自己的DS和Wii有他们自己的专有系统),声音和网络(实际上只有DS在协议上有很多不同,但是他们每个人都有自己的服务器系统,你必须使用它)。

The way we ended up tackling this was by simply writing fairly high level wrappers for each of the systems (e.g. meshes for graphics, key mapping systems for controllers, etc.) and having all the systems use the same header files for access. It's then just a matter of writing specific cpp files for each platform (thus forming "the engine").

我们最终解决这个问题的方法是简单地为每个系统编写相当高级的包装器(例如图形网格,控制器的键映射系统等),并让所有系统使用相同的头文件进行访问。然后,只需为每个平台编写特定的cpp文件(从而形成“引擎”)。

Compiler Differences
This is one thing that can't be tackled too easily, as we run into problems with compilers, we usually log the information on a local wiki (so others can see what to look out for and the workarounds to go with it) and if possible, write a macro that will handle the situation for us. While its not the most elegant solution, it works and seeing how some compilers a simply broken in certain places, the more elegant solutions tend to break the compilers anyway. (I just wish all of the compilers implemented Microsoft's "#pragma once" command, so much easier than wrapping everything in #ifdef's)

编译器差异这是一件无法轻易解决的问题,因为我们遇到了编译器的问题,我们通常会在本地维基上记录信息(因此其他人可以查看要注意的内容以及使用它的变通方法)如果可能的话,写一个宏来处理我们的情况。虽然它不是最优雅的解决方案,它可以工作并看到某些编译器在某些地方被简单地破坏,但更优雅的解决方案往往会破坏编译器。 (我只是希望所有编译器都实现了Microsoft的“#pragma once”命令,比在#ifdef中包装所有内容容易得多)

10 个解决方案

#1


8  

A great deal of this complexity is generally solved by the third party libraries (boost being the most famous) you are using. One rarely writes everything from scratch...

很多这种复杂性通常由您使用的第三方库(boost是最着名的)解决。人们很少从头开始写一切......

#2


6  

For endian issues in data loaded from files, embed a value such as 0x12345678 in the file header.

对于从文件加载的数据中的endian问题,在文件头中嵌入了一个值,如0x12345678。

The object that loads the data, look at this value, and if it matches its internal representation of the value, then the file contains native endian values. The load is simple from there.

加载数据的对象,查看此值,如果它与值的内部表示形式匹配,则该文件包含本机端序列值。那里的负载很简单。

If the value does not match, then it is a foreign endianism, so the loader needs to flip the values before storing them.

如果值不匹配,则它是外部字节序,因此加载器需要在存储它们之前翻转值。

#3


4  

I usually encapsulate system-specific calls in a single class. If you decide to port your application to a new platform, you only have to port one file...

我通常将系统特定的调用封装在一个类中。如果您决定将应用程序移植到新平台,则只需移植一个文件...

#4


3  

I normally use multi-platform libraries, like boost or Qt, they solves about the 95% of my problems dealing with platform specific codes (i admit the only platform i-m dealing with are win-xp and linux). For the remaining 5%, I usually encapsulate the platform specific code in one or more classes, using factory pattern or generic programming to reduce the #ifdef/#endif sections

我通常使用多平台库,比如boost或Qt,它们解决了我处理平台特定代码的95%的问题(我承认我唯一处理的平台是win-xp和linux)。对于剩下的5%,我通常使用工厂模式或通用编程将平台特定代码封装在一个或多个类中,以减少#ifdef / #endif部分

#5


3  

I think the other answers have done a great job of addressing all your concerns except for endianness, so I'll add something about that... it should only be a problem at your interfaces to the outside world. All your internal data processing should be done in the native endianness. When communicating via TCP/IP (or any other socket protocol), there are functions you should always use to convert your values to and from network byte order. IIRC, the functions are htons() and htonl(), (host to network short, host to network long) and their inverses, which I can't remember... perhaps something like ntohl(), etc?

我认为其他答案在处理除了字节序之外的所有问题方面做得很好,所以我会添加一些关于它的东西......它应该只是你外部世界的接口问题。所有内部数据处理都应以本机字节顺序完成。通过TCP / IP(或任何其他套接字协议)进行通信时,应始终使用一些函数将值转换为网络字节顺序。 IIRC,函数是htons()和htonl(),(主机到网络短,主机到网络长)和它们的反转,我记不清了......也许像ntohl()之类的东西?

The only other place you should be interacting with data that has the wrong byte order is reading files from your local hard drive, so make your file loaders and writers use similar functions (perhaps you can even get away with using the network functions).

您应该与具有错误字节顺序的数据交互的唯一其他位置是从本地硬盘驱动器读取文件,因此使您的文件加载器和编写器使用类似的功能(也许您甚至可以使用网络功能)。

By using these library-provided functions for dealing with endianness always (use them even in code you never intend to port, unless you have a compelling reason not to -- it'll make like easier later when you decide to port), you can run the code on any platform and it will "just work", regardless of the native endianness.

通过使用这些库提供的函数始终处理字节序(即使在你从不打算移植的代码中也使用它们,除非你有一个令人信服的理由不 - 当你决定移植时它会变得更容易),你可以在任何平台上运行代码,它将“正常工作”,无论本机字节顺序如何。

#6


2  

Usually, this kind of portability problem are left to the build system (autotools or cmake in my case) which detect specific of the system. Finally, I get a config.h from this build system and then I just have to use constant defined in this header (using IF DEFINED).

通常,这种可移植性问题留给构建系统(在我的情况下是autotools或cmake),它检测系统的特定情况。最后,我从这个构建系统得到一个config.h然后我只需要在这个头文件中使用常量定义(使用IF DEFINED)。

For example here is a config.h :

例如,这是一个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

Then the code will look like this (for time.h for example) :

然后代码看起来像这样(例如对于time.h):

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

#7


1  

For data formats - use plain text for everything. For compiler differences, be aware of the C++ standard and make use of compiler switches such as g++ -pedantic, which will warn you of portability problems.

对于数据格式 - 使用纯文本表示所有内容。对于编译器差异,请注意C ++标准并使用编译器开关,例如g ++ -pedantic,它会警告您可移植性问题。

#8


1  

It depends on the kind of things you are doing. One thing which is almost always the right choice is to port the basic stuff to any target platform, and then deal with it with a common API.

这取决于你正在做的事情。有一点几乎总是正确的选择是将基本内容移植到任何目标平台,然后使用通用API处理它。

For example, I do a lot of numerical computation coding, and some platforms have a lot of broken/non standard code: the way to solve it is to reimplement those functions, and then use those new functions everywhere in your code (for platforms which work, the new function just calls the old one).

例如,我做了很多数值计算编码,有些平台有很多破坏/非标准代码:解决它的方法是重新实现这些功能,然后在代码中的任何地方使用这些新功能(对于平台而言)工作,新功能只是调用旧功能)。

But this only really works for low level stuff. For GUI, high level IO, using an already existing library is definitely a better option almost every time.

但这只适用于低级别的东西。对于GUI,高级IO,使用现有的库几乎每次都是更好的选择。

#9


1  

For platforms without native floating point support, we have used some own fixed point type and some typedefs. Like this:

对于没有本机浮点支持的平台,我们使用了一些自己的固定点类型和一些typedef。喜欢这个:

// native floating points
typedef float Real;

or for fixed points something like:

或固定点类似于:

typedef FixedPoint_16_16 Real;

Then math functions may look this:

那么数学函数可能看起来如下:

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

Actual implementation might ofcourse be:

实际执行可能是:

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

#10


0  

For things like endianness using different functions or classes is a bit nore overhead. try using the pre-processor like:

对于像使用不同函数或类的字节序这样的东西有点过分。尝试使用预处理器,如:

#ifdef INTEL_LINUX:
   code here
#endif

#ifdef SOLARIS_POWERPC
   code here
#endif

#1


8  

A great deal of this complexity is generally solved by the third party libraries (boost being the most famous) you are using. One rarely writes everything from scratch...

很多这种复杂性通常由您使用的第三方库(boost是最着名的)解决。人们很少从头开始写一切......

#2


6  

For endian issues in data loaded from files, embed a value such as 0x12345678 in the file header.

对于从文件加载的数据中的endian问题,在文件头中嵌入了一个值,如0x12345678。

The object that loads the data, look at this value, and if it matches its internal representation of the value, then the file contains native endian values. The load is simple from there.

加载数据的对象,查看此值,如果它与值的内部表示形式匹配,则该文件包含本机端序列值。那里的负载很简单。

If the value does not match, then it is a foreign endianism, so the loader needs to flip the values before storing them.

如果值不匹配,则它是外部字节序,因此加载器需要在存储它们之前翻转值。

#3


4  

I usually encapsulate system-specific calls in a single class. If you decide to port your application to a new platform, you only have to port one file...

我通常将系统特定的调用封装在一个类中。如果您决定将应用程序移植到新平台,则只需移植一个文件...

#4


3  

I normally use multi-platform libraries, like boost or Qt, they solves about the 95% of my problems dealing with platform specific codes (i admit the only platform i-m dealing with are win-xp and linux). For the remaining 5%, I usually encapsulate the platform specific code in one or more classes, using factory pattern or generic programming to reduce the #ifdef/#endif sections

我通常使用多平台库,比如boost或Qt,它们解决了我处理平台特定代码的95%的问题(我承认我唯一处理的平台是win-xp和linux)。对于剩下的5%,我通常使用工厂模式或通用编程将平台特定代码封装在一个或多个类中,以减少#ifdef / #endif部分

#5


3  

I think the other answers have done a great job of addressing all your concerns except for endianness, so I'll add something about that... it should only be a problem at your interfaces to the outside world. All your internal data processing should be done in the native endianness. When communicating via TCP/IP (or any other socket protocol), there are functions you should always use to convert your values to and from network byte order. IIRC, the functions are htons() and htonl(), (host to network short, host to network long) and their inverses, which I can't remember... perhaps something like ntohl(), etc?

我认为其他答案在处理除了字节序之外的所有问题方面做得很好,所以我会添加一些关于它的东西......它应该只是你外部世界的接口问题。所有内部数据处理都应以本机字节顺序完成。通过TCP / IP(或任何其他套接字协议)进行通信时,应始终使用一些函数将值转换为网络字节顺序。 IIRC,函数是htons()和htonl(),(主机到网络短,主机到网络长)和它们的反转,我记不清了......也许像ntohl()之类的东西?

The only other place you should be interacting with data that has the wrong byte order is reading files from your local hard drive, so make your file loaders and writers use similar functions (perhaps you can even get away with using the network functions).

您应该与具有错误字节顺序的数据交互的唯一其他位置是从本地硬盘驱动器读取文件,因此使您的文件加载器和编写器使用类似的功能(也许您甚至可以使用网络功能)。

By using these library-provided functions for dealing with endianness always (use them even in code you never intend to port, unless you have a compelling reason not to -- it'll make like easier later when you decide to port), you can run the code on any platform and it will "just work", regardless of the native endianness.

通过使用这些库提供的函数始终处理字节序(即使在你从不打算移植的代码中也使用它们,除非你有一个令人信服的理由不 - 当你决定移植时它会变得更容易),你可以在任何平台上运行代码,它将“正常工作”,无论本机字节顺序如何。

#6


2  

Usually, this kind of portability problem are left to the build system (autotools or cmake in my case) which detect specific of the system. Finally, I get a config.h from this build system and then I just have to use constant defined in this header (using IF DEFINED).

通常,这种可移植性问题留给构建系统(在我的情况下是autotools或cmake),它检测系统的特定情况。最后,我从这个构建系统得到一个config.h然后我只需要在这个头文件中使用常量定义(使用IF DEFINED)。

For example here is a config.h :

例如,这是一个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

Then the code will look like this (for time.h for example) :

然后代码看起来像这样(例如对于time.h):

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

#7


1  

For data formats - use plain text for everything. For compiler differences, be aware of the C++ standard and make use of compiler switches such as g++ -pedantic, which will warn you of portability problems.

对于数据格式 - 使用纯文本表示所有内容。对于编译器差异,请注意C ++标准并使用编译器开关,例如g ++ -pedantic,它会警告您可移植性问题。

#8


1  

It depends on the kind of things you are doing. One thing which is almost always the right choice is to port the basic stuff to any target platform, and then deal with it with a common API.

这取决于你正在做的事情。有一点几乎总是正确的选择是将基本内容移植到任何目标平台,然后使用通用API处理它。

For example, I do a lot of numerical computation coding, and some platforms have a lot of broken/non standard code: the way to solve it is to reimplement those functions, and then use those new functions everywhere in your code (for platforms which work, the new function just calls the old one).

例如,我做了很多数值计算编码,有些平台有很多破坏/非标准代码:解决它的方法是重新实现这些功能,然后在代码中的任何地方使用这些新功能(对于平台而言)工作,新功能只是调用旧功能)。

But this only really works for low level stuff. For GUI, high level IO, using an already existing library is definitely a better option almost every time.

但这只适用于低级别的东西。对于GUI,高级IO,使用现有的库几乎每次都是更好的选择。

#9


1  

For platforms without native floating point support, we have used some own fixed point type and some typedefs. Like this:

对于没有本机浮点支持的平台,我们使用了一些自己的固定点类型和一些typedef。喜欢这个:

// native floating points
typedef float Real;

or for fixed points something like:

或固定点类似于:

typedef FixedPoint_16_16 Real;

Then math functions may look this:

那么数学函数可能看起来如下:

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

Actual implementation might ofcourse be:

实际执行可能是:

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

#10


0  

For things like endianness using different functions or classes is a bit nore overhead. try using the pre-processor like:

对于像使用不同函数或类的字节序这样的东西有点过分。尝试使用预处理器,如:

#ifdef INTEL_LINUX:
   code here
#endif

#ifdef SOLARIS_POWERPC
   code here
#endif