在不使用包装类型的情况下,是否可以从OCaml调用C函数?

3
我想写一个OCaml编译器(非常微小、简单——实际上我不知道该怎么做!)。与此同时,我希望避免在项目中检查任何ISO-C代码(尽管我对C相当熟悉;这里的目标是仅学习和使用OCaml)。为此,我需要在OCaml中编写编译后语言的“运行时”,将其与主项目分开编译,然后链接到编译器本身的输出。不幸的是,似乎任何外部函数——即使它们不涉及任何OCaml数据结构/OCaml堆,也需要使用OCaml的C宏来构建:
CAMLprim value scheme_entry(value unit) {
    int i;
    i = 42;
    return Val_int(i);
}

如果我自己发出汇编指令,这可能不是一个选择。(至少在我学到更多之前不是!)

是否有任何方法(包括hacky的方法-这是个人学习项目),可以从OCaml调用以下非常简单的函数?

_scheme_entry:
    movl $42 %eax
    ret

参考资料,我正在学习 Ghuloum 的 IACC:http://ell.io/tt$ocameel

据我所知,Ocaml并不打算使用机器级指令,出于可移植性的原因。因此,没有任何直接从Ocaml内联汇编代码的方法。此外,ABI也不是一成不变的,可能会随着时间的推移而改变。如果需要,您仍然可以尝试找出运行时调用约定,并向OCaml编译器提供obj文件(在使用C代码时最终就是这样)。 - didierc
2个回答

3

很遗憾,看起来任何外部函数——即使是不涉及任何OCaml数据结构或OCaml堆的函数,都需要使用OCaml的C宏进行构造:

不,它们不需要。如果您的函数根本不涉及OCaml,或者不分配或处理分配的值,则可以直接调用函数,例如:

value scheme_entry(value unit) {
    int i;
    i = 42;
    return Val_int(i);
}

在 OCaml 方面:

external scheme_entry : unit -> int = "scheme_entry" [@@noalloc]

在最新的OCaml版本中,我们拥有更多的控制权,因此可以传递浮点数和更大的整数而无需装箱/拆箱。阅读这里了解更多信息。
请注意,Val_int只是一个宏,将C整数向左移动一位并将最低有效位置为1,即(((unsigned)(x) << 1)) + 1)。因此,如果您不需要将OCaml整数转换为C整数或反之亦然,则甚至不需要使用这些宏,因此您的运行时可以完全不知道OCaml,例如:
 void runtime_init(void) {
    printf("Hello from runtime");
 }

在 OCaml 方面:

 val runtime_init : unit -> unit = "runtime_init" [@@noalloc]

注意:在OCaml 4.03之后,添加了[@@noalloc]属性,在该版本之前,您应该使用"noalloc",例如:

 val runtime_init : unit -> unit = "runtime_init" "noalloc"

当然,你的极简函数应该遵循适用于特定体系结构和操作系统的C调用约定。因此,如果您的函数破坏了应该被保留的一些寄存器,那么您可能会遇到问题,例如在amd64 ABI上,您应该保留rbp、rbx、r12-r15。这个要求与OCaml无关,只是一个普通的C调用约定。

虽然上面关于 ctypes 库的回答几乎肯定是我需要的,但我有一个快速问题关于你仍然出色的解释:Val_int 宏怎么办?我的问题的整个重点(尽管看起来我表达得不好!)是避免与 OCaml 外部交互的那些 需要 C 代码 的部分(例如,依赖于 OCaml 标头的 Val_int),因此我可以自己编译这个“外部代码”。 - ELLIOTTCABLE
在那里,某种方式未能提出实际问题。让我们这样表达:Val_int和类似的东西是否稳定,即定义不会改变?如果是这样,我可以在编译器端实现它们,因此编译后的汇编代码可以调用(或被编译后的OCaml运行时调用),而无需任何C“粘合剂”... - ELLIOTTCABLE
1
嗯,它很稳定,但使用OCaml整数来编写编译器是不正确的。您应该使用nativeint并使用unboxed属性。然后普通的机器整数将被传递到和从外部函数中。 - ivg
不需要在这里使用ctypes。它们会引入很多中间翻译(在OCaml中即时进行 - 这是回答是否可能在编译器端执行的问题)。由于您将操作机器字和浮点数,因此根本不需要任何翻译。 - ivg

2
您可能想要研究使用 ctypes 库和相关软件包,这些软件包可以通过OPAM获取。
opam install ctypes ctypes-foreign posix-types

例子:

open Foreign
open Ctypes
open Posix_types

let getpid = foreign "getpid" (void @-> returning pid_t)
let () = Printf.printf "%d\n" (Pid.to_int (getpid ()))

编译这个需要安装 ctypesctypes.foreignposix-types 包。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接