编写二进制兼容的库

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

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

时间: 2024-12-27 03:02:28

编写二进制兼容的库的相关文章

d指针在Qt上的应用及实现(d指针能实现二进制兼容)

Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念.那么为什么d指针能实现二进制兼容呢?为了回答这个问题,首先弄清楚什么是二进制兼容?所谓二进制兼容动态库,指的是一个在老版本库下运行的程序,在不经过编译的情况下,仍然能够在新的版本库下运行:需要经过编译才能在新版本下运行,而不需要修改该程序源代码,我们就说该动态库是源代码兼容的.要使一个dll能达到二进制兼容,对于一个结构,对于一个对象,其数据模型应该不变,若有变动,比如在类中增加数据成员或删除数据成员,其结果肯定影响对象的数据模型,

Qt 中的二进制兼容策略(简而言之就是地址不能变,剩下的就是让地址不变的技巧)

本文翻译自 Policies/Binary Compatibility Issues With C++ 二进制兼容的定义 如果程序从一个以前版本的库动态链接到新版本的库之后,能够继续正常运行,而不需要重新编译,那么我们就说这个库是二进制兼容的. 如果一个程序需要重新编译来运行一个新版本的库,但是不需要对程序的源代码进一步的修改,这个库就是源代码兼容的. 二进制兼容性可以节省很多麻烦.这使得为特定平台分发软件变得更加容易.如果不确保版本之间的二进制兼容性,人们将被迫提供静态链接的二进制文件.静态二

qt之二进制兼容

一.回顾 使用qt2年多了,但是还是觉得很陌生,总是会被qt搞的很紧张,有时候当我自信满满的打开帮助文档,搜索某个已知的类时,由于笔误敲错了一个字母而出现了另外一个类,不过奇怪的是还真有这么一个类,哎!!!我怎么都不知道呢!看来qt的东西还真不是一般的多,随便一个笔误都可能发现新的东西.特别是qt现在已经发布了qt5.7版本,相对于qt4的时代那真是天差地别,就我个人而言,我现在用的是qt5.6.1-1,因为qt5.6版本官方声称维护2年的时间.qt5.6取消了webkit模块,如果需要使用可以

编写自己的代码库(javascript常用实例的实现与封装)

编写自己的代码库(javascript常用实例的实现与封装) 1.前言 大家在开发的时候应该知道,有很多常见的实例操作.比如数组去重,关键词高亮,打乱数组等.这些操作,代码一般不会很多,实现的逻辑也不会很难,下面的代码,我解释就不解释太多了,打上注释,相信大家就会懂了.但是,用的地方会比较,如果项目有哪个地方需要用,如果重复写的话,就是代码沉余,开发效率也不用,复用基本就是复制粘贴!这样是一个很不好的习惯,大家可以考虑一下把一些常见的操作封装成函数,调用的时候,直接调用就好!源码都放在githu

Qt之美(一):d指针/p指针详解(二进制兼容,不能改变它们的对象布局)

Translated  by  mznewfacer   2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持一下Xizhi Zhu,在引用一下Jerry Sun的话,“C++需要宏定义就像需要设计模式一样.也许你不知道,宏是图灵完全(turing complete)的,至少LISP下是这样,C/C++需要宏,几乎所有重要的C/C++库都需要和依赖宏.这些都超过咱们的想象,宏能带给我们所谓语法糖(Synta

【C/C++学院】0725-内存补码分析/补码原码实战/打印整数二进制数据/静态库说明

[送给在路上的程序员] 对于一个开发者而言,能够胜任系统中任意一个模块的开发是其核心价值的体现. 对于一个架构师而言,掌握各种语言的优势并可以运用到系统中,由此简化系统的开发,是其架构生涯的第一步. 对于一个开发团队而言,能在短期内开发出用户满意的软件系统是起核心竞争力的体现. 每一个程序员都不能固步自封,要多接触新的行业,新的技术领域,突破自我. 内存补码分析 #include<stdio.h> #include<stdlib.h> void main3() { //printf

Android源代码使用第三方jar包和android-support-v7-appcompat兼容lib库

下面是我引用了一个小的例子,这个makefile使用到了tct.drm.frameworks.jar,mtk-drm:libs/mtk.drm.frameworks.jar这两个jar包,并且该工程是依赖android-support-v7-appcompat兼容库的. 如果我们想把这个工程放到Android源码下编译,那么就需要加下面标注红色的内容: LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := o

如何编写自己的Arduino库?

一开始写Arduino 的时候很不习惯,没有main函数,因为好多东西都被隐藏了.一直想搞清楚,以便编写自己的库文件.于是研究一下午,下面是一些总结. Arduino工程的初步认识 一.目录规范 当你创建一个空的工程,先按下ctrl+s保存一下.这个时候弹出对话框,命名工程.假如命名为LED,并保存在 我自己的Arduino工作目录下  H:\Arduino\workspace\ 于是IDE会自动帮我们在workspace下创建1个文件夹,并将sketch主文件放在里面,而且主文件和文件夹同名.

为Tcl编写C的扩展库

Tcl是一个比较简洁的脚本语言,官方地址 http://www.tcl.tk. tcl脚本加载C实现的动态库非常方便. 1. 为Tcl编写一个用C实现的扩展函数. #include <stdio.h> #include <stdlib.h> #include <string.h> #include <tcl.h> extern "C" { // extern for C++. int Myimpltcl_Init(Tcl_Interp *