首页 > 代码库 > 编写二进制兼容的库
编写二进制兼容的库
开发小组公共库的过程中,遇到二进制兼容问题。下面是二进制兼容和代码兼容的具体定义:
A library is binary compatible, if a program linked dynamically to a former version of the library continues running with newer versions of the library without the need to recompile.
If a program needs to be recompiled to run with a new version of library but doesn‘t require any further modifications, the library is source compatible.
举个简单的例子,一个随机数生成库提供2个接口:设置种子set_seed和生成随机数my_rand。
liba.h
#ifndef _LIBA_H#define _LIBA_Hstruct A{ int seed;};int set_seed(struct A* a, int s);int my_rand(struct A a, int b);#endif
liba.c
#include "liba.h"int set_seed(struct A* a, int s){ a->seed = s; return 0;}int my_rand(struct A a, int b){ int r = a.seed + b; return r;}
编译成动态库libcutil.so,调用方代码也很简单:
main.c
#include "liba.h"#include <stdio.h>#include <dlfcn.h>int main(){ void* handle=dlopen("libcutil.so", RTLD_LAZY); if(!handle) { fprintf(stderr, "%s\n", dlerror()); return -1; } struct A a; int (*fa)(struct A*, int); fa = dlsym(handle, "set_seed"); fa(&a, 31); int (*fb)(struct A, int); fb = dlsym(handle, "my_rand"); int b = fb(a, 4); printf("%d\n", b); return 0;}
这样能正常工作:
export LD_LIBRARY_PATH="../api_r1/:$LD_LIBRARY_PATH"; ./main
35
第二版本,在liba.h里面的struct A增加了一个成员:
#ifndef _LIBA_H#define _LIBA_Hstruct A{ int add; //增加了一个成员 int seed;};int set_seed(struct A* a, int s);int my_rand(struct A a, int b);#endif
liba.c几乎不变:
#include "liba.h"int set_seed(struct A* a, int s){ a->add = 123; //增加此行 a->seed = s; return 0;}int my_rand(struct A a, int b){ int r = a.seed + b; return r;}
编译后,生成新的动态库libcutil.so,使用方不重新编译代码,运行:
export LD_LIBRARY_PATH="../api_r2/:$LD_LIBRARY_PATH"; ./main
4
结果错误,这个库libcutil.so二进制不兼容。
原因: 库的头文件liba.h中不但包含了接口,还包含了实现相关的代码(struct A)。使用此库的代码(main.c)include了此头文件,自然就包含了struct A的定义,而libcutil.so完全依赖struct A的定义。不编译客户代码,只替换库时,库中依赖的struct A(最新版)与使用库的代码中struct A(老版)不一致。
解决方案:
原理--头文件中只暴露接口,实现相关的代码全部放在.c/.cpp中,实现与接口分离。
实现--通过一个指针增加一层indirection解耦。
具体有2个方案:
1. C接口使用void*指针指向具体的struct。
#ifndef _LIBA_H#define _LIBA_Htypedef void* Api;int init(Api *api, int s); //初始化int run(Api api, int b);int release(Api api); //释放资源#endif
.c文件包含所有跟实现相关的代码:
#include "liba.h"#include <stdio.h>#include <stdlib.h>struct A{ int seed;};typedef struct A* ApiII;int init(Api* api, int s){ ApiII p = (ApiII)malloc(sizeof(struct A)); if(api == NULL || p == NULL) return -1; p->seed = s; *((ApiII*)api) = p; return 0;}int run(Api api, int b){ if(api == NULL) return -1; ((ApiII)api)->seed+=b; return ((ApiII)api)->seed;}int release(Api api){ if(api != NULL) { free(api); api = NULL; } return 0;}
2. C++接口使用pimpl
#ifndef _LIBA_H#define _LIBA_Hclass A{public: A(); ~A(); int init(int s); int run(int b);private: class Aimpl; //前置申明,在类A中 Aimpl* pimpl; //暴露一个指针,多一层引用};#endif
.cpp代码:
#include "liba.h"#include <stdio.h>class A::Aimpl{public: Aimpl(int s):seed(s){} int run(int b) { seed+=b; return seed; }private: int seed;};A::A():pimpl(NULL){}A::~A(){ if(pimpl != NULL) { delete pimpl; pimpl=NULL; }}int A::init(int s){ if(pimpl != NULL) delete pimpl; pimpl = new A::Aimpl(s); return 0;}int A::run(int b){ if(pimpl == NULL) return -1; return pimpl->run(b);}
附件包含相关代码。
参考文献:
https://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++#Definition
编写二进制兼容的库