问题描述
应用代码:
typedef struct B1 {
int bparam1;
int bparam2;
int bparam3;
}B1_t;
typedef struct A1 {
int version; //version2
int param1;
int param2;
B1_t bparam;
char param3;
int param4;
}A1_t;
库版本 1:
typedef struct B1
{
int bparam1;
int bparam2;
}B1_t;
typedef struct A1
{
int version; //version1
int param1;
int param2;
B1_t bparam;
char param3;
int param4;
}A1_t;
我在与 C 库链接时在我的 C 应用程序之间传递 A1 结构参数时遇到问题。 我的代码在 A1 version1 上针对使用 A1 version2 编译的应用程序运行。库解码 A1 结构时,我看到数据与预期不符。
应用程序用 version=2 填充参数,B1 结构在 version 中添加了一些新参数。
当我的应用程序向后兼容时,尝试在平台 version=1 上运行,同时解码 A1 参数,param3 和 para4 不符合预期。
在不同版本上运行时,有什么方法可以解码应用程序和库之间的结构?
解决方法
C 标准不支持传递在不同翻译单元(例如用于编译库模块的翻译单元和用于编译应用程序模块的翻译单元)中定义不同的对象。
>对象作为参数传递的方法取决于每个 C 实现,通常根据平台应用程序二进制接口 (ABI) 完成。 ABI 可以指定在寄存器中传递小结构,并且可以指定将大结构写入堆栈。如果库需要一个大小的参数,而应用程序传递另一个大小,则库代码期望找到传递的参数的位置与它们实际位置之间的不匹配会导致各种不当行为。
在常见的 C 实现中,没有实用的方法来支持在例程之间按值传递不同定义的结构版本。
处理版本化数据结构的一种常见方法是让应用程序完全不知道数据结构的大小或内部成员。为了创建一个数据结构,应用程序调用一个库例程来分配内存,初始化一个数据结构,并返回一个指向它的指针。为了以任何方式与数据结构交互,应用程序将指针传递给各种库例程。这可用于在数据结构中设置成员(或更抽象地说,状态信息)、访问成员(或信息)、在使用结束时释放结构以及执行其他功能。
如果您确实希望应用程序更直接地访问结构并通过值而不是通过指针来使用它,那么有一些选项。他们需要关注 C 标准的细节,以确保行为保持定义。一种方法是确保结构的较新版本始终是先前版本的超集。理想情况下,该结构的任何先前版本都将是该结构新版本的初始成员,然后可以将该成员传递给旧的库例程。如果做不到这一点,可以确保旧版本的结构是新版本结构的前缀——在正在使用的 C 实现中,结构的布局是这样的,它的初始字节形成表示旧结构的字节. (这暗示了 C 标准中的一些技术细节。)在确保这一点后,由于 ABI 问题,您仍然无法在预期旧结构的地方按值传递新结构,但您可以通过指针,库可以使用指针访问表示旧版本结构的字节。 (出于技术原因,通过将字节复制到旧结构类型的实例化来执行此操作可能是合适的。一些旧的将尝试通过转换指针类型来执行此操作,但结果行为并未由 C 标准定义。)
上面的段落是作为技术可能性包含的,但我不推荐它。常见的解决方案是使用前面段落中描述的指针。搜索“不透明结构”或“不透明指针”会提供更多信息。
所有这些都需要在库的初始版本中进行规划,以便其发布的界面适合未来升级。