My Tortured Relationship With libnotes
Sat May 22 12:31:10 EDT 2021
A tremendous amount of my work lately involves wrangling the core Notes library, variously "libnotes.dylib", "libnotes.so", or (for some reason) "nnotes.dll" (edit: see the comments!). I do almost all of my daily work in Liberty servers on the Mac loading this library, my first major use of the Domino Docker image was to use it as an overgrown carrier for this native piece, and in general I spend a lot of time putting this to use.
It ain't easy, though! Unlike a lot of native libraries that you can just kind of load from wherever, libnotes is extremely picky about its loading environment. There are a few main ways that this manifests.
Required-Library References
libnotes isn't standalone, and it doesn't just rely on standard OS stuff like libc. It also relies on other Notes-specific libraries like libxmlproc and libgsk8iccs, and some of those refer back to each other. This all shakes out in normal practice, since they're all next to the Notes/Domino executable and each other, but it makes things finicky when you're running from outside there.
This seems to be the most finicky on macOS, where the references to each library are marked with @executable_path
relative paths, meaning relative to the running executable.
I've wrangled with this quite a bit over the years, but the upshot is that, on macOS and Linux, you really want to set some environment variables before you ever load your program. Naturally, since your running program can't do anything about what happened before it was loaded, this means you have to balance the teacups in your environment beforehand.
Non-Library Files
Beyond the dynamic libraries, libnotes also need some program-support executable files, things like string resource files and whatnot. And, very importantly, it needs an active data directory with an ID, notes.ini, and names.nsf at least. The data-directory contents bits are at least a little more forgiving: though there's still a hard requirement that they be present on the filesystem (since libnotes loads them by string path, not as configurable binary streams), you could at least bundle them or copy them around. For example, for my Dockerized app runners, I tend to have some basic versions of those in the repo that get copied into the Docker container's notesdata directory during build.
Working Around It
As I mentioned, the main way I go about dealing with this is by telling whatever is running my program to use applicable environment variables, ideally in a reproducible config-file-based way. This works perfectly with Docker and with tycho-surefire-plugin in Maven, but doesn't work so well for maven-surefire-plugin (the normal unit test runner) or Eclipse's JUnit tools. In Eclipse's case, I can at least fill in the environment variables in the Run Configuration. It hampers me a bit (I can't just right-click a test case and run it individually without first running the whole suite or setting up a configuration specially), but it works.
I gave a shot recently to copying the Mac dylibs and programmatically fiddling with otool
and install_name_tool
to adjust their dependency paths, and I got somewhere with that, but that somewhere was an in-libnotes fatal panic, so I'm clearly missing something. And besides, even if I got that working, it'd be a bit of a drag.
What Would Be Nice
What would be really nice would be a variant of this that's just more portable - something where I can just System.load
a library, call NotesInitExtended
to point to my INI and ID, and be good to go. I'm not really sure what this would entail, especially since I'd also want libinotes, libjnotes, and liblsxbe. I do know that I don't have the tools to do it, which fortunately frees me up to idly speculate about things I'd like to have delivered to me.
As long as I'm wishing for stuff, I'll say what would be even cooler would be a WebAssembly library usable with Wasmer, which is a multi-language WebAssembly runtime. The promise there is that you'd have a single compiled library that would run on any OS on any processor that supports WebAssembly, from now until forever. I'm not sure that this would actually be doable at the moment - for one, I don't know if callback parameters work, which would be fairly critical. Still, my birthday is coming up, and that would be a nice present.
Ben Langhinrichs - Sat May 22 15:19:30 EDT 2021
I just wanted to comment briefly on why it is "nnotes.dll". One of the fairly clever things that Lotus did, though IBM messed it up some later, was to use the prefix to determine the OS. Each library would have the same core name, but when loaded by Notes, it would find the correct file based on that prefix. _ was for 16 bit Windows, n for 32 bit Windows, L for OS/2 (I think - it was a long time ago). All the addins could use this, so for example our Midas LSX is loaded using
UseLSX "lsxrtc"
but that means on 16-bit Windows , it resolved to "_lsxrtc.dll", on Windows 32-but to "nlsxrtc.dll", and on Linux to "liblsxrtc.so" and so forth.
It was a great system, but IBM didn't understand and had 64-bit Windows share the n and 64-bit Linux be indistinguishable from their 32-bit prefixes. That is why Exciton Boost loads as ExcitonBoost32 or ExcitonBoost64. If IBM had followed through with the scheme, they could all use ExcitonBoost in the extmgr_addins= line as intended by Lotus. Oh well.