OCaml C interface undefined reference to "caml_local_roots"

Hi !

I’m trying to compile a very simple C++ project for OCaml. Here is what I did so far:

#include <iostream>
#include <string>

#include "osm.hpp"

extern "C" {
    #include <caml/mlvalues.h>
    #include <caml/memory.h>
    #include <caml/alloc.h>
    #include <caml/custom.h>
}

extern "C"
CAMLprim value osm_from_file(value file)
{
    CAMLparam1 (file);
    std::string fileName = String_val(file);

    std::cout << fileName << std::endl;

    CAMLreturn (Val_unit);
}

extern "C"
CAMLprim value osm_read_data(value osmObj)
{
    CAMLparam1 (osmObj);

    std::cout << "Testing" << std::endl;

    CAMLreturn (Val_unit);
}

I’m compiling it using CMake for the “osm” little class I’m writing and Dune to link it with my OCaml project.

Here is what I get while building my project with Dune.

$>> dune build
    ocamlopt project/bin/main.exe (exit 2)
(cd _build/default && /home/user/.opam/4.12.0/bin/ocamlopt.opt -w @1..3@5..28@30..39@43@46..47@49..57@61..62-40 -strict-sequence -strict-formats -short-paths -keep-locs -g -o project/bin/main.exe project/lib/project.cmxa project/bin/osm/cosm.cmxa -I project/bin/osm project/bin/.main.eobjs/native/dune__exe.cmx project/bin/.main.eobjs/native/dune__exe__Map.cmx project/bin/.main.eobjs/native/dune__exe__Main.cmx -linkall -cclib -lstdc++)
/usr/bin/ld: project/bin/osm/libbinding.a(binding.cpp.o): in function `osm_from_file':
binding.cpp:(.text+0x55e): undefined reference to `caml_local_roots'
/usr/bin/ld: binding.cpp:(.text+0x56c): undefined reference to `caml_local_roots'
/usr/bin/ld: binding.cpp:(.text+0x57b): undefined reference to `caml_local_roots'
/usr/bin/ld: binding.cpp:(.text+0x61f): undefined reference to `caml_local_roots'
/usr/bin/ld: project/bin/osm/libbinding.a(binding.cpp.o): in function `osm_read_data':
binding.cpp:(.text+0x6ba): undefined reference to `caml_local_roots'
/usr/bin/ld: project/bin/osm/libbinding.a(binding.cpp.o):binding.cpp:(.text+0x6c5): more undefined references to `caml_local_roots' follow
collect2: error: ld returned 1 exit status
File "caml_startup", line 1:
Error: Error during linking (exit code 1)

caml_local_roots should be defined in “caml/memory.h” but it looks like there is a problem while linking this file with my project.

Here is my Dune file to compile the “osm” library.

(library
    (name cosm)
    (c_library_flags :standard -lstdc++ -lbz2 -lexpat -lz -lpthread)
    (foreign_archives binding)
)

(rule
    (deps (source_tree osm))
    (targets libbinding.a dllbinding.so)
    (action
        (no-infer
            (progn
                (chdir osm (run make))
                (copy osm/libbindingsh.so dllbinding.so)
                (copy osm/libbinding.a libbinding.a)
))))

Does anyone has an idea on how can I solve this problem ?

It looks like you need to link with libasmrun.a (the runtime system).

Typically this should be taken care for you if you build your stubs using the (foreign_stubs ...) field, see General concepts — dune documentation.

Cheers,
Nicolás

1 Like

Hi !

Thanks for your reply. I’m using the foreign_archives stanza because I need CMake in order to compile my library (it’s using external C++ dependencies).

Even when adding the -lasmrun flag, I still got the same error.

Do you have an idea how can I “reproduce” the same behavior of foreign_stubs with the foreign_library ? Or maybe foreign_stubs is usable with an external build system like CMake but I saw nothing on the doc ?

If I understand correctly you have a C++ library, osm, that you build with CMake, and also some C bindings that are currently being built together with the C++ library, so that libbinding.a contains both the C++ library and the bindings. The idea would be to continue to use foreign_archive to build the osm library, but move the bindings to the OCaml library using foreign_stubs, something like:

(library
 (name cosm)
 (c_library_flags ...)
 (foreign_stubs (language c) (names binding))
 (foreign_archives osm))

(rule
 (deps (source_tree osm))
 (target libosm.a dllosm.so)
 (action (progn ...)))

Cheers,
Nicolás

1 Like

Thanks a lot for your help, looks like it’s on the good way.

Actually, it broke the CMake build chain.

I’m using a library called “osmium” which has been installed using vcpkg. To build it, I did a CMake file that tells what libraries to link when building. In fact, I was building binding.cpp with this makefile so I had no errors regarding the libraries used in my “osm” library.

I did what you advised. Looks like my original problem has been solved but I now have an other: since binding.cpp is no more build using my CMake file, I guess that gcc can no more know where to find the libraries used in osm.

Here is the error I have:

$ dune build
    make project/bin/osm/dllosm.so,project/bin/osm/libosm.a
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/project/project/bin/osm/osm
[ 25%] Linking CXX shared library libosmsh.so
[ 50%] Built target osmsh
[ 75%] Linking CXX static library libosm.a
[100%] Built target osm
     gcc project/bin/osm/binding.o (exit 1)
(cd _build/default/project/bin/osm && /usr/bin/gcc -O2 -fno-strict-aliasing -fwrapv -fPIC -g -I /home/user/.opam/4.12.0/lib/ocaml -o binding.o -c osm/binding.cpp)
In file included from osm/binding.cpp:14:
osm/mapping/osm.hpp:13:10: fatal error: osmium/io/any_input.hpp: No such file or directory
   13 | #include <osmium/io/any_input.hpp>
  |          ^~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

So no, here is my current dune file to compile the library:

(library
    (name osm)
    (foreign_stubs (language cxx) (names binding))
    (foreign_archives osm)
    (c_library_flags
        (:standard -lstdc++ -lbz2 -lexpat -lz -lpthread)))

(rule
    (deps (source_tree osm))
    (targets libosm.a dllosm.so)
    (action
        (no-infer
            (progn
                (chdir osm (run make))
                (copy osm/libosmsh.so dllosm.so)
                (copy osm/libosm.a libosm.a)
))))

I’m sorry if what I say isn’t very accurate, I’m far to be an expert in C/C++ compiling.

Do you have an idea on how can I now tell dune to tell gcc that this library has to be included ?

It looks like the bindings.cpp file is still inside the osm directory and CMake is trying to “build” it. You should take it out of that directory and move it to the parent directory (where the cosm OCaml library lives). The build of the osmium library has nothing to do with that of the bindings, so there is no reason to keep them in the same directory.

Cheers,
Nicolás

1 Like

Hi !

Again, thanks a lot for the time you take to answer.

I still have the same problem.

Here is the architecture of the project:

project
|--bin
|----osm
|----|-- osm
|----|--|--mapping
|----|--|--|--osm.cpp
|----|--|--|--osm.hpp
|----|--|--CMakeLists.txt
|----|--|--Makefile
|----|--binding.cpp
|----|--dune

Here is my binding.cpp file:

#include <iostream>
#include <string>

#include "osm/mapping/osm.hpp"

extern "C" {
    #define CAML_NAME_SPACE
    #include <caml/mlvalues.h>
    #include <caml/memory.h>
    #include <caml/alloc.h>
    #include <caml/custom.h>
}

extern "C"
CAMLprim value ocaml_osm_from_file(value file)
{
    CAMLparam1 (file);
    std::string fileName = String_val(file);

    Osm osm("testing");

    std::cout << fileName << std::endl;

    CAMLreturn (Val_unit);
}

And now, here is my osm/dune file. I tried to do what you advised:

(library
    (name cosmium)
    (foreign_stubs (language cxx) (names binding))
    (foreign_archives osm)
    (c_library_flags
        (:standard -lstdc++ -lbz2 -lexpat -lz -lpthread)))

(rule
    (deps (source_tree osm))
    (targets libosm.a dllosm.so)
    (action
        (no-infer
            (progn
                (chdir osm (run make))
                (copy osm/libosmsh.so dllosm.so)
                (copy osm/libosm.a libosm.a)
))))

It doesn’t compile, and I got this error when compiling with dune build:

dune build
         gcc project/bin/osm/binding.o (exit 1)
(cd _build/default/project/bin/osm && /usr/bin/gcc -O2 -fno-strict-aliasing -fwrapv -fPIC -g -I /home/user/.opam/4.12.0/lib/ocaml -o binding.o -c binding.cpp)
In file included from binding.cpp:14:
osm/mapping/osm.hpp:13:10: fatal error: osmium/io/any_input.hpp: No such file or directory
   13 | #include <osmium/io/any_input.hpp>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

In fact, when compiling osm.cpp on its own, with make, it does works well (everything is well included). Do I have to pass some flags to dune ? What did I miss ?

Thanks a lot,

This is not a problem compiling osm.cpp but binding.cpp. You need to pass the required include directories in the (foreign_stubs ...) using (include_dirs ...), eg

(foreign_stubs (language cxx) (include_dirs osm) (names binding))

If you do this you will need to change your #include "osm/mapping/osp.hpp" to #include "mapping/osp.hpp" inside binding.cpp because dune will pass -I osm when compiling it.

Cheers,
Nicolás

2 Likes

It worked, thanks a lot @nojb for your help and the time you took.

Have an excellent day !