从OCaml获取C二进制数据

时间:2011-05-16 21:43:23

标签: c ocaml binary-data ffi

(为了论证而忽略字节序 - 这只是一个测试用例/概念证明 - 我绝不会在实际代码中使用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'或是否有一个聪明的方法?谢谢!

4 个答案:

答案 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我决定在博客中采用的方法。