標籤:http io 使用 ar for 檔案 div 問題 cti
開發小組公用庫的過程中,遇到二進位相容問題。下面是二進位相容和代碼相容的具體定義:
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
編寫二進位相容的庫