首页 文章

将C结构写入文件并使用其他编程语言读取文件?

提问于
浏览
3

我有一个充满挑战的情况;我们将在Mac,PC,iOS和Android上安装以旧格式接收文件的程序,并从这些文件中解析数据 . 我们无法更改这些文件的创建方式 .

这些文件由C程序生成,该程序用数字和字符串填充结构,然后将其写出 . 这是一个消毒版本 .

struct MyObject {
String Kfkj(MAXKYS); 
String Oern(MAXKYS);
String Vdflj(MAXKYS, 9); 
int Muic;
int Tdfkj;
int VdfkAsdk;
int SsdjsdDsldsk; 
int Ndsoief; 
String TdflsajPdlj; 
String TdckjdfPas; 
String AdsfakjIdd;
int IdkfjdKasdkj;
int AsadkjaKadkja(MAXKYS); 
int Kasldsdkj;
bool Usadl;
String PsadkjOasdj(9); 
String PasdkjOsdkj;
};

正如你所看到的,原语和字符串 .

然后是他们如何将其写入文件:

MyInstance MyObject;
FileName = "C:\MyFile.ab2"
ofstream fout (FileName, ios::binary);
fout.write((char*)& MyInstance, sizeof(MyInstance));

我们没有选择将其翻译一次然后将文件分发到其他平台;我们必须在每个不同的平台上进行翻译,这是我们必须要合作的 . 我很欣赏有关C序列化数据的任何信息,因此我们知道如何解析文件 .

编辑:解决方案

我从多个答案收到的反馈非常有帮助 . 使用它,我使用十六进制编辑器进行了广泛的分析并发现:

  • 元素一个接一个地进入文件

  • a "String,"在这种情况下,以一个int开头,描述该String的int后面有多少个字符 . 如果String不存在,它仍将具有值为0的int .

  • 整数,对于我看到的文件和机器,是两个字节,小端,并且MOSTLY无符号(有一些已签名,只是为了让我保持警觉)

  • 布尔值是两个字节,显然-1(FF FF)代表"true"

到目前为止,我们还没有遇到不同设备上不同填充或字节序的问题,但这些都是非常现实的问题 . 这些答案中的技术说明和警告为我们提供了更多的弹药,试图说服客户改变不那么脆弱的替代方案,例如XML或JSON,以便跨平台在线传输数据 .

至于那些询问开发人员是否被解雇的人...好吧,我们只是说他们的代码很老了,但经过多次对话后我们仍然无法说服他们写出C结构并试图在不同平台上阅读不是个好主意 .

7 回答

  • 4

    数据文件的格式完全取决于编译C程序的编译器以及String类的定义 . 你可以依赖它们声明的顺序中的字段,在这种情况下,我认为你可以依赖于那里没有任何填充,但这就是全部 . 在这种情况下,一些可能会帮助您的提示: -

    • 你没有't give the definition of the String class you'重新使用 . 如果它是std :: string的 typedef ,你在内存中是're completely screwed, because the contents of the string aren't . 我假设你的C程序员正在使用一些特殊的本地缓冲区,在这种情况下我猜你会发现对象的第一个字节是字符串,之后会有一些无用的填充 . 我希望结构在开始时包含一个int,告诉你它中有多少数据是有用的 .

    • 您可能会发现 int 字段长度为四个字节 .

    • 您可能会发现 bool 字段长一个字节,后跟三个字节的无用填充 . 只有一位,很可能是底部位,将被设置 .

    这就是我可以为你提供的所有有用的猜测 . 在您的目标语言中,请确保将整个文件作为与该语言中可用的字节数组最接近的内容读取,并且只有在此之后,才能使用语言功能将其转换为您所用语言中的正确类型 . 不要尝试以整数形式读取它,因为如果您在具有与C程序不同的字节顺序的平台上,则不会让您进行字节交换 . 我建议在文本编辑器中查看文件以对其进行反向工程,并帮助您找到每个字段的偏移量 .

    最后一条建议:考虑打印P45(或粉红色单据,或您所在国家/地区的任何内容),无论哪个程序员或项目经理都认为这种“序列化”是个好主意 . 在生死攸关的情况下,这种草率的工作可能是可以接受的,但是他们严重地以一种你很难恢复的方式搞砸了你 . 编写要读取这些文件的代码并不会那么难,如果它只是这样的一个结构,但保持可靠将是一个痛苦的世界,并且它们有效地使自己无法安全地更改编译器或编译器版本 .

  • 1

    在ofstream中写入不会序列化数据 . 此代码写入结构的原始内存内容,因为它是一串char . 根据您的编译器,其版本,选项以及它在内容上运行的系统将完全不同 . 甚至char的位数也允许在c实现之间改变 . 结构对象引用的数据将不会被写入(忘记std :: string的内容) .

    如果您无法更改编写器代码 . 您必须知道对齐策略,基本类型的大小和数据表示 . 您将不得不分析手工生成的文件,例如使用像这样的十六进制编辑器http://www.physics.ohio-state.edu/~prewett/hexedit/和可能看看你的编译器文档 .

    如果您可以更改编写器代码 . 使用正确的序列化,如json,协议缓冲区或简单的xml .

  • 0

    你会遇到很多问题 .

    C没有用于序列化数据本身的特定格式 . 它高度依赖于您运行的计算机体系结构/处理器 .

    允许编译器添加填充以帮助在系统上进行对齐 . 当我们说对齐时,我们基本上是指架构/处理器对数据位于特定字节边界的亲和力 . 例如,一些处理器非常喜欢浮点数位于4或8字节边界 - 如果不是,则处理器可能工作得慢得多或根本不工作 .

    所以,你不能简单地知道你的系统添加了什么填充物 .

    你可以做的是使用#pragma pack(1)/ #pragma pack(0)来阻止编译器填充你的数字 .

    PS:你还要担心字节序 . 如果一台计算机在big-endian上运行而另一台是小端的怎么办?没有转换,它们将以不同方式解释字节 .

    简而言之,您要么必须修复生成文件的应用程序,以便使用正确的序列化方案,要么需要查看它在SPECIFIC计算机上运行,查看它是如何编写文件的,以及为每个目标平台编写翻译器(这很愚蠢) .

    Interesting Suggestion

    如果您真的陷入困境,请编写一个监视您编写文件的文件夹的应用程序 . 让应用程序获取文件(因为它在同一台PC上,它将能够无问题地阅读其格式) . 让它以XML或其他一些真正的序列化格式将文件写回来并分发它们 .

  • 1

    哇 - 那太疯狂了 . 那么String对象不包含任何指针?不能 - 因为你声称这是工作代码 .

    无论如何,该代码没有进行任何序列化 . 它只是将结构写出来以完全按照它在内存中的布局方式进行归档 . 你唯一的问题是在某些平台上填充和整数类型的大小如int可能会有所不同 .

    您必须找到整数类型的大小,并在读者/编写器中将这些信息用于较新的平台,以确保它们在旧版平台上以相同的方式布局 .

    但是,您正在使用该代码运行真正的风险 . 实际上,编译器更改可能会突然导致文件布局发生更改 .

  • 1

    没有人指出一些对我来说特别棘手的事情(也许是因为我已经被它咬了一口气) . 那个问题:数据成员 bool Usadl; . sizeof(bool) 因跨平台,跨编译器,甚至跨同一编译器的版本而异 . sizeof(bool) 的常用值是4和1.这会咬你 . 现在很难找到一台大端机器,非常非常难以找到 CHAR_BIT 不是8或 sizeof(int) 不是4的计算机 . 对于 sizeof(bool) 来说情况并非如此 .

    与其他人一致,Chad的团队需要记录文件中记录的结构,然后确保生成该文件的程序明确地写入此结构,包括元素大小,填充和字节序 . 不要依赖 class 布局为你做这件事 . 那只是在惹麻烦 .

  • 4

    最好的方法可能是使用JSON,或者如果你想要一个更强大的解决方案,可以使用像Avro这样的东西 . Avro有C++ APIJava API,因此它涵盖了您遇到的大多数情况 .

  • 4

    它的方式,结构以原始形式写入文件 . 所以解析这个文件基本上你需要知道的是结构的二进制布局 .

    基本上,字段只是一个接一个,所以要读取一个int,你只需读取4个字节并将其转换为int等 .

    字符串是一个特例 . 从您的代码中不清楚这个“String”类型是字符的内联数组,还是指向这种数组的指针 . 在第一种情况下,您需要知道每个字符串包含多少个字符,然后按顺序读取该字符数 . 在第二种情况下,您将无法返回字符串,因为它不会被写入文件 . 指针对你没用 .

    最后一个问题是结构是否包装 . 由于您没有给出任何指示,默认情况下,struct字段与4字节边界对齐,因此在您需要考虑的布尔字段之后可能存在空格 . 如果结构被打包,则每个字段直接在前一个字段之后 .

    因此,简而言之,使用其定义找出结构二进制布局,如果所有其他方法都失败,则使用调试器在运行时检查内存,或使用十六进制编辑器来研究输出文件 . 然后在某处写下该规范,这将为您提供从文件中读取的内容 . 通过查看您给出的伪定义,不可能简单地确定布局是什么 .

相关问题