易语言支持库升级之后,要保证向下兼容性,主要是做到以下几点:
一:保证原有的易语言源程序(.e)能正常打开(兼容点1)、正常编译(兼容点2)、编译结果正确(兼容点3);
二:保证原有的易语言程序(.exe)能正常运行(兼容点4)、运行结果正确(兼容点5)。
这里说的“原有的易语言源程序”和“原有的易语言程序”是指,替换新版支持库文件之前,使用旧版支持库编写的易语言源程序,和使用该源程序编译生成的可执行程序。

本文主要就此问题结合具体情况进行分析和总结。

一,为支持库增加一条命令

新增加的命令,必须放在所有原有命令的后面,否则将违反兼容点2和4,更无法保证兼容点3和5。这是因为,在源程序和EXE中,记录的都是命令的索引,一旦在中间插入一条命令,将导致后面的命令索引全变了,进而导致非常严重的错位问题。只要记住,总是在所有命令的最后添加新的命令,就不会引入兼容性问题。具体到数据类型的成员方法,与上面的分种一致,因为它也是使用支持库中唯一的全局函数表的,但这里引入了一个新的细节,有一个 LIB_DATA_TYPE_INFO.m_pnCmdsIndex 用于指定方法在全局函数表中的命令索引,所以通过它可以调整各成员方法的顺序,这种做法通常不会引入兼容性问题。

二,为命令增加一个参数,或修改命令的参数

新增加的参数,必须放在所有参数的后面,否则将违反兼容点2,3,4,5;必须允许省略该参数(AS_DEFAULT_VALUE_IS_EMPTY)或该参数有默认值(AS_HAS_DEFAULT_VALUE);同时在编写代码读取该参数值时要小心,须判断nArgCount和MDATA_INF.m_dtDataType(否则违反兼容点4,5),大致大代如下:

void fn_Global_SetWindowRgn (PMDATA_INF pRetData, INT nArgCount, PMDATA_INF pArgInf)  
{  
    int nArg1 = pArgInf[0].m_int;  
    int nArg2 = pArgInf[1].m_int;  
    //假设第三个参数是新版添加的参数  
    int nArg3 = 0;  
    if(nArgCount > 2 && pArgInf[2].m_dtDataType != _SDT_NULL)  
    {  
        nArg3 = pArgInf[2].m_int;  
    }  
    //...  
}

修改一个已有命令的参数时,不能随便修改数据类型,否则将违反兼容点2,3,4,5,要改也只能改成通用型(同时在代码中根据参数传递来的真实类型(MDATA_INF.m_dtDataType)做相应处理)。参数的名称和说明,因为都是文本,可以随便改,有会有兼容性问题,但不要改的意思与原来相反哦(排除原来失误写反了的情况),改变参数语义往往会导致代码改动,进而导致违反兼容点5。

三,为窗口组件增加属性

新增加的属性,必须放在已有属性的后面,否则违反兼容点4,5,因为易语言运行时是通过属性的索引读写属性值的。然后在序列化属性值时,提高一个版本号,读取时先判断版本,旧版序列化出的数据少,就不能多读。在此提示,序列化属性值时,一定要先写出版本号,如果没有版本号,后续的兼容性问题多多。
(TODO:这一段另成一文)但即使有了版本号,使用不当,也会导致升级时的麻烦,我前一段就遇到过,代码有这么一句:if(dwVersion > CURRENT_VER) return FALSE; 版本号比现在能处理的版本号大,出现在什么情况呢,用旧版支持库打开/运行用新版支持库编译的易源程序/程序。返回假是合理的(毕竟不能完全处理这种情况),但是比较粗暴(这意味着无法用旧版支持库打开用新版支持库编写的源程序/程序,这不是向下兼容的问题,是向上兼容的问题),如果能尽量读取,放弃不能识别的数据,温和的处理,效果更好。简单的把这条判断语句删除,有用吗?当然没用,旧版的支持库已经在用户手上了,编译结果是确定的,你的改动只能体现在新版中,而对旧版无能为力。解决这个问题需要技巧,我(liigo)采取的方案是,版本号不升反降!即,把CURRENT_VER减小,那么我序列化出的数据,版本号比以前还小,旧版支持库里的那条判断(if(dwVersion > CURRENT_VER))就不起作用了,嘿嘿。新的序列化数据版本更低,但写的数据更多,需要担心旧版支持库读出多余的数据来吗?当然不用,旧版支持库的编译结果和运行结果是确定的。只要新版支持库中识别出这种情况,不要因为版本号小就认为是旧版就行了,同时后续升级的方便,再引入一个新的版本号,存到原有序列化数据的最后,这样再次升级时直接判断新版本号就行了,原有的那套版本号停止使用。这一段有点乱,需整理。

四,为数据类型增加私有成员

如果某个数据类型已经有了一个或多个私有成员,升级时需要增加一个私有成员,该怎么办,直接在 LIB_DATA_TYPE_ELEMENT 数组中添加一项吗?不行!这样将违反兼容点4,5,因为对象所占用内存是在由EXE分配的,旧的EXE中只为对象分配了N个成员的空间,而新库要去访问第N+1个成员,不就发生内存访问越界的错误了吗?解决方案是,把原来的N个私有成员连用新增加的成员,集中存储到一块新分配的内存中,然后把这个内存地址存储到原有的第一个私有成员位置上(原有的其它成员位置废弃不用)。因为即使是旧EXE也会调用新支持库中的构造函数和析构函数,所以改变私有成员存储位置不会影响程序的执行。这种方案对代码的影响是非常大的,需要修改很多地方(所有对私有成员的读写),但是为了保证支持库的向下兼容性,这种付出是值得的。

五,为支持库增加常量,或为枚举类型增加常量成员

必须添加到现有常量或常量成员的最后面,否则违反兼容点1,2,3。因为易语言源代码.e文件中记录的是常量在支持库中的索引,一旦在中间插入新的常量,后续常量索引全变了,以前用的这些常量的地方,全错位了,破坏力非常大。但因为常量值是直接编译到EXE/DLL中的,运行时不会去支持库中加载常量,所以不会违反兼容点4,5。

最后修改:2023 年 11 月 24 日
如果觉得我的文章对你有用,请随意赞赏