[讨论] 如何将一个结构体的 buffer 数据,拷贝到另一个结构体的实例中

85 天前
 Gorvery

背景

我有一个静态库 A ,提供的头文件中有 public 的结构体和方法接口,以供别人调用

namespace api {

struct MyStruct {
    std::string text;
    int text_length;
};

std::string getStructInfo(const MyStruct &struct);

}

另一个静态库 B ,依赖了静态库 A ,其源码内实例化了一个 MyStruct 结构体对象,并调用了 getStructSize 方法获取了结果

静态库 A 和静态库 B 均以 二进制.a 形式集成到工程 App 中使用

问题

某一天,静态库 A 中的头文件 MyStruct 定义发生变化:

struct MyStruct {
    int text_length;
    float score;
    std::string full_text;
};

我想在不重新构建静态库 B ,保持 MyStruct 命名不变,使用新定义的情况下,通过指针偏移或二进制适配手段,使静态库 B 内的代码逻辑能正常运行,应该如何进行处理?

我的想法是,由于 getStructSize 的参数是引用,所以打算通过上测算出老的结构体定义的大小,然后 memcpy 对应长度的 buffer 到一个和原结构体定义完全一致,但命名空间不一致的的结构体中,随后进行提取:

第一步:定义一个结构体,和原来的 MyStruct 完全一致,但通过命名空间隔离:

namespace old {

struct MyStruct {
    std::string text;
    int text_length;
};

}

第二步:在 getStructInfo 内部,进行转换

int getStructInfo(const MyStruct &struct) {
    size_t old_size = sizeof(old::MyStruct);
    old::MyStruct old_struct;
    memcpy(&old_struct, &struct, old_size);

    printf("old struct text:%s", old_struct.text.c_str());
    ……
}

总觉得这个方法是可以的,但实际操作的过程中就不行,无法获得 text 的值,这是为什么呢?求大神赐教下

补充信息:iOS 系统,arm64 架构

2294 次点击
所在节点    程序员
37 条回复
thevita
84 天前
怎么会 A/B 的共同依赖有变更了, A/B 中只变更其中一个,这不是给自己找不痛快么

不要纠结于 什么 新 Struct / 老 Struct , 就一个问题, A/B 的依赖关系怎么处理

1) 保持依赖关系, 那就更新 B ,(这也交给你的构建系统去更新啊,又不耽误你自己)
2) 拆掉依赖关系, 通过一些其他约定/API , 拆掉 A/B 间的依赖关系

如果是那种 这个 .a 没源码有不可控的情况,这种模块也应该再包一层把它隔离掉
Gorvery
84 天前
@StarsunYzL 如果不拷贝,只做指针偏移应该怎么处理,我只要能读到里面的一两个成员属性的值就可以了
Gorvery
84 天前
@MoYi123 求问,不考虑 copy 的情况通过指针偏移能做吗
greycell
84 天前
以为这样做省事,其实是浪费时间
Gorvery
84 天前
@mightybruce 假如我可以区分是新老定义调用的这个方法,不考虑 copy 的情况可以通过指针偏移手段,读到使用老定义调用函数时,传入结构体中的部分属性么
Gorvery
84 天前
@thevita 还有另外一个静态库 C 依赖了新的结构体定义,如果结构体回滚或者再封装一层,这个影响更大。静态库 C 也是不可重新构建了
Skifary
84 天前
@Gorvery 可以通过指针偏移的方式做到,但是这种方式要严格保证结构体成员顺序。

int getStructInfo(const MyStruct& s)
{
auto sizeOfInt = sizeof(int);
auto sizeOfFloat = sizeof(float);

char* pointer = (char*)&s;

old::MyStruct old;
old.text_length = *((int*)(pointer));
old.text = *((std::string*)(pointer + sizeOfInt + sizeOfFloat));

return 0;
}
jones2000
84 天前
结构体里面定义好,数据版本号, 不同的版本号,拷贝不同的大小。
leonshaw
84 天前
直接 reinterpret_cast 成老 struct 的指针
txhwind
84 天前
工厂方法虚基类应该是 best practice 了,不过不知道屎山代码好不好改
Gorvery
84 天前
@Skifary 不知道为啥我这边试了不好使,看汇编和逆向后的代码感觉没问题,也通过 offsetof 函数动态计算偏移了,还是取不到数据。😭
Gorvery
84 天前
@leonshaw 这个方法我也试了,不行……我也不知道为啥
leonshaw
84 天前
@Gorvery 那考虑是不是标准库实现或者 ABI 不一致了
Skifary
84 天前
@Gorvery 试过 msvc 那段函数是可以用的,这里还有一个 online 的版本用的 g++ https://wandbox.org/permlink/9VbIO1IZ4gV9Vzkb
sampeng
84 天前
这种东西重构静态库 B 是最优解。并且暴露出的不是 struct ,而是接口的方法是最优解。。。把所有不对的地方都重构一遍。

从工程的角度来说,个人建议。。别这么干拷贝内存的事。不然就是下一个接手的人跳脚骂人。因为这就是副作用,也就是在偷偷摸摸的干了一些外部不知道的事。
daimen
84 天前
变量申明顺序一致,对齐规则一致,编译环境一致,应该是可以的
shapper
84 天前
B 要重新编译,B 中 A 的结构和符号还是之前没变化的原型,现在新 A 虽然变化了。假设 B 的结构跟着变为新 A 的,B 里原来的位置肯定 corrupt ,后面一连串的 corrup 。要想更新快改动态链接

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1066298

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX