I’m wondering if I am compiling and linking a simple one-file Fortran sublibrary correctly into my OCaml library. Ideally I would like to be able to make my library installable on linux and macos with a simple opam install.
After some fiddling and googling I have arrived at the project structure shown below, which seems to work for loading the Fortran library via ctypes like so:
integrate_mvnd.ml
open Containers
open Ctypes
[...]
let mvnd =
match
Sys.getenv "CAML_LD_LIBRARY_PATH"
|> String.split_on_char ':'
|> List.cons (Sys.getcwd ())
|> List.find_map (fun p ->
try
let filename = p ^ "/dllmvnd.so" in
Some (Dl.dlopen ~flags:Dl.[RTLD_NOW;] ~filename)
with Dl.DL_error _ -> None)
with
| Some l -> l
| None -> failwith "unable to load mvnd shared library"
let ocaml_mvnd arg1 arg2 [...] =
Foreign.foreign ~from:mvnd "mvnormaldist" (ptr.integer @-> [...])
Some points I’m not content with:
I have to manually check for the CAML_LD_LIBRARY_PATH
I have to build a static library although afaict this will never work the way the library is loaded, because otherwise dune complains
I am using hardcoded dll filenames, but when trying @{dll_ext} in dune that also gave .so on macos not .dylib as i had expected.
Am I using dune correctly? Is there a way to switch to either fully static linking of the fortran sublibrary or only dynamic linking?
I may be missing something, but I don’t see why you need to load your library dynamically. If you use foreign_archives, the Fortran library should have been linked statically into your final executable (at least on native code).
?
The Fortran function takes integer pointer and double pointer arguments and writes the result into one of the double pointers. I was under the impression that I would have to write a C stubs file if I want external functions but maybe that’s wrong?
Or can I use ctypes to build these pointers to pass to the external function and read them out later?
(clearly I don’t grok how external functions and/or ctypes work)
This is something I had tried. If I remove let mvnd = [...] and also remove the ~from argument but leave everything else the same, I get a runtime error: exception Dl.DL_error("dlsym(RTLD_DEFAULT, mvnormaldist): symbol not found") . If i remove only ~from but leave the dynamic loading in place, with unused resulting value mvnd, then the symbol is found – presumably due to the mere act of loading the library.
I was trying to dune exec a native code executable (.exe) when this error was produced. i haven’t specified any special compilation/linking mode fields for the executables so i think native is chosen by default
It looks like the symbols from the Fortran lib are being dropped by the linker because they are not referenced from the main program (they are only being addressed dynamically using dlsym/ctypes). How to keep this from hapenning is rather OS-dependent (I don’t remember how to do it off the top of my head).
and tried to compile. This time, the linker complains it cannot find __gfortran_runtime_error_at which seems to be a gfortran internal symbol. Also adding -cclib -lgfortran does not find a corresponding library. On my system I have libgfortran.dylib but as it appears no libgfortran.a. (I got this lead from https://askubuntu.com/questions/581905/undefined-references-to-gfortran-runtime-error-at)
Anyway, the static compilation option seems more painful than the dynamic library loading. I would be content with making the latter work without also producing a static library. Can I just leave out the (foreign_archives) field?
which installs both the static and dynamic libraries. if i leave out foreign_archives neither get installed. if i keep foreign_archives but prevent building of the static library in the Makefile and in the rule listed first, then dune complains that the static lib is missing – apparently foreign_archives wants the static library.
i can’t seem to convince dune that only a dynamic library should be produced and then installed.