Raw ocamlopt commands to create a cmxs file

I was trying to demonstrate cmxs and native dynamic loading to someone but I cannot come up with the raw commands needed to make a simple example, and I can’t find anyone who has written them down either. NB I do not want to know how to do this in dune or another build system, just the raw ocaml* commands.

Here’s what I have so far:

main.ml:

open Printf

let f () =
  printf "this is Main.f ()\n%!"

let () =
  printf "this is Main toplevel\n%!";
  Dynlink.loadfile "lib.cmxs"

lib.ml:

open Printf

let () =
  printf "this is Lib toplevel\n%!";
  Main.f ()

Commands:

ocamlopt -linkall -shared dynlink.cmxa main.ml -o main
ocamlopt -shared lib.ml -o lib.cmxs

Running ./main core dumps!

The -shared flag is only for generating .cmxs files, so your first link command should not include it. If you fix that, I believe that your commands should work.

However, there is a big issue in your code itself: it’s forbidden to refer to a module that has not been initialised completely. With normal linking this is checked by the compiler, but with Dynlink it’s more complicated, and here your Lib module refers to Main, but it’s loaded before Main is fully initialised.
The usual setup for small dynlink programs is to have three files:

  • a common library, which would contain f in your example, and is compiled normally (in your example you only need a single file, so you don’t even need to bother with a .cmxa, just producing the .cmx will work)
  • a plugin file that depends on the library and is compiled with -shared
  • a main file that does the dynlinking (and depends on the library). It’s not strictly necessary to link it with -linkall, but it’s safer as it ensures that modules from the linked libraries (typically the standard library) are present even if the main module doesn’t need them, in case the plugins do need them.

I hope this helps.

I forgot to add the typical commands for the three file setup:

ocamlopt -linkall dynlink.cmxa common.ml main.ml -o main
ocamlopt -shared lib.ml -o lib.cmxs

Where common.ml contains the functions that lib.ml needs to call.

That would explain my next question about what Dynlink.Uninitialized_global "Main" means.

Is it possible at all to call functions in the main program from a library which is loaded at runtime? That’s actually what I’m trying to demonstrate here.

To answer my own question, yes it is. We just have to split up main into two parts:

main.ml:

open Printf

let f () =
  printf "this is Main.f ()\n%!"

loader.ml

open Printf

let () =
  printf "this is Loader toplevel\n%!";
  Dynlink.loadfile "lib.cmxs"

(lib.ml as above)

$ ./main 
this is Loader toplevel
this is Lib toplevel
this is Main.f ()