如何在C++代码中兼容不同的API

时间:2024-10-12 07:31:41

同样的API,不同的参数

在一个C/C++程序中,通常都会或多或少地使用第三方库。这些第三方库通常都是使用头文件中,和动态链接库(.so)或者静态连接库(.lib)的形式来提供的。

这些第三方库的API函数,经常会在不同的版本间存在差异。

比如,我们有一个程序hello,使用了一个库libworld。而libworld中的API hello_func(),则有两个版本:

在libworld-1.0中,声明是:

extern int hello_func(const char *msg);

而在libworld-2.0中,函数升级成了:

extern void hello_func(const char *msg, int *ret_code);

如何在一份C/C++代码中,兼容这种API上的差异呢?

解决方案一:库版本

一些好的,或者说比较成熟的库,会有相应的变量,来定义自己的版本号。在我们的程序中,只要比较这种版本号的变量,就可以适配不同的API函数。

比如,GTK库的gtkversion.h中,就定义了如下这些变量:


#define GTK_MAJOR_VERSION (3)


#define GTK_MINOR_VERSION (24)


#define GTK_MICRO_VERSION (43)


#define GTK_BINARY_AGE    (2443)


#define GTK_INTERFACE_AGE (32)

/**
 * GTK_CHECK_VERSION:
 * @major: major version (e.g. 1 for version 1.2.5)
 * @minor: minor version (e.g. 2 for version 1.2.5)
 * @micro: micro version (e.g. 5 for version 1.2.5)
 *
 * Returns %TRUE if the version of the GTK+ header files
 * is the same as or newer than the passed-in version.
 *
 * Returns: %TRUE if GTK+ headers are new enough
 */
#define GTK_CHECK_VERSION(major,minor,micro)                          \
    (GTK_MAJOR_VERSION > (major) ||                                   \
     (GTK_MAJOR_VERSION == (major) && GTK_MINOR_VERSION > (minor)) || \
     (GTK_MAJOR_VERSION == (major) && GTK_MINOR_VERSION == (minor) && \
      GTK_MICRO_VERSION >= (micro)))

通过在我们的源代码中使用GTK_CHECK_VERSION宏,就可以动态地判断出当前依赖的库函数版本,进而使用不同的参数,调用相同的API,或者使用不同的API,实现相同的功能。

比如:

GtkWidget *hbox;

#if GTK_CHECK_VERSION(3, 0, 0)
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
#else
  hbox = gtk_hbox_new (FALSE, 0);
#endif

就实现了如果GTK的版本高于3.0.0,就使用gtk_box_new,否则就使用旧的gtk_hbox_new来创建横向布局盒子的功能。

解决方案二:构建指定

如果库没有定义相应的变量,以至于我们无法在代码中检测到具体的版本信息,也可以预留出相应的变量信息,让编译程序的用户,在构建的时候指定。

比如,我们可以把源代码写成这样:

#ifdef WORLD_IS_1
int ret;

ret = hello_func("hi ...");
#else
int ret;

hello_func("hi ...", &ret);
#endif

就可以在构建阶段,定义一个WORLD_IS_1变量,来编译hello_func的第一个版本。

如使用gcc编译:

gcc -DWORLD_IS_1 -lworld hello.c

解法方案三:使用构建工具检测,以CMake举例

CMake本身不直接提供检测C库中API函数的合法参数的功能。CMake主要是一个构建系统生成器,用于自动化构建过程,包括检测系统环境、生成Makefile、Solution工程或其他构建脚本等。它可以检测系统上是否存在某个库、某个函数是否存在于库中,但不涉及函数参数的检测。

如果需要检测一个C库中API函数的参数,需要依赖其他工具,或编写测试代码来实现。例如,我们可以使用单元测试框架(如C的Check、Google Test等)来编写测试用例,通过调用这些API函数并传入不同的参数来检测其行为是否符合预期。

如果我们的目的是在CMake配置过程中检测某个函数是否支持特定的参数(比如,检测库版本或编译选项导致的API差异),可以编写一个小的测试程序,然后在CMake中使用check_c_source_compiles等命令来编译并运行这个测试程序,从而间接推断出函数参数的合法性。这种方法通常用于环境检测,而不是直接的API参数合法性检测。

CMake的check_c_source_compiles命令,需要包含CheckCSourceCompiles模块。

定义如下:

check_c_source_compiles(<code> <resultVar>
                        [FAIL_REGEX <regex1> [<regex2>...]])

简单来说,就是写一段测试代码,然后加一个返回值。

例如,使用check_c_source_compiles来检测一下hello_func怎么使用:

cmake_minimum_required(VERSION 3.25)

project(hello)

include(CheckCSourceCompiles)

check_c_source_compiles("
#include <world.h>
int main() {
    hello_func('hi ...');
    return 0;
}" WORLD_IS_1)

if(WORLD_IS_1)
    message(STATUS "Function hello_func version is 1.")
else()
    message(STATUS "Function hello_func version is not 1.")
endif()

这段代码尝试编译一个小程序,该程序尝试以一个参数调用hello_func函数。

根据编译结果,CMake脚本可以判断出该函数这样调用是否可以编译。

但请注意,这种方法并不能直接检测参数的合法性,而是通过编译能否通过来间接判断。