Kotlin Multiplatform native executable using dlopen+dlsym

I have a native launcher written in C for my Java application. I am trying to rewrite the launcher in Kotlin Native. I found this nice example code that links to libjvm so that you can call the JNI invocation API from Kotlin. However, the generated native executable then ends up with a libjvm dependency baked in, which my current native launcher does not have; rather, it has various heuristics to discover and select the best available libjvm, and then load it via dlopen and access it dynamically via dlsym.

I was not able to find a Kotlin Multiplatform example that uses dlopen+dlsym in this way, to avoid baking in a shared library dependency into the compiled binary. Could someone point me in the right direction? I know Java well but I’m rather new to Kotlin, and pretty bad at C. :sweat_smile:

My first attempt can be found here: GitHub - ctrueden/java-launcher: [EXPERIMENTAL] Native executable to launch Java in the same process

I decided to make the Kotlin/C interop interface much simpler by building a tiny shared library in C that uses dlopen/dlsym to work with libjvm, exposing only a single function launch_java(char *libjvm_path, int jvm_argc, char *jvm_argv[], char *main_class_name, int main_argc, char *main_argv[]), then wrap that C library to KMP using C interop. What I have so far can be found here. It almost works on my Linux system, but I’m seeing errors at link time:

./libjaunch.so: error: undefined reference to 'dlerror', version 'GLIBC_2.34'
./libjaunch.so: error: undefined reference to 'dlclose', version 'GLIBC_2.34'
./libjaunch.so: error: undefined reference to 'dlopen', version 'GLIBC_2.34'
./libjaunch.so: error: undefined reference to 'dlsym', version 'GLIBC_2.34'

I know that the libdl stuff (dlopen et al.) moved intoi glibc core as of 2.34, so I don’t understand why these errors are happening. But this is maybe becoming more of a C question than a Kotlin one I suppose. Still, if anyone has any insight, I would appreciate any thoughts and ideas.

I gave up on making Kotlin Native use libdl functions in their intended fashion. I switched instead to the following design:

  1. A small (~300 line) C program with functions:

    1. launch_jvm(libjvm_path, jvm_argc, jvm_argv, main_class_name, main_argc, main_argv)
    2. run_command(command, input, numInput, &output, &numOutput)
    3. main(argc, argv), which invokes run_command on a separate configurator program that reads the argv on its stdin, and emits the arguments to pass to launch_jvm on its stdout, and then indeed calls launch_jvm with those arguments specified.
  2. A configurator program written using Kotlin Native, with no dependencies, and no custom use of cInterop—only built-in platform.posix, platform.windows, etc. This configurator can do most of the launcher’s heavy lifting, including discovery of installed JVMs, reasoning about which flags passed should be passed to the JVM vs the main method, implementation of any custom arguments that shouldn’t be passed directly to the main method, and anything else that needs to happen before launching the JVM (e.g. moving updated JAR files into place).

In this way, I can replicate the design of the project’s previous 5000-line C-only program, using Kotlin for most of it, and suffering the pain of C as minimally as possible.

If anyone has a better design, or knows of an existing project meeting these requirements in a better way, I would be very interested. Otherwise, I believe I can move forward well enough for now.

For anyone curious, the repo is at:

P.S. The reason for the undefined reference errors in the second design iteration above was discovered by @elect: it is due to Kotlin Native using a different, older version of glibc compared to the system one. A workaround is to call the Konan compiler directly, something like:

~/.konan/kotlin-native-prebuilt-linux-x86_64-1.9.20/bin/run_konan clang clang linux_x64 @/path/to/jaunch/args

But TBH I did not try this solution myself…

1 Like