编写二进制兼容的库

    开发小组公共库的过程中,遇到二进制兼容问题。下面是二进制兼容和代码兼容的具体定义:

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_H struct 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_H struct 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_H typedef 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_H class 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

 

你可能感兴趣的:(二进制)