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