第三方库是如何做到跨平台的

跨平台的库一直被视为减轻程序员工作负担的一大利器。那么这些程序是怎么样做到跨平台的呢。这里我分析了SDL2和Catch2的源码,发现了编写的方法。

首先需要注意的是:以下说的方法都只在GUNC编译器下编译,其他编译器不知道能不能通过


识别当前的操作系统

首先需要知道如何识别当前的操作系统。GNUC编译器会在编译程序的时候定义平台相关的宏。你可以在SDL2和Catch2中看到如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#if (defined(linux) || defined(__linux) || defined(__linux__))
#undef __LINUX__
#define __LINUX__   1
#endif
#if defined(ANDROID) || defined(__ANDROID__)
#undef __ANDROID__
#define __ANDROID__ 1
#endif
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
#undef __FREEBSD__
#define __FREEBSD__ 1
#endif

如果你是Linux系统,那么GNUC会给你宏linux,__linux,__linux__三个宏中的一个。如果你是安卓系统,那么就会给ANDROID,__ANDROID__宏中的一个。 所以跨平台就是根据这些GNUC给出的宏来判断的。根据源码,我们可以知道GUNC会给出如下的宏:

  • linux系统:linux, __linux, __linux__
  • 苹果系统:__APPLE__
    • 苹果电脑:__MACOSX__
    • 苹果手机:__IPHONEOS__
    • 苹果电视:__TVOS__
  • 安卓系统:__ANDROID__
  • windows系统:WIN32, _WIN32, __CYGWIN__, __MINGW32__,__WINDOWS__
  • PSP系统:__PSP__

需要注意的是,苹果系统GNUC只会提供__APPLE__宏。你在判断这个宏之后你还需要包含苹果的头文件AvailabilityMacros.h,TargetConditionals.h,然后继续通过__MACOSX__等宏来判断。 还有很多很多的平台识别宏。这里就不列举了。

声明自己的宏用于方便识别当前系统

当你能够识别当前的操作系统时,你需要定义自己的宏来便于以后自己识别操作系统,和根据系统来进行不同的操作。比如SDL2就是这样做的。在判断是linux系统后,他会定义__LINUX__宏;判断为FreeBSD操作系统之后,会定义__FREEBSD__宏。

通过系统宏来定义系统特定操作

SDL2在这一点上做的很好。它定义了平台宏之后,为每一个平台写了一个config文件(SDL_config_os2.h,SDL_config_android.h等)。然后将这些头文件按照平台包含到一个总的config文件中(SDL_config.h),之后想要利用平台相关特性就可以只包含这个总的config文件了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#if defined(__WIN32__)
#include "SDL_config_windows.h"
#elif defined(__WINRT__)
#include "SDL_config_winrt.h"
#elif defined(__MACOSX__)
#include "SDL_config_macosx.h"
#elif defined(__IPHONEOS__)
#include "SDL_config_iphoneos.h"
#elif defined(__ANDROID__)
#include "SDL_config_android.h"
#elif defined(__PSP__)
#include "SDL_config_psp.h"
#elif defined(__OS2__)
#include "SDL_config_os2.h"
#else
/* This is a minimal configuration just to get SDL running on new platforms. */
#include "SDL_config_minimal.h"
#endif /* platform config */

#ifdef USING_GENERATED_CONFIG_H
#error Wrong SDL_config.h, check your include path?
#endif

在每个系统独立config文件中,通过宏来表示“这个系统能做到什么”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//SDL_config_osx.h
#define HAVE_STDINT_H 1
#define HAVE_LIMITS_H 1
#define HAVE_CTYPE_H 1
#define HAVE_MATH_H 1
#define HAVE_FLOAT_H 1
#define HAVE_SIGNAL_H 1

//SDL_config_windows.h
#define HAVE_CTYPE_H 1
#define HAVE_FLOAT_H 1
#define HAVE_LIMITS_H 1
#define HAVE_MATH_H 1
#define HAVE_SIGNAL_H 1
#define HAVE_STDIO_H 1
#define HAVE_STRING_H 1
#define HAVE_ATOI 1

这里在Macosx中就没有ATOI函数,所以就没有定义HAVE_ATOI宏。而windows里面有了,就可以定义这个宏。 一般都会定义如下的宏:

  • 是否有某个头文件的,比如HAVE_MATH_H
  • 是否有需要用到的函数,比如HAVE_MALLOC
  • 为平台特定函数声明宏,比如HAVE_ATOI

然后你就可以通过这些宏来控制平台头文件和函数了。

同一模块不同系统编写不同的文件

有了以上的准备,当我们编写平台之间差别较大的函数的时候(比如GUI界面需要用到不同系统的API),就可以为不同的平台编写不同的文件。比如SDL的文件组织如下: 屏幕快照 2019-05-27 下午9.04.19.png

这里为线程方面编写了不同系统的文件。为计时器方面编写了不同系统的文件。

总结

跨平台库需要设计者知道各个系统的API,并且有很大的代码量。开发跨平台库实属不易。

updatedupdated2023-06-082023-06-08