我完全同意 raiph 的回答和 cmets,只是想补充一点。
我考虑了两种类型的解析,一种是您可以根据内部找到的内容进行解析,另一种是您使用描述数据排列方式的外部架构。二进制数据可以任意排列msgpack 是二进制数据的前者示例。
您正在使用的示例,解压二进制结构是后者的示例。
看起来NativeCallCStructs几乎可以直接做你想做的事。也许它已经可以做到,我只是不知道,但它似乎缺乏表达嵌入式大小数组的能力。 (这还有效吗?)
如果没有这些,您的首要工作就是弄清楚您要解包的结构。有几种方法可以做到这一点。第一个是最简单的,但也许最容易出错——看看结构。 (我将编造一些虚假的定义,以便我可以使用真实的数字):
记录.h:
#define UT_LINESIZE 80
#define UT_IDSIZE 4
#define UT_NAMESIZE 50
#define UT_HOSTSIZE 20
struct record {
short int ut_type;
char ut_line[UT_LINESIZE];
char ut_id[UT_IDSIZE];
char ut_user[UT_NAMESIZE];
char ut_host[UT_HOSTSIZE];
};
看着那个,我可以捕捉到每个字段的偏移量和大小:
constant \type-size := nativesizeof(int16);
constant \line-size := 80;
constant \id-size := 4;
constant \user-size := 50;
constant \host-size := 20;
constant \record-size := type-size + line-size + id-size + user-size + host-size;
constant \type-offset := 0;
constant \line-offset := type-offset + type-size;
constant \id-offset := line-offset + line-size;
constant \user-offset := id-offset + id-size;
constant \host-offset := user-offset + id-size;
这里有一些注意事项——您必须充分了解您的格式才能考虑到任何alignment or padding。您在此处的示例比其他示例更容易工作。
这为我们提供了足够的信息来确定二进制结构中的哪些字节映射到每个字段。
接下来,您需要将每个字节块转换为正确的 Perl 类型。 NativeCall 的nativecast 例程可以为您做到这一点。它可以轻松地将一大块字节转换为多种 Perl 数据类型。
我将假设您的字段是 C 字符串,总是由 NUL 正确终止,并且适合解码为 UTF8。您可以针对其他特定情况进行调整。
use NativeCall;
class record {
has Int $.ut-type;
has Str $.ut-line;
has Str $.ut-id;
has Str $.ut-user;
has Str $.ut-host;
}
sub unpack-buf(Mu:U $type, Blob $buf, $offset, $size) {
nativecast($type, CArray[uint8].new($buf[$offset ..^ $offset+$size]))
}
sub unpack-record($buf) {
record.new(
ut-type => unpack-buf(int16, $buf, type-offset, type-size),
ut-line => unpack-buf(Str, $buf, line-offset, line-size),
ut-id => unpack-buf(Str, $buf, id-offset, id-size),
ut-user => unpack-buf(Str, $buf, user-offset, user-size),
ut-host => unpack-buf(Str, $buf, host-offset, host-size)
)
}
然后您可以从数据文件中查找/读取二进制数据以获取单个记录,或者只是遍历所有记录:
my @data = gather {
given 'data'.IO.open(:bin) {
while .read(record-size) -> $buf {
take unpack-record($buf)
}
.close
}
}
因为我们从 C 结构定义中手动复制了一些东西,所以对它的任何更改都必须更新,对齐/填充可能总是会咬我们。
另一种选择是直接读取C头文件并使用sizeof()和offsetof()将所有数字交给我们。这自然会考虑对齐/填充。您甚至可以在 Perl 代码中使用 TCC 直接访问头文件。除了上面所有的 constant 行之外,您可以使用它从 C .h 文件中提取所有内容。
my $tcc = TCC.new('');
$tcc.compile: q:to/END/;
#include <stddef.h>
#include "record.h"
size_t record_size() { return sizeof(struct record); }
size_t type_offset() { return offsetof(struct record, ut_type); }
size_t type_size() { return sizeof(short int); }
size_t line_offset() { return offsetof(struct record, ut_line); }
size_t line_size() { return UT_LINESIZE; }
size_t id_offset() { return offsetof(struct record, ut_id); }
size_t id_size() { return UT_IDSIZE; }
size_t user_offset() { return offsetof(struct record, ut_user); }
size_t user_size() { return UT_NAMESIZE; }
size_t host_offset() { return offsetof(struct record, ut_host); }
size_t host_size() { return UT_HOSTSIZE; }
END
$tcc.relocate;
my &record-size := $tcc.bind('record_size', :(--> size_t));
my &type-offset := $tcc.bind('type_offset', :(--> size_t));
my &type-size := $tcc.bind('type_size', :(--> size_t));
my &line-offset := $tcc.bind('line_offset', :(--> size_t));
my &line-size := $tcc.bind('line_size', :(--> size_t));
my &id-offset := $tcc.bind('id_offset', :(--> size_t));
my &id-size := $tcc.bind('id_size', :(--> size_t));
my &user-offset := $tcc.bind('user_offset', :(--> size_t));
my &user-size := $tcc.bind('user_size', :(--> size_t));
my &host-offset := $tcc.bind('host_offset', :(--> size_t));
my &host-size := $tcc.bind('host_size', :(--> size_t));