在64位系统上读取32位打包的二进制数据

时间:2020-03-06 14:44:29  来源:igfitidea点击:

我正在尝试编写一个Python C扩展,该扩展读取打包的二进制数据(将其存储为结构的结构),然后将其解析为Python对象。一切都可以在32位计算机上正常运行(二进制文件始终以32位体系结构编写),而不是在64位设备上运行。有没有"首选"的方式来做到这一点?

作为示例,将发布很多代码:

struct
{
    WORD    version;
    BOOL    upgrade;
    time_t  time1;
            time_t  time2;
} apparms;

File *fp;
fp = fopen(filePath, "r+b");
fread(&apparms, sizeof(apparms), 1, fp);
return Py_BuildValue("{s:i,s:l,s:l}",
  "sysVersion",apparms.version,
  "powerFailTime", apparms.time1,
  "normKitExpDate", apparms.time2
 );

现在,在32位系统上,这很好用,但是在64位系统上,我的time_t大小有所不同(32位和64位长)。

该死的,你们这些人很快。

帕特里克(Patrick),我最初开始使用struct包,但发现它只是减慢我的需求的一种方式。另外,我一直在寻找写Python扩展的借口。

我知道这是一个愚蠢的问题,但是我需要注意哪些类型?

谢谢。

解决方案

我们读取二进制数据的代码是什么?确保将数据复制到适当大小的类型(如" int32_t")中,而不只是" int"。

为什么不使用struct包?

尽管在数据中间对齐结构始终是一个问题,但"结构"模块应该能够做到这一点。但是,很难做到这一点不是很困难:找出(一次)struct-in-structs对齐的边界,然后(手动地,使用" x"说明符)填充到该边界。我们可以通过将struct.calcsize()与实际数据进行比较来仔细检查填充。这肯定比为其编写C扩展要容易。

为了继续使用Py_BuildValue(),我们有两个选择。我们可以在编译时确定time_t的大小(就基本类型而言,所以是'int'或者'long'或者'ssize_t'),然后将正确的格式字符用于Py_BuildValue -'i'表示int,' l'表示很长,'n'表示ssize_t。或者,我们可以手动使用PyInt_FromSsize_t(),在这种情况下,编译器将为我们进行向上转换,然后使用" O"格式字符将结果传递给Py_BuildValue。

明确指定数据类型(例如整数)为32位。否则,如果我们在读取时有两个相邻的整数,它们将被读取为一个64位整数。

在处理跨平台问题时,需要注意的两件事是:

  • 一点点如果打包的数据是用32位整数编写的,则所有代码在读写时都必须显式指定32位整数。
  • 字节顺序。如果将代码从英特尔芯片转移到PPC或者SPARC,则字节顺序将是错误的。我们将必须导入数据,然后将其字节翻转以使其与当前体系结构匹配。否则,12(0x0000000C)将被读取为201326592(0x0C000000)。

希望这会有所帮助。

我们需要确保对结构使用与体系结构无关的成员。例如,一个int在一种体系结构上可以是32位,而在另一种体系结构上可以是64位。正如其他人所建议的那样,请改用int32_t样式类型。如果结构包含未对齐的成员,则可能还需要处理编译器添加的填充。

跨体系结构数据的另一个常见问题是字节序。英特尔i386架构是低端的,但是如果我们在完全不同的机器(例如Alpha或者Sparc)上阅读,则也必须担心这一点。

Python struct模块使用作为格式字符串的一部分传递的前缀来处理这两种情况。

  • @-使用本地大小,字节序和对齐方式。 i = sizeof(int),l = sizeof(long)
  • =-使用本地字节序,但使用标准大小和对齐方式(i = 32位,l = 64位)
  • <-小端标准大小/对齐方式
Big-endian standard sizes/alignment

通常,如果数据从计算机上通过,则应将字节序和大小/填充格式确定为特定的值,即。使用" <"或者">"作为格式。如果要在C扩展中处理此问题,则可能需要添加一些代码来处理它。