pluginsocamlocamlbuildlablgtk

How to create a GTK plugin (cmxs) for my OCaml program


I'd like to make a GTK plugin for my OCaml application, loaded using Dynlink. How can I get ocamlbuild to include the lablgtk2 library in the generated plugin?

As a test, I have main.ml:

let () =
  try
    Dynlink.loadfile "_build/gtk_plugin.cmxs"
  with Dynlink.Error err ->
    failwith (Dynlink.error_message err)

gtk_plugin.ml:

let () =
  print_endline "GTK plugin loaded!";
  GMain.Main.main ()

_tags:

<main.*>: package(dynlink)
<gtk_plugin.*>: package(lablgtk2)

But I get:

$ ocamlbuild -use-ocamlfind main.native gtk_plugin.cmxs
$ ./main.native 
Fatal error: exception Failure("error loading shared library:
.../_build/gtk_plugin.cmxs: undefined symbol: camlGtkMain")

Note: the main binary must not depend on libgtk (which might not be installed on the target system) - if the plugin fails to load I want to fall back to console mode.


Solution

  • You need to

    1. add the linkall flag to main, otherwise it will remove parts of the OCaml runtime that will later be needed by dynamic plugins

    2. compile the gtk_plugin.cmxs file with option -lflag lablgtk.cma (which I deduced from seeing in the _log that this option was not passed)

    The way ocamlbuild deduces .cmxs dependencies is not optimal right now, and it's hard because different users may want different things (minimal plugins assuming libs are present, or on the contrary portable statically linked stuff). For modules coming from your project you can write a foo.mldylib file to be explicit about what you want excluded, but I don't know whether it's possible to include "all modules of this external library".

    Note that it is also possible to distribute lablgtk.cmxs and the relevant .cmi along with your plugin, and load it dynamically first.

    mkdir lablgtk
    cp `ocamlfind query lablgtk2`/lablgtk.cmxs lablgtk
    cp `ocamlfind query lablgtk2`/*.cmi lablgtk
    echo "\"lablgtk\": not_hygienic" >> _tags
    

    then in your main.ml

    let () =
      try
        Dynlink.loadfile "lablgtk/lablgtk.cmxs";
        Dynlink.loadfile "_build/gtk_plugin.cmxs"
      ...