将C++结构写入文件并使用另一种编程语言读取文件

Write a C++ struct to a file and read file using another programming language?

本文关键字:文件 另一种 编程语言 读取 结构 C++      更新时间:2023-10-16

我遇到了一个具有挑战性的情况;我们将在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++如何序列化数据的任何信息,以便我们知道如何解析文件。

编辑:解决方案

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

  • 元素一个接一个地出现在文件中
  • 在这种情况下,"字符串"以 int 开头,描述该字符串的 int 后面有多少个字符。如果 String 不存在,它仍将具有值为 0 的 int。
  • 整数,对于我看到的文件和机器,是两个字节,小端序,大部分是无符号的(有一些是有符号的,只是为了让我保持警惕)
  • 布尔值是两个字节,显然是 -1 (FF FF) 表示"真"

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

至于你们中那些问开发商是否被解雇的人......好吧,让我们说他们的代码非常古老,但是经过多次对话,我们仍然难以说服他们写出C++结构并尝试在不同的平台上阅读它不是一个好主意。

> 你会遇到很多问题。

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

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

因此,您不能简单地知道您的系统神奇地添加了什么填充。

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

PS:您还必须担心字节序。 如果一台计算机运行在大端序上,而另一台计算机是小端序怎么办? 它们将在不进行转换的情况下以不同的方式解释字节。

简单地说,你要么必须修复生成文件的应用程序,以便它使用适当的序列化方案,要么你需要查看它在特定计算机上运行,查看它如何写入文件,并为每个目标平台编写一个转换器(这太愚蠢了)。

有趣的建议

如果您真的卡住了,请编写一个监视写入文件的文件夹的应用程序。 让应用程序选取文件(因为它在同一台电脑上,因此可以毫无问题地读取它们的格式)。 让它以 XML 或其他一些真正的序列化格式将文件写回去,然后分发这些文件。

哇 - 这太疯狂了。所以字符串对象不包含任何指针?不能 - 因为你声称这是工作代码。

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

您必须找到整型的大小,并在较新平台的读取器/写入器中使用该信息,以确保它们在旧平台上以相同的方式布局。

不过,使用该代码确实存在风险。实际上,编译器更改可能会突然导致文件布局更改。

数据文件的格式完全取决于编译C++程序时使用的编译器以及 String 类的定义。您可以依赖字段按声明顺序排列,在这种情况下,我认为您可以依靠开始时没有任何填充,但仅此而已。在这种情况下,一些可能对您有所帮助的提示:-

  • 您没有给出正在使用的 String 类的定义。如果它是 std::string 的typedef,那么您完全搞砸了,因为字符串的内容不在内存中。我假设你的C++程序员正在使用一些特殊的本地缓冲区,在这种情况下,我猜你会发现对象的第一个字节是字符串,之后有一些无用的填充。我希望该结构在开头包含一个 int,告诉您其中有多少数据是有用的。
  • 您可能会发现int字段的长度为四个字节。
  • 您可能会发现bool字段长一个字节,后跟三个字节的无用填充。只会设置一个位,很可能是底部位。

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

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

完成

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

基本上,这些字段只是一个接一个,所以要读取一个 int,您只需读取 4 个字节并将其转换为 int,依此类推。

字符串是一种特殊情况。从代码中不清楚此"String"类型是字符的内联数组,还是指向此类数组的指针。在第一种情况下,您需要知道每个字符串包含多少个字符,并简单地按顺序读取该数量的字符。在第二种情况下,您将无法取回字符串,因为它不会写入文件。指针对你毫无用处。

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

因此,长话短说,请使用其定义找出结构二进制布局,如果所有其他方法都失败,则在运行时使用调试器检查内存,或使用十六进制编辑器研究输出文件。然后将该规范写在某处,这将为您提供需要从文件中读取的内容。仅仅通过查看您给出的伪定义,就不可能确切地判断该布局是什么。

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

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

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

没有人指出一些对我来说特别有问题的东西(也许是因为我被它咬了)。该问题:数据成员bool Usadl; . sizeof(bool)因平台、编译器甚至同一编译器的版本而异。sizeof(bool)的常见值为 4 和 1。这会咬你。现在很难找到一台大的字节序机器,很难找到一台CHAR_BIT不是 8 或sizeof(int)不是 4 的计算机。sizeof(bool)的情况并非如此。

Chad 的团队同意其他人的意见,需要记录文件中记录的结构,然后确保生成文件的程序显式写入此结构,包括元素大小、填充和字节序。不要依赖类布局来为您执行此操作。那只是自找麻烦。

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