简体   繁体   中英

How to compile OCaml and C/C++ code that depend on each other

I'm having problems defining the signature of a C void function that accepts an uint64_t and a char* . I tried int64 -> string -> _ .

I also don't know how to compile my C++ file (with C interface) together

events.ml

open Lwt.Infix

external call: int64 -> string -> _ = "function_pointer_caller"

let begin_event pointer = 
    Lwt_unix.sleep 5.0 >>= fun () ->
        call pointer "message"

let () = Callback.register "begin_event" begin_event

interface.c :

#include <stdio.h>
#include <string.h>
#include <caml/mlvalues.h>
#include <caml/callback.h>
#include <caml/alloc.h>
#include <caml/bigarray.h>

extern void register_function_callback();

void print_from_event(char* message) {
    printf("OCaml event: %s\n", message);
}

void function_pointer_caller(uint64_t pointer, char* message)
{
    void (*f)(char *);
    f = pointer;
}

void register_function_callback() {
    static const value *begin_event_closure = NULL;
    if (begin_event_closure == NULL)
    {
        begin_event_closure = caml_named_value("begin_event");
        if (begin_event_closure == NULL)
        {
            printf("couldn't find OCaml function\n");
            exit(1);
        }
    }
    uint64_t pointer = (uint64_t) &function_pointer_caller;
    caml_callback(*begin_event_closure, (int64_t) &pointer);
}

main.cc

#include <stdio.h>
#include <caml/callback.h>

extern "C" void register_function_callback();

int main(int argc, char **argv)
{
  caml_startup(argv);
  register_function_callback();
  while (true)
  {
  }
  return 0;
}

I think there's no way to compile a.cc together with a.ml, because a.cc does not necessairly have a C interface. Maybe it's possible to compile the.ml to a.so object and link it to the.cc with the C interface?

Anyways, I did change interface.cc to interface.c and added interface.c to the ocamlopt command:

ocamlfind ocamlopt -o s -linkpkg -package lwt.unix -thread event_emitter.ml interface.c

g++ -o event_emitter_program -I $(ocamlopt -where) \
    s.o interface.o main.cc event_emitter.o $(ocamlopt -where)/libasmrun.a -ldl

The first command compiles ok, but g++ gives

event_emitter.o: In function `camlEvent_emitter__begin_event_90':
:(.text+0x3f): undefined reference to `camlLwt_unix__sleep_695'
:(.text+0x4c): undefined reference to `camlLwt__bind_1276'
collect2: error: ld returned 1 exit status

Notice that I plug the interface.o from the previous command ( ocamlopt ) and link in g++, because ocamlopt actually passes C files to a C compiler.

I don't know why it complains about Lwt_unix things, since I already compiled with them in ocamlopt .

The ocamlopt utility is doing a bit more than just linking the specified compilation units and libraries, it also embeds the architecture startup code, which is not included in the asmrun library. It is possible to obtain this code using the -output-obj , but I find it a little bit error-prone and hard to maintain as it is not really composable 1 .

So, it is better to rely on ocamlopt to build the final binary, which will consist of modules written in different languages. Each unit will be built with the appropriate to the language, in which it is written, tool. Let's build the events.ml compilation unit:

ocamlfind ocamlopt -c -package lwt.unix events.ml

Now, let's build the interface (we can use cc here, but using ocamlopt will save us a bit of hassle of providing the location of the includes)

ocamlfind ocamlopt -c interface.c 

Now, let's build the C++ part (but first fix it and use caml_main instead of caml_startup since we want the native runtime).

g++ -c main.cc -o main.o -I `ocamlc -where`

Now, when we have all units we're ready for the final linking command:

ocamlfind ocamlopt events.cmx interface.o main.o -package lwt.unix -package lwt -thread -linkpkg -o emitter

Et voila, we can run our program with ./emitter .


1) using this option for more than one compilation unit may easily lead to symbol conflicts.

You should be more careful with “warning” messages and not rely on things that bind the building process to your configuration. Avoid usage of root console please!

[WARNING] Running as root is not recommended

In the root environment path to libraries can be complicated.

According to your pastebin I see that linker is unable to locate the lwt_unix_stubs library. And don't mix '.a' and '.so' libraries please ( /root/.opam/4.10.0/lib/lwt/unix/liblwt_unix_stubs.a /root/.opam/4.10.0/lib/stublibs/dlllwt_unix_stubs.so ). '.a' is for static build while '.so' is for dynamic.

Example of build that works for me (Ubuntu Linux, ocaml 4.05.0):

ocamlfind ocamlopt -output-obj -dstartup -linkpkg -package lwt.unix -thread -o s.o event_emitter.ml
g++ -o event_emitter_program -I`ocamlfind c -where` -I $(ocamlopt -where) s.o interface.o main.cc -L `ocamlc -where` -L`ocamlfind query lwt.unix` -L`ocamlfind query threads` -lasmrun -llwt_unix_stubs -lunix -lbigarray -lcamlstr -lthreadsnat -lpthread -ldl

Usage of $(ocamlopt -where)/libasmrun.a is bad idea. It's more pedantic way to use -lasmrun and -L for path to libraries.

If you have problems with some undefined symbols, it's best to use readelf to find out which library is missing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM