5

(为了争论而忽略字节顺序——这只是一个测试用例/概念证明——我也永远不会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);
}

例如

$ ./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 中的辅助函数,例如“what is sizeof(int)”,然后获取 n 个字节并调用 C 函数以“将这些字节转换为 int”,但在这种情况下,我无法添加任何新的 C 代码,即“解包”必须在 OCaml 中完成,基于我所知道的“应该”。是否只是在sizeofs 块中迭代字符串或寻找 '\0' 还是有一个聪明的方法?谢谢!

4

4 回答 4

5

对于这种低级结构处理,我发现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"

照原样,您将不得不 trim message_text,但也许 bitstring 通常是您想要执行此类任务的。

于 2011-05-17T19:05:24.977 回答
4

在您弄清楚如何在 Ocaml 中对此进行编码之前,您需要弄清楚您的数据表示是什么。您的 C 代码在读取器和写入器之间不一致:写入器只strlen(m->message_text)+1为字符串写入字节,而读取器期望完整的最大 80 个字节。

我的建议是使用相同的语言(C 或 Ocaml)进行所有编组。我推荐 Ocaml 的编组库,它已经在工作、跨平台且易于使用。

如果您需要 C 和 Ocaml 编组代码之间的互操作性,那么您需要采用编组格式,并在两种语言中实现相同的规范。在您这样做之前,请考虑您是否可以使用文本表示,这将更不容易出错并且更容易使用第三方工具进行检查和操作,但体积更大。JSON是一种轻量级的数据表示格式,也可以转向重量级的XML。如果您的所有数据确实像整数和字符串一样简单,并且字符串不包含换行符,则可以将整数写入十进制,后跟空格(或 a:或 a ,),后跟字符串,后跟换行符。

如果 C 编组格式是预定义的并且您无法更改它,请注意它是平台相关的(取决于架构和 C 编译器),并且 Ocaml 不会让您访问此类平台详细信息。因此,最好的办法是将您的 Ocaml 程序与 C 助手链接,确保您的助手使用与sizeof(int)原始应用程序相同的 C 类型表示(、字节序、结构填充)。

于 2011-05-16T23:04:28.357 回答
2

您依赖于在同一平台上使用相同的 C 编译器来避免必须考虑写入和读取数据的格式是什么。不幸的是,如果你试图在 C 和 OCaml 之间进行互操作,你就没有那么奢侈了。您必须计算结构中的字节数,确定整数是小端还是大端,并在 OCaml 端进行相应的编码。

您必须单独手动解组每种类型,实际上是解析二进制文件。例如,要读取 little-endian 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分配。

于 2011-05-17T13:14:44.807 回答
1

谢谢大家的建议;我已经在我的博客中写下了我决定采用的方法。

于 2011-05-17T22:06:14.207 回答