zoukankan      html  css  js  c++  java
  • [笔记]程序App链接B库时,如何解决枚举值不匹配的问题

    情况是这样的:

    App程序只提供了部分代码,B库有全部代码。
    B库的代码需要用到App里面定义的一些枚举量,现在需要将B库链接到App里。
    由于枚举量的定义中使用了编译开关来控制,而App具体打开了哪些开关未知,从而导致B库中如果直接#include App使用的头文件所得到的枚举值与App里面运行时所得到的枚举值不匹配。

    还是写个Demo程序来说吧,App的代码是这样的,首先是App.h这个双方都要用的头文件如下:

    #ifndef APP_H
    #define APP_H
    
    /* Value of ID_E is controlled by switch SWITCH_ID_CD 
       If SWITCH_ID_CD is enabled,  ID_E = 4,
       If SWITCH_ID_CD is disabled, ID_E = 2.
    */
    typedef enum t_ID{
        ID_A = 0,
        ID_B,
    #ifdef SWITCH_ID_CD
        ID_C,
        ID_D,
    #endif
        ID_E,
        ID_F
    }ID;
    
    #endif

    可以看到如果编译开关 SWITCH_ID_CD 打开,则 ID_E = 4,否则 ID_E = 2。

    BLib 的代码 BLib.c 如下:

    #define BLIB_C
    
    /* App.h is from App, and we have no idea of SWITCH_ID_CD is switched on or off */
    #include "../App/App.h"
    
    /* Get value of id */
    int get_id_value(ID id){
        return (int)id;
    }
    
    /* It will return 2 because SWITCH_ID_CD not defined in this lib */
    int get_ID_E(){
        return get_id_value(ID_E);
    }

    BLib.c 并没有打开编译开关 SWITCH_ID_CD,那么当App调用BLib的函数 get_ID_E() 时,将会返回2。

    App的主程序App.c如下:

    /* Link BLib.lib in App */
    #pragma comment(lib, "../Debug/BLib.lib")
    
    /* We switch on SWITCH_ID_CD in App, and disable it in BLib.lib */
    #define SWITCH_ID_CD
    
    #include "stdio.h"
    #include "App.h"
    
    /* Functions from BLib.lib */
    extern int get_id_value(ID id);
    extern int get_ID_E();
    
    int main(int argc, char* argv[])
    {
        printf("\n----- In App -----\n");
        printf("ID_E = %d.\n", (int)ID_E);                      // 此处会返回4
        printf("\n----- In BLib.lib -----\n");
        printf("ID_E = %d. (call get_ID_E())\n", get_ID_E());   // 此处会返回2
    
        getchar();
        return 0;
    }

    App.c 打开了编译开关 SWITCH_ID_CD,所以当App显示ID_E的值时,将会返回4。

    运行的结果也验证了这一点。

    ----- In App -----
    ID_E = 4.
    
    ----- In BLib.lib -----
    ID_E = 2. (call get_ID_E())

    上面的不匹配就是遇到的问题,怎么解决它呢?曾经考虑过几个方案:

    方案1:直接Hack出枚举值的数值,然后在BLib.c中使用之

    方案2:使用字符串到数值的译码方式匹配枚举值

    方案3:使用extern方式引用外部变量

    下面分别论述其可行性。

    方案1:直接Hack出枚举值的数值,然后在BLib.c中使用之

    这种方法最简单粗暴,既然无法确认App使用了哪些编译开关,那就通过debug的方式将App.h中定义的枚举量的具体数值直接打印出来,然后在BLib.c中使用。

    但是这种硬编码的方式其扩展性也最差,一旦App的编译开关发生变化,其枚举量势必会跟着变化,这时再手工修改BLib.c就成了苦差事。

    本方法仅适合于快速调试等短线投资,不适合长线持有。

    方案2:使用字符串到数值的译码方式匹配枚举值

    这种方法实现方式就是在BLib.c里新建一个函数,向其传入“ID_E”这个字符串,然后该函数调用App里面的解析函数,将这个字符串匹配到实际的ID_E的数值并返回。

    听上去稍微优雅一些,但是问题也不少,比方说对于这些字符串的解析过程,将来枚举量增加的话(目前项目的枚举量在上千个左右),还用一堆的if else来线性挨个比较显然效率不高,可如果用HashTable做快速映射则势必增加代码量,更别说存储这些字符串及哈希值需要占用的存储空间了。

    方案3:使用extern方式引用外部变量

    考虑再三,要想节省存储空间,又要能够对不同的编译开关产生的枚举值做出适配,用extern方式引用外部变量是个方法,修改代码如下。

    新增头文件 ForBLib.h 如下:

    /* Declare and init in App.c, and extern in BLib */
    
    #ifdef APP
        #define GLOBAL
    #else
        #define GLOBAL extern
    #endif
    
    /* Declare needed enum, e.g. LIB_ID_E */
    GLOBAL int LIB_ID_E;

    当App.c插入此头文件时,宏 GLOBAL 的定义为空,即为声明 LIB_ID_E 变量,而当BLib.c插入此头文件时,宏 GLOBAL 的定义为 extern,即为引用外部变量。

    BLib.c 修改代码如下:

    #define BLIB
    
    /* App.h is from App, and we have no idea of SWITCH_ID_CD is switched on or off */
    #include "../App/App.h"
    #include "../App/ForBLib.h"
    
    /* Get value of id */
    int get_id_value(ID id){
        return (int)id;
    }
    
    /* It will return 2 because SWITCH_ID_CD not defined in this lib */
    int get_ID_E(){
        return get_id_value(ID_E);
    }
    
    /* It will return 4 if LIB_ID_E is init as ID_E in App.c, or return 0 if not */
    int get_LIB_ID_E(){
        return get_id_value(LIB_ID_E);
    }

    在BLib.c中定义了宏 BLIB,用于在插入 ForLibB.h 时识别。

    App.c 修改代码如下:

    #define APP
    
    /* Link BLib.lib in App */
    #pragma comment(lib, "../Debug/BLib.lib")
    
    /* We switch on SWITCH_ID_CD in App, and disable it in BLib.lib */
    #define SWITCH_ID_CD
    
    #include "stdio.h"
    #include "App.h"
    #include "ForBLib.h"
    
    /* Functions from BLib.lib */
    extern int get_id_value(ID id);
    extern int get_ID_E();
    extern int get_LIB_ID_E();
    
    /* Init LIB_ID_X, e.g. LIB_ID_E = ID_E */
    void init_LIB_ID(){
        LIB_ID_E = ID_E;
    }
    
    int main(int argc, char* argv[])
    {
        printf("\n----- In App -----\n");
        printf("ID_E = %d.\n", (int)ID_E);                                  // 此处会返回4
        printf("\n----- In BLib.lib and did not run init_LIB_ID() -----\n");// 此时暂未调用 init_LIB_ID()
        printf("ID_E = %d. (call get_ID_E())\n", get_ID_E());               // 此处会返回2
        printf("LIB_ID_E = %d. (call get_LIB_ID_E())\n", get_LIB_ID_E());   // 此处会返回0,因为 LIB_ID_E 未初始化,被编译器自动初始化为0
        printf("\n----- In BLib.lib and run init_LIB_ID() -----\n");
        init_LIB_ID();                                                      // 此时调用 init_LIB_ID(),LIB_ID_E = ID_E 
        printf("ID_E = %d. (call get_ID_E())\n", get_ID_E());               // 此处仍然返回2
        printf("LIB_ID_E = %d. (call get_LIB_ID_E())\n", get_LIB_ID_E());   // 此处会返回4,这就OK了
    
        getchar();
        return 0;
    }

    在App.c中定义了宏 APP,同样用于在插入 ForLibB.h 时识别。

    运行的结果如下:

    ----- In App -----
    ID_E = 4.
    
    ----- In BLib.lib and did not run init_LIB_ID() -----
    ID_E = 2. (call get_ID_E())
    LIB_ID_E = 0. (call get_LIB_ID_E())
    
    ----- In BLib.lib and run init_LIB_ID() -----
    ID_E = 2. (call get_ID_E())
    LIB_ID_E = 4. (call get_LIB_ID_E())

    可见,只要先对变量 LIB_ID_E 进行初始化,则 BLib.c 中使用 LIB_ID_E 就等同于使用 ID_E 了。

    但是,如果需要用到的枚举量增加,如成百上千个,手工写代码就成了一件苦事,能否通过宏来设计一套机制,只需要更改一处,两边都可以达到升级的目的?

    3.1 利用宏+extern,一次修改,两边运行

    增加一个辅助头文件BLibHelper.h如下:

    #ifndef FORBLIB_H
    #define FORBLIB_H
    
    /* Declared when use in App.c */
    #ifdef APP
        #define GLOBAL 
    #else
        #define GLOBAL extern
    #endif
    
    /* ID_E -> LIB_ID_E */
    #define GET_LIB_ID(ID_NAME) LIB_##ID_NAME
    
    /* ID_E -> extern int LIB_ID_E */
    #define DECLARE_LIB_ID(ID_NAME) GLOBAL int GET_LIB_ID(ID_NAME)
    
    /* ID_E -> LIB_ID_E = (int)ID_E */
    #define INIT_LIB_ID(ID_NAME) GET_LIB_ID(ID_NAME) = (int)ID_NAME
    
    /* Declare and init in App.c */
    #include "ForBLib.h"
    
    #endif

    GLOBAL的用法上面已经说过,GET_LIB_ID的用法需要讲讲。

    举例来说,代码中的 GET_LIB_ID(ID_E) 会被编译器在预编译阶段展开为 LIB_ID_E,宏代码中的"##"等同于连接左右两端的字符串。

    同理,代码中的 DECLARE_LIB_ID(GET_LIB_ID(ID_E)) 会被展开为两个不同的形式,当在App.c中使用时,展开为 int LIB_ID_E,而在BLib.c中使用时,则为 extern int LIB_ID_E

    同理,代码中的 INIT_LIB_ID(GET_LIB_ID(ID_E)) 会被展开为 LIB_ID_E  = (int)ID_E

    在这个头文件的最后,插入了 ForBLib.h 文件,这个文件与之前的文件变化很大,如下:

    /* Declare and init in App.c, and extern in BLib */
    
    #ifdef APP_INIT_LIB_ID
        #define OPERATE_LIB_ID INIT_LIB_ID
    #else
        #define OPERATE_LIB_ID DECLARE_LIB_ID
    #endif
    
    /* Declare needed enum, e.g. LIB_ID_E */
    
    OPERATE_LIB_ID(ID_E);
    OPERATE_LIB_ID(ID_F);

    该文件引用了两个枚举量ID_E和ID_F。

    如果该文件被App.c插入,则在文件头是对 LIB_ID_E 和 LIB_ID_F 的定义,而在 init_LIB_ID() 则是对其的初始化,如果被BLib.c插入,就成了对它们的extern声明。

    现在BLib.c的文件修改如下:

    #define BLIB
    
    /* App.h is from App, and we have no idea of SWITCH_ID_CD is switched on or off */
    #include "../App/App.h"
    #include "../App/BLibHelper.h"
    
    /* Get value of id */
    int get_id_value(ID id){
        return (int)id;
    }
    
    /* It will return 2 because SWITCH_ID_CD not defined in this lib */
    int get_ID_E(){
        return get_id_value(ID_E);
    }
    
    /* It will return 4 if LIB_ID_E is init as ID_E in App.c, or return 0 if not */
    int get_LIB_ID_E(){
        return get_id_value(GET_LIB_ID(ID_E));
    }
    
    /* It will return 5 if LIB_ID_F is init as ID_F in App.c, or return 0 if not */
    int get_LIB_ID_F(){
        return get_id_value(GET_LIB_ID(ID_F));
    }

    可以看到,它只需要插入BLibHelper.h即可。

    在BLib.c中,需要使用到ID_E正确的值,只需要调用 GET_LIB_ID(ID_E) 即可,因为这只是一个预编译阶段的宏展开,所以比方案2中的查表匹配效率高。

    App.c的代码修改为:

    #define APP
    
    /* Link BLib.lib in App */
    #pragma comment(lib, "../Debug/BLib.lib")
    
    /* We switch on SWITCH_ID_CD in App, and disable it in BLib.lib */
    #define SWITCH_ID_CD
    
    #include "stdio.h"
    #include "App.h"
    #include "BLibHelper.h"
    
    /* Functions from BLib.lib */
    extern int get_id_value(ID id);
    extern int get_ID_E();
    extern int get_LIB_ID_E();
    
    /* Init LIB_ID_X, e.g. LIB_ID_E = ID_E */
    void init_LIB_ID(){
    #define APP_INIT_LIB_ID
        #include "ForBLib.h"                                                // 初始化变量
    #undef APP_INIT_LIB_ID
    }
    
    int main(int argc, char* argv[])
    {
        printf("\n----- In App -----\n");    
        printf("ID_E = %d.\n", (int)ID_E);                                  // 此处会返回4
        printf("\n----- In BLib.lib and did not run init_LIB_ID() -----\n");// 此时暂未调用 init_LIB_ID()
        printf("ID_E = %d. (call get_ID_E())\n", get_ID_E());               // 此处会返回2
        printf("LIB_ID_E = %d. (call get_LIB_ID_E())\n", get_LIB_ID_E());   // 此处会返回0,因为 LIB_ID_E 未初始化,被编译器自动初始化为0
        printf("\n----- In BLib.lib and run init_LIB_ID() -----\n");
        init_LIB_ID();                                                      // 此时调用 init_LIB_ID(),LIB_ID_E = ID_E 
        printf("ID_E = %d. (call get_ID_E())\n", get_ID_E());               // 此处仍然返回2
        printf("LIB_ID_E = %d. (call get_LIB_ID_E())\n", get_LIB_ID_E());   // 此处会返回4,这就OK了
        printf("LIB_ID_F = %d. (call get_LIB_ID_F())\n", get_LIB_ID_F());   // 此处会返回5,这就OK了
    
        getchar();
        return 0;
    }

    文件中 init_LIB_ID() 的内容很简单,就是插入 ForBLib.h。因为有开关 APP_INIT_LIB_ID 控制,可以保证此处仅有初始化代码。

    运行结果如下:

    ----- In App -----
    ID_E = 4.
    
    ----- In BLib.lib and did not run init_LIB_ID() -----
    ID_E = 2. (call get_ID_E())
    LIB_ID_E = 4. (call get_LIB_ID_E())
    
    ----- In BLib.lib and run init_LIB_ID() -----
    ID_E = 2. (call get_ID_E())
    LIB_ID_E = 4. (call get_LIB_ID_E())
    LIB_ID_F = 5. (call get_LIB_ID_F())

    如果需要增加枚举量,只需要修改 ForBLib.h 即可,如:

    ...
    OPERATE_LIB_ID(ID_E);
    OPERATE_LIB_ID(ID_F);
    // 此处增加新枚举量,如
    // OPERATE_LIB_ID(ID_G);

    之所以在定义 LIB_ID_E 时类型为 int 而不是 ID,是因为枚举值可以轻易的转为 int 值,这样在增加不同类型的枚举量时不用考虑类型不一致了。

    附上方案3.1的VC2010版本例子代码,在这里。需要注意的是App和BLib都是选择“Compile As C Code”

    综上所述,使用方案3.1还是较为简单粗暴的:

    - App.c 和 BLib.c 都插入 BLibHelper.h 这个头文件,App.c中加入一个 init_LIB_ID() 的函数,内容只有一句 #include "ForBLib.h"。

    - BLib.c 中使用 ID_E 这样的枚举量时,使用 GET_LIB_ID(ID_E) 的方式。

    - 需要增加枚举量X时,就在 ForBLib.h 后面增加 OPERATE_LIB_ID(X);

    题外话,如果要提高App的可移植性,最好还是将App.h中的枚举量如下挨个硬编码出确定数值,这样就不需要担心被编译开关打断了。

    typedef enum t_ID{
        ID_A = 0,
        ID_B = 1,
    #ifdef SWITCH_ID_CD
        ID_C = 2,
        ID_D = 3,
    #endif
        ID_E = 4,
        ID_F = 5
    }ID;
  • 相关阅读:
    修改Firebug字体
    [CodeWars][JS]如何判断给定的数字是否整数
    [CodeWars][JS]实现链式加法
    【ACM成长之路】刷题记录
    【C++】用于ACM/OI等算法竞赛的读入优化
    C# 读取写入excel单元格(包括对excel的一些基本操作)
    Git上传本地项目到GitHub等云托管仓库
    贝塞尔曲线(B-spline)的原理与应用
    【已解决】Ubuntu U盘启动出现“Failed to load ldlinux.c32”问题
    【算法】Tarjan算法求强连通分量
  • 原文地址:https://www.cnblogs.com/journeyonmyway/p/2549352.html
Copyright © 2011-2022 走看看