(为了论证而忽略字节序 - 这只是一个测试用例/概念证明 - 我绝不会在实际代码中使用strcpy
!)
考虑以下简单的C代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* variables of type message_t will be stored contiguously in memory */
typedef struct {
int message_id;
char message_text[80];
} message_t;
int main(int argc, char**argv) {
message_t* m = (message_t*)malloc(sizeof(message_t));
m->message_id = 1;
strcpy(m->message_text,"the rain in spain falls mainly on the plain");
/* write the memory to disk */
FILE* fp = fopen("data.dat", "wb");
fwrite((void*)m, sizeof(int) + strlen(m->message_text) + 1, 1, fp);
fclose(fp);
exit(EXIT_SUCCESS);
}
它写的文件很容易从磁盘中读回:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int message_id;
char message_text[80];
} message_t;
int main(int argc, char**argv) {
message_t* m = (message_t*)malloc(sizeof(message_t));
FILE* fp = fopen("data.dat", "rb");
fread((void*)m, sizeof(message_t), 1, fp);
fclose(fp);
/* block of memory has structure "overlaid" onto it */
printf("message_id=%d, message_text='%s'\n", m->message_id, m->message_text);
exit(EXIT_SUCCESS);
}
E.g。
$ ./write
$ ./read
message_id=1, message_text='the rain in spain falls mainly on the plain'
我的问题是,在OCaml中,如果我只有:
type message_t = {message_id:int; message_text:string}
我如何获得该数据? Marshal
无法做到,input_binary_int
也无法做到。我可以调用C中的辅助函数,比如“什么是sizeof(int)
”,然后得到n个字节并调用C函数“将这些字节转换为int”,例如但在这种情况下我不能添加任何新的C代码,“解包”必须在OCaml中完成,基于我所知道的“应该”。只是在sizeof
s的块中搜索字符串或寻找'\ 0'或是否有一个聪明的方法?谢谢!
答案 0 :(得分:6)
为了进行这种低级结构处理,我发现OCaml Bitstring 非常非常方便。如果您将所有80个字符写入磁盘,那么您的message_t的等效阅读器就是这个:
bitmatch (Bitstring.bitstring_from_file "data.dat") with
| { message_id : 32;
message_text : 8 * 80 : string;
} ->
Printf.printf "message_id=%ld, message_text='%s'\n"
message_id message_text
| { _ } -> failwith "Not a valid message_t"
按原样,你必须修剪message_text
,但也许你可以通过bitstring来完成这类任务。
答案 1 :(得分:4)
在了解如何在Ocaml中编写代码之前,您需要弄清楚数据表示是什么。您的C代码在阅读器和编写器之间不一致:编写器只为字符串写入strlen(m->message_text)+1
个字节,而阅读器则需要最多80个字节。
我的建议是用同一种语言(C或Ocaml)进行所有编组操作。我推荐Ocaml的编组库,它已经可以工作,跨平台且易于使用。
如果您需要C和Ocaml编组代码之间的互操作性,那么您需要坐下编组格式,并在两种语言中实现相同的规范。在您这样做之前,请考虑是否可以使用文本表示,这种表示不易出错,并且更容易使用第三方工具进行检查和操作,但更笨重。 JSON是一种轻量级数据表示格式,您也可以转向重量级XML。如果您的所有数据都像一个整数和一个字符串一样简单,并且字符串不包含换行符,您可以写入十进制的整数,后跟一个空格(或:
或,
)后跟字符串后跟换行符。
如果C编组格式是预定义的并且您无法更改它,请注意它与平台相关(取决于体系结构和C编译器),并且Ocaml不允许您访问此类平台详细信息。因此,最好的办法是将Ocaml程序与C帮助程序链接,确保您的帮助程序使用与原始应用程序相同的C类型表示(sizeof(int)
,字节顺序,结构填充)。
答案 2 :(得分:2)
您依赖于在同一平台上使用相同的C编译器,以避免必须考虑写入和读回数据的格式。不幸的是,如果您尝试在C和OCaml之间进行互操作,那么您就没那么奢侈了。你有来计算结构中的字节数,弄清楚整数是小端还是大端,并在OCaml端进行相应的编码。
您必须单独手动解组每种类型,实际上是解析二进制文件。例如,要读取您必须使用的小端32位整数:
let input_le_int32 inch =
let res = ref 0l in
for i = 0 to 3 do
let byte = input_byte inch in
res := Int32.logor !res (Int32.shift_left (Int32.of_int byte) (8*i))
done;
!res
并读取以NUL结尾的字符串:
let input_c_string inch =
let res = Buffer.create 256 in
try while true do
let byte = input_byte inch in
if byte = 0 then raise Exit else
Buffer.add_char res (char_of_int byte)
done; assert false with Exit ->
Buffer.contents res
如果一切正常,您可以通过以下方式回读您的结构:
let input_message inch =
let message_id = input_le_int32 inch in
let message_text = input_c_string inch in
{ message_id; message_text; }
注意:命令式(!)对读取进行排序以避免无序读取字段。 不使用并行let
作业。
答案 3 :(得分:1)
感谢所有人的建议;我有written up我决定在博客中采用的方法。