-
Notifications
You must be signed in to change notification settings - Fork 163
可移植性的考虑
可移植是软件开发必然考虑和追求的一个目标,软件的最大威力(已经不是效率可以形容的啦^_^)就在于重用,可移植性则是重用的一个重要保证。实现了重用,就意味了已经解决的问题,不用再解决,已经实现的功能,不用再去实现,借助开源、移植、重用、拷贝、分享,一个软件功能一旦实现,理论上就可以服务于整个人类...
-
使用标准 c 语言编写代码
-
使用可移植的标准功能库
-
跟平台相关的部分抽象出接口
-
抽象的可扩展的接口定义
-
尽量少的使用第三方库
使用标准 c 来编写代码有很多好处,有关 c 与 c++ 之间的争论,我们就不提了。但在可移植性方面 c 的好处多多,是客观存在的。原因就在于,几乎在任何平台上,我们都可以找到 c 编译器,但却不一定有 c++ 编译器。做过嵌入式开发的人,应该对此深有体会。另外一个问题,就是 c++ 的类接口的二进制兼容性问题,目前仍然是没有解决方案的。简单的例子,gcc 编译器,可以编译 c++ 代码是吧,可以生成 .a, .so,如果使用 mingw 的 gcc 编译器,可以将 c++ 编译出 .a 或 .dll,看起来没问题?注意 gcc 编译出的 .dll 里面,如果包含了导出的 c++ 的类接口,我们如何在 msvc++ 里面直接使用?不是说没有方法,问题是麻烦。但是如果是用 extern “C” 导出 c 语言的接口,那么 .dll 的二进制兼容问题就解决了。许多项目,比如 ffmpeg 也就是通过 mingw 的 gcc 编译出 dll,然后再 msvc++ 里面使用的,这里面至少所有导出的 dll 接口,都是 c 语言接口。
使用可移植的标准库,举个例子,对于线程,我们是使用 windows 的 thread api(CreateThread CreateEvent)呢,还是用 pthread api 呢?这也是一个移植性的问题,我建议写代码时,都使用 pthread,因为 msvc++ 下有已经有现成的 pthread 的实现,我们只需要把库加进来,就可以编译成功。而多数 linux 平台,都是天生支持 pthread api 的。所以使用 pthread api 来写多线程,在代码可移植性上是有保证的。
我们在做架构设计的时候,跟平台相关的部分,一定要抽象出接口,通过接口来隔离平台差异。接口是不变的,具体的平台做具体的实现,这样才是真正的抽象和重用。fanplayer 项目中,针对音频渲染,抽象出了 adev 接口,针对视频渲染抽象出了 vdev 接口。不同平台,音视频渲染的实现差异是巨大的,windows 下可以用 waveout gdi,linux 下是 alsa,framebuffer,android 下是 AudioTrack,NativeWindow。这就是平台相关,这部分代码,必然要针对具体平台去做实现。抽象出接口的好处就是,除了 adev、vdev 的代码,fanplayer 的其他代码,基本上都是可以重用的。所以,真正做到代码重用的关键,不是什么面向对象的设计,而是抽象和接口。
那么 fanplayer 本身给出的 api 的接口,我们如何设计,才能保证可移植?
void* player_open (char *file, void *win);
void player_close (void *hplayer);
void player_play (void *hplayer);
void player_pause (void *hplayer);
void player_seek (void *hplayer, LONGLONG ms);
void player_setrect (void *hplayer, int type, int x, int y, int w, int h);
int player_snapshot(void *hplayer, char *file, int w, int h, int waitt);
void player_setparam(void *hplayer, DWORD id, void *param);
void player_getparam(void *hplayer, DWORD id, void *param);
可以看到,我们的接口非常简单,一目了然,player_open 多了一个 void *win 的参数,这个就兼容了不同平台,windows 上也许传入的是一个窗口句柄 hwnd,android 上传入的可以是一个 NativeWindow 的指针。主要功能都有对应的接口函数。而 player_setparam 和 player_getparam 则是一个功能扩展的体现。就算是要增加功能,一般情况下可以不用更改,而是通过 player_setparam 和 player_getparam 来实现,这样就保持了代码的兼容性和二进制的兼容性。
为什么要尽量少的使用第三方库?使用第三方库的好处是可以提高开发效率,比如用 sdl 做音视频渲染的库。并且看起来也带来了更好的可移植性,比如 sdl 本身就是跨平台的啊。但是事实上的弊端是,你没法对你的代码做更多的优化,比如音视频渲染,如果我吃透了对应平台的 api,我可以将优化做到极致,但是如果用 sdl 呢?我们难道还去优化 sdl?如果音视频渲染方面,出现了 bug,我们自己实现的代码,所有的一切都是在掌控范围内的,都是 under control 的。如果用了 sdl 呢,如果其本身有 bug 呢?我们还得去 debug sdl?事实上我们多数情况下,都不愿意去看别人的代码,而更愿意看自己的代码。(不管别人的代码多牛逼多伟大也是如此。)另外一个问题就是第三方库的编译和发布问题,也足够你受的了。所以我的结论就是,一旦你多用了一个第三方库(特别是开源的免费的),那么你必然就更多的背上了,未知的风险,性能的负担,编译的负担,发布的负担,等等各种负担。所以尽量少用第三方库,必须要用的时候,也要慎重选择。
fanplayer wiki