头文件向下兼容,如何优雅实现? - V2EX
FranzKafka95
V2EX    C

头文件向下兼容,如何优雅实现?

  •  
  •   FranzKafka95 Dec 1, 2021 via Android 2763 views
    This topic created in 1627 days ago, the information mentioned may be changed or developed.
    因为项目中经常与第三方合作,两者间通过接口实现交互,所以有定义公共使用的头文件,用于定义接口和公共使用的东西。

    但是由于项目一直在发展,经常需要变更头文件,怎样能优雅的解决头文件变动而不影响已经量产的环境呢?

    如果只是单纯的增加接口,影响是有限的,主要是以前用的一些结构体,会存在增加成员的情况,这个改动会导致新头文件无法运用到已经量产的项目,比较头疼。

    不知各位有没有好的解决方案?
    19 replies    2021-12-13 13:09:28 +08:00
    wzzzx
        1
    wzzzx  
       Dec 1, 2021
    这里可以借鉴一下 C++的 Pimpl 思想
    ysc3839
        2
    ysc3839  
       Dec 1, 2021
    把函数指针都放在一个 struct 里面,struct 头部存储当前版本的 sizeof ,新增函数都放到最后,结构体变了就新增一个函数
    newmlp
        3
    newmlp  
       Dec 1, 2021
    Pimpl +1 类似于 Qt 的 d 指针,类的私有成员包装成另一个类,然后通过指针去访问,这样对外的类就只包含 public 的成员和一个私有的指针,更改结构体就不会影响对外类的内存布局了
    FranzKafka95
        4
    FranzKafka95  
    OP
       Dec 1, 2021 via Android
    我可能没太说清楚,举个例子吧。
    如在头文件版本 1 中,有这样一个结构体(这个结构体我们自己与第三方都有使用):

    struct A{
    int a;
    float b;

    }

    在头文件版本 2 中,需要在结构体 A 中增加成员 c ,增加取下:

    struct A{
    int a;
    float b;double c;

    }

    第三方程序最终会编译成一个动态库(so)。现在的构想是头文件变动(如结构体成员增加),但是第三方 so 不变,怎样实现头文件向下兼容呢
    FranzKafka95
        5
    FranzKafka95  
    OP
       Dec 1, 2021 via Android
    @newmlp pimpl 好像不太使用,这里的接口都是第三方实现的,由我们调用,而且是我们制定了接口,包括接口传参,现在就是传参是一个结构体,这个结构体包含很多第三方需要的信息,我要改变这个结构体(追加成员),而第三方程序不做改变,实现向下兼容
    FranzKafka95
        6
    FranzKafka95  
    OP
       Dec 1, 2021 via Android
    其实我很好奇,程序中索引结构体成员不是根据符号索引的(没有符号?)貌似是根据类型直接取内存块里的数据,暂且不论关闭结构体对齐与否都会存在问题(因为还有结构体嵌套),感觉无解了
    xylxAdai
        7
    xylxAdai  
       Dec 1, 2021
    @FranzKafka95 。。。不太理解,第三方如果用不到这个新加的结构体变量的话,那么你直接内部用自己的结构体,用到它们接口再转过去就好了嘛。。
    3dwelcome
        8
    3dwelcome  
       Dec 1, 2021
    传什么 struct 哦,太落后了。

    学 JS 理念,交互接口就只传一个 json 对象,以后怎么变都没问题。
    FranzKafka95
        9
    FranzKafka95  
    OP
       Dec 1, 2021 via Android
    @xylxAdai 是这样的,这一份头文件会对接多个第三方,新增加的成员对新的第三方库有用,但是对已经量产的第三方库不会使用。我想保持一份头文件。
    FranzKafka95
        10
    FranzKafka95  
    OP
       Dec 1, 2021 via Android
    @3dwelcome 得分具体应用场景,我的应用与第三方库是存在大量数据交互的,传 Json 对象一点不实用
    Sephirothictree
        11
    Sephirothictree  
       Dec 1, 2021
    需要使用新结构体的地方,可以单独定义一下扩展参数,例如,旧头文件 struct A;
    新第三方库,内部添加一个 struct B {
    struct A;
    double c;
    };

    这样对于新第三方库可以传参后强制转换 ,test(A*t){
    struct B * b = (struct B *)t;
    ....
    }
    3dwelcome
        12
    3dwelcome  
       Dec 1, 2021
    json 实用啊,意味着你头文件只需要提供一个获取函数就可以,不用提供结构变量给第三方访问。

    比如就一个函数 void get_keyvalue(char* inname, char* outarray, int* outarraycount);

    对方获取内容时,就一句 get_keyvalue, 不同版本可以把 inname 的属性无限扩展,就和 json 一毛一样。
    Sephirothictree
        13
    Sephirothictree  
       Dec 1, 2021
    前提就是新第三方 so ,要按照 struct B 来编写代码
    weidaizi
        14
    weidaizi  
       Dec 1, 2021
    楼主是给用户头文件和 so ,来连接你们的服务的意思吗? 如果是的话,兼容主要做在服务里就可以了,比如最简单的方法是在传输协议的头中带上版本号,注册回调函数的时候,应该是消息+版本号确定唯一的回调函数,比如你上面自己举的例子:
    ------------------------------------
    版本 1.0.0:
    struct A{
    int a;
    float b;

    };
    那么传输的包中,应该类似于这样 | v1.0.0 | msg_type | payload |
    ------------------------------------
    版本 1.0.1:
    struct A{
    int a;
    float b;
    double c;

    }
    那么传输的包中,应该类似于这样 | v1.0.1 | msg_type | payload |
    ------------------------------------

    但是问题又来了,你现在已经发出去了 so ,里面没有版本号怎么办? 其实也简单,可以设置一些特殊的魔法字来识别,比如旧的版本,你的自定义头是这样
    | msg_type(int32) | payload |
    然后在新版本中,你的自定义头应该是这样
    | 特定的 msg_type(int32) | version | msg_type(int32) | payload |
    当你读到 特定的 msg_type 时,你知道这是带上来 version 版本的传输协议,接着转而解析 version + msg_type 即可
    learningman
        15
    learningman  
       Dec 1, 2021
    #IFDEF VERSION_XXX
    ....
    #ENDIF
    icylogic
        16
    icylogic  
       Dec 1, 2021 via iPhone
    > 第三方程序最终会编译成一个动态库(so)。现在的构想是头文件变动(如结构体成员增加),但是第三方 so 不变,怎样实现头文件向下兼容呢

    source/binary compatibility 的问题看 kde 这篇就好

    https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C%2B%2B#The_Do.27s_and_Don.27ts

    还有一个例子是 v4l2 的 api 在三年前添加了一个 data member

    https://github.com/torvalds/linux/commit/f35f5d7
    GeruzoniAnsasu
        17
    GeruzoniAnsasu  
       Dec 11, 2021
    #ifndef __PRODUCT_VER__
    typedef struct S_EssentialStruct_v0 S_EssentialStruct
    #elif __PRODUCT_VER__ > 1
    typedef struct S_Essential Struct_01 S_EssentialStruct
    #endif

    你们不会在代码里直接写 struct S_EssentialStruct_v0 吧
    FranzKafka95
        18
    FranzKafka95  
    OP
       Dec 13, 2021 via Android
    @GeruzoniAnsasu 我们还真是的,一开始就没有考虑兼容性,后面发现需要跟好几家第三方合作,而第三方的需求都不完全一样,所以才出现了这个问题。另外按照答主的思路,定义一个头文件版本的宏,根据宏去控制结构体成员貌似是个不错的想法。
    GeruzoniAnsasu
        19
    GeruzoniAnsasu  
       Dec 13, 2021
    @FranzKafka95 原项目里用的结构用的是 typedef 的 type 就很方便偷梁换柱,如果都写 `struct S` 那就…… 来一次全员搜索替换吧

    另外动态库层面还有这个
    https://sourceware.org/binutils/docs/ld/VERSION.html

    用来控制 API 导出版本
    About     Help     Advertise     Blog     API     FAQ     Solana     877 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 70ms UTC 20:35 PVG 04:35 LAX 13:35 JFK 16:35
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86