首页 > 代码库 > 编写二进制兼容的库

编写二进制兼容的库

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

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

 

编写二进制兼容的库