Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native libraries loading support #218

Closed
ileasile opened this issue May 2, 2021 · 4 comments
Closed

Add native libraries loading support #218

ileasile opened this issue May 2, 2021 · 4 comments
Labels

Comments

@ileasile
Copy link
Collaborator

ileasile commented May 2, 2021

During the investigation of #214, it turned out that the only way to use a JNI library in notebook is to call System.load() or System.loadLibrary() inside the library itself. In some cases (see RDKit) library authors don't do it and don't provide any methods for native library loading.

In "general" project setup all dependencies (user code and libraries) are loaded with one URL classloader, so the user may call System.load() in their code, and it works. In kernel, library dependencies are loaded with URL classloader, and snippets loaded with another org.jetbrains.kotlin.scripting.compiler.plugin.impl.CompiledScriptClassLoader. So, loaded JNI wrapper just can't see the native definitions which were loaded by System.load() call in the snippet code.

I propose the following solution for this problem:

  1. Define a new file annotation @file:NativeLibrary(path: String)
  2. On this annotation, compile a single class with the following code:
object Loader_$id {
   fun load() = System.load("$path")
}
  1. Load this class with the same URL classloader with which all other dependencies are loaded. To do it, just add the path to the compiled class to addedClasspath list in org.jetbrains.kotlinx.jupyter.dependencies.JupyterScriptDependenciesResolverImpl#resolveFromAnnotations.
  2. Before cell execution but after the dependencies were loaded execute snippet Loader_$id.load()

After all, it allows to write the code like this:

@file:DependsOn("<jni-wrapper.jar>")
@file:NativeLibrary("<native-library.dll>")
// some code that uses methods from <jni-wrapper.jar>

Current workaround for this problem is to make up a wrapper for the native library with the wrapper inside it, see the warpper for RDKit here

@altavir
Copy link
Contributor

altavir commented May 2, 2021

The native library does not make a lot of sense without its wrapper. The annotation that only hot-fixes the problem, but does not add anything new seems to be a rather weak solution.
Since Jupyter is not constrained by the JDK version I wonder if we could consider using JEP 389.

@ileasile
Copy link
Collaborator Author

ileasile commented May 2, 2021

I came up to a hacky solution that solves this problem a bit easier. From the user perspective it looks like this:

@file:DependsOn(<jni-wrapper.jar>")
import some.wrapper.Clazz
loadLibrary(Clazz::class.java, "<path-to-native-library>")

// some code that uses methods from <jni-wrapper.jar>

All we need to do is to implement loadLibrary function. Here it is:

fun loadLibrary(javaClass: Class<*>, name: String, isAbsolute: Boolean = true) {
    val clazz = ClassLoader::class.java
    val method = clazz.getDeclaredMethod("loadLibrary", Class::class.java, java.lang.String::class.java, Boolean::class.java)
    method.isAccessible = true
    method.invoke(null, javaClass, name, isAbsolute)
}

ClassLoader.loadLibrary is a package-private static method. We need to invoke it to load a library not from "current" classloader. So, we simply do it using reflection.

@ileasile
Copy link
Collaborator Author

ileasile commented May 2, 2021

Let's discuss the aforementioned JEP in a separate issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants