Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embedding OCaml in C: linking error

Tags:

c

linker

ocaml

I'm trying to compile a program using mixed C and Ocaml sources, with the main of the application in C calling some pieces of OCaml code. All right, no problem here, It's seems to be a common operation, fully documented, easy to do with the standard Ocaml tools.

Let me explain a bit, this kind of compilation is divided in 4 steps : Caml compiling to Caml objects, then compiling the Caml to C objects, then compiling the C files, and lastly compiling all the C objects together and getting the executable.

The theory is, the Ocaml compiler will embed the Caml runtime, GC, and all its stuff automatically, and we just have to indicate if we use whichever the ocaml bytecode (referencing -lcamlrun) or the native binary (referencing -lasmrun).

So, it seems to be quite simple, let's do it. Steps 1, 2 and 3 went as expected, good! Only the 4th step is problematic. Just take a look:

cc -o /home/thomas/Documents/projects/ocaml/GogoGame/bin/GogoPlayer.exe \
        -L/usr/lib/ocaml  -lcamlrun \
        /home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o \
            /home/thomas/Documents/projects/ocaml/GogoGame/src/init.o \
            /home/thomas/Documents/projects/ocaml/GogoGame/src/interface.o \
            /home/thomas/Documents/projects/ocaml/GogoGame/src/caml_func.oo
/home/thomas/Documents/projects/ocaml/GogoGame/src/interface.o: In function `main':
interface.c:(.text+0x0): multiple definition of `main'
/usr/lib/ocaml/libcamlrun.a(main.o):(.text+0x0): first defined here
/usr/lib/ocaml/libcamlrun.a(floats.o): In function `caml_exp_float':
(.text+0x488): undefined reference to `exp'
/usr/lib/ocaml/libcamlrun.a(floats.o): In function `caml_fmod_float':
(.text+0x4f9): undefined reference to `fmod'
/usr/lib/ocaml/libcamlrun.a(floats.o): In function `caml_log_float':
(…)
/usr/lib/ocaml/libcamlrun.a(unix.o): In function `caml_dlopen':
(.text+0x2ed): undefined reference to `dlopen'
/usr/lib/ocaml/libcamlrun.a(unix.o): In function `caml_dlclose':
(.text+0x300): undefined reference to `dlclose'
/usr/lib/ocaml/libcamlrun.a(unix.o): In function `caml_dlsym':
(.text+0x31b): undefined reference to `dlsym'
/usr/lib/ocaml/libcamlrun.a(unix.o): In function `caml_dlerror':
(.text+0x342): undefined reference to `dlerror'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0xc): undefined reference to `caml_array_get_addr'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0x10): undefined reference to `caml_array_get_float'
(...)
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0x31c): undefined reference to `caml_lazy_make_forward'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0x320): undefined reference to `caml_get_public_method'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0x3ac): undefined reference to `caml_terminfo_setup'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0x3b0): undefined reference to `caml_terminfo_backup'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0x3b4): undefined reference to `caml_terminfo_standout'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init_camlcode.o:(.data.rel+0x3b8): undefined reference to `caml_terminfo_resume'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__f_1030':
(.text+0xc): undefined reference to `camlPervasives'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__f_1030':
(.text+0x11): undefined reference to `camlPervasives__output_string_1191'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__f_1030':
(.text+0x19): undefined reference to `camlPervasives__string_of_int_1130'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__f_1030':
(.text+0x20): undefined reference to `camlPervasives'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__f_1030':
(.text+0x25): undefined reference to `camlPervasives__output_string_1191'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__format_result_1034':
(.text+0x9c): undefined reference to `camlPrintf__sprintf_1414'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__entry':
(.text+0xe1): undefined reference to `caml_c_call'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__entry':
(.text+0xfb): undefined reference to `caml_c_call'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__entry':
(.text+0x115): undefined reference to `caml_c_call'
/home/thomas/Documents/projects/ocaml/GogoGame/src/init.o: In function `camlInit__f_1030':
(.text+0x32): undefined reference to `camlPervasives__print_newline_1276'
collect2: ld returned 1 exit status
make: *** [GogoPlayer] Error 1

IMHO, it seems that there is two errors:

  • Multiple definitions of main
  • The linker doesn't find the module Pervasive

I have really no idea how to fix that, maybe I have to link an other file. Does someone have an idea ?

As asked, I put the code that give these errors. It will be quite simple because there is very little code, most of it was given in an example in the documentation.

init.ml

let f x = print_string "f is applied to "; print_int x; print_newline()
let rec fib n = if n < 2 then 1 else fib(n-1) + fib(n-2)
let format_result n = Printf.sprintf "Result is: %d\n"

let _ =
  Callback.register "Arbitrary Name" f;
  Callback.register "fib" fib;
  Callback.register "format_result" format_result

caml_func.c

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

void call_caml_f(int x)
{
    static value * closure_f = NULL;
    if (closure_f == NULL) /* First time around, look up by name */
        closure_f = caml_named_value("Arbitrary Name");

    caml_callback(*closure_f, Val_int(x));
}

int fib(int n)
{
    static value * fib_closure = NULL;
    if (fib_closure == NULL) fib_closure = caml_named_value("fib");
    return Int_val(caml_callback(*fib_closure, Val_int(n)));
}

char * format_result(int n)
{
   static value * format_result_closure = NULL;
   if (format_result_closure == NULL)
      format_result_closure = caml_named_value("format_result");
   return strdup(String_val(caml_callback(*format_result_closure, Val_int(n))));
   /* We copy the C string returned by String_val to the C heap
   so that it remains valid after garbage collection. */
}

interface.c

#include <stdio.h>
#include "caml_func.c"

#define BYTECODE

int main(int argc, char **argv)
{
#ifdef BYTECODE
    caml_startup(argv);
#else
    caml_main(argv);
#endif

    /* Make sure that stdout is not block buffered. */
    setbuf(stdout, NULL);

    /* Process GTP commands. */
    //gtp_main_loop(commands, stdin, NULL);
    // CAML code here ?

    return 0;
}

And this is all. Obviously, I skipped all the meaningless stuff as this simple example should work and does not. It must be my Makefile, which follows.

By the way, it's quite ugly. If you have proposals for this kind of application (Caml inside C), or refactoring suggestions, I'll take them all.

Makefile.ocaml

.PHONY: all clean mrproper

# RULES and EXEC are magically set in Makefile.magic
all: depend $(RULES) $(EXE)
    @echo [DONE]

mli: $(CAML_ONLY:.ml=.mli)
ml-byte: $(CAML_ONLY:.ml=.cmo)
ml-called-byte: $(CAML_CALLED_BY_C:.ml=.$(OBJ))
ml-nativ: $(CAML_ONLY:.ml=.cmx)
ml-called-nativ: $(CAML_CALLED_BY_C:.ml=.$(OBJ))
c-wrapper: $(C_WRAPPERS:.c=.oo)
c-only: $(C_ONLY:.c=.o)

$(EXE):
    $(CC) -o $(BIN)/$(EXE).exe \
        $(FLAGS) \
        -L$(OCAMLLIB) $(LINKED) -l$(RUNLIB) \
        $(wildcard $(SRC)/*.$(OBJ)) $(wildcard $(SRC)/*.oo) # */

%.o: %.c
    $(CC) $(FLAGS_C) -c $< -o $(SRC)/$(*F).o
%.mli: %.ml
    $(OCAMLC) $(FLAGS_ML) -i $< > $(SRC)/$(*F).mli
%.cmi: %.mli
    $(OCAMLC) $(FLAGS_ML) -c $< -o $(SRC)/$(*F).cmi
%.cmo: %.ml
    $(CAMLC) $(FLAGS_ML) -c $< -o $(SRC)/$(*F).cmo
%.cmx: %.ml
    $(CAMLOPT) $(FLAGSOPT) -c $< -o $(SRC)/$(*F).cmx
# native
%.o: %.ml
    $(cd $(SRC))
    $(OCAMLC) -output-obj -o $(*F)_camlcode.o \
        $(FLAGS_MLC) \
        $<
# bytecode
%.ob: %.ml
    $(cd $(SRC))
    $(OCAMLOPT) -output-obj -o $(*F)_camlcode.ob \
        $(FLAGS_MLC) \
        $<
%.oo: %.c
    $(CC) $(FLAGS_WRAP) -c $< -o $(SRC)/$(*F).oo


clean_mli:
    rm -f $(SRC)/*.mli # */
clean:
    rm -f $(BIN)/*.{a,o,oo,cmi,cmo,cmx} # */
    rm -f $(SRC)/*.{a,o,oo,cmi,cmo,cmx} # */

mrproper: clean, clean_mli
    rm -f $(BIN)/$(EXE)

depend:
    $(OCAMLDEP) $(INCLUDES) $(SRC)/*.ml $(SRC)/*.mli > .depend # */

include .depend
like image 670
Thomas Wickham Avatar asked Apr 20 '12 10:04

Thomas Wickham


1 Answers

Your link command is incorrect in two ways:

  1. You need to link with -ldl for dlopen, etc.
  2. You must put libraries after objects that reference them (i.e. your -lcamlrun arguments is in the wrong place on the link line). The order of arguments on the link line matters.
like image 57
Employed Russian Avatar answered Oct 12 '22 16:10

Employed Russian