同样的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脚本可以判断出该函数这样调用是否可以编译。
但请注意,这种方法并不能直接检测参数的合法性,而是通过编译能否通过来间接判断。