Replacing part of a running ocaml program using the Dynlink library

I am trying to replace (a small part) of my program while it is running using the Dynlink module. I have tried to replicate this example:
http://zderadicka.eu/plugins-in-ocaml-with-dynlink-library/

In my code, I have a plugface library, that is available to both main and the plugin. So I set the refs from the plugin, and can then read it using get_plugin () in my main exec.

let p = ref None
let get_plugin () : int Seq.t  =
  match !p with 
  | Some s -> s
  | None -> failwith "No plugin loaded"

plugin.ml

open Plugface
let () = 
  let _ = print_endline "version 3.21" in
  p := Some (Seq.cycle (List.to_seq [200]))

This works the first time like I would expect :slight_smile: .
However, when I edit my plugin.ml and then recompile it and try to load the “plugin.cmxs” for a second time in main.app. I don’t get an error, but also nothing really changes, it seems that it just running the old code again, instead of the new. Is this expected? Is it even possible what I try to do?

1 Like

If you give your plugin the same filename each time, you should use Dynlink.loadfile_private instead of Dynlink.loadfile. Otherwise, the dynamic linker will think the module you tried to load is already loaded and error out (you should get an exception Module_already_loaded).

Thank you, unfortunately, this does not seem to be the problem. I am using Dynlink.loadfile_private to load the new binary code. However, I can see from including a log message in my plugin.ml, that the second time I change the code and recompile it, when I do Dynlink.loadfile_private for the second time, it is not really loading my updated plugin code, instead it is executing the original version of the plugin again as if it was cached.

Under the hood, Dynlink is calling the dlopen system call (on Unix-like systems), which returns the previously loaded object of the same name, if it exists. You need to give your plugin a new file name every time to make sure it is loaded in your process memory. (For Coq, we are randomly generating the name of any module we need to load multiple times in a single session.)

2 Likes

Thanks, yes, giving it a unique name did at least make it work.
Having a random library name does not play very nicely with Dune, but I think I am just going to compile the plugin using ocamlopt to simplify this whole setup a bit.

Drive-by comment: it might be tricky to have ocamlopt resolve the “include” paths without hard-coding them.

Thanks for the “drive by” advice: in the end Dune was the only way I could get it to work.
One of the problems is that since I use a module (Plugface) to communicate between Plugin and Main app, I ran into problems where that module was loaded twice when I compiled it with ocamlopt.

Unfortunately, I now got so far as to find out that this Dynlink dynamic route is not as fast as I hoped, and is probably not suitable for the problem I tried to solve. My program is for generating musical patterns, and the idea was that I would write little musical patterns as dynamic plugins and swap them out using Dynlink. The benefit would be that I could write these small pattern plugins in normal Ocaml, since I actually really like writing this kind of stuff in normal Ocaml, I just don’t want to stop and start the whole program when I make a small change. :smiley:

However, the audio & midi link, which is implemented through Jack Audio client, suffers from dropouts when I load the plugin with Dynlink.loadfile_private, so I think I will have to find some other way.