jnim
jnim is a library that wraps jni api and can call Java from nim. Qiita also has several articles about jnim.
Looking at the original site, I wrote only a sample of System.out.println
, and even if I googled how to use a library like that registered in the Maven repository, I can not find an article like that. It was.
This time, it will be an article that explains the sample source for calling Java classes contained in external Jar files from Nim.
import jnim
# Import a couple of classes
jclass java.io.PrintStream of JVMObject:
proc println(s: string)
jclass java.lang.System of JVMObject:
proc `out`: PrintStream {.prop, final, `static`.}
# Initialize JVM
initJNI()
# Call!
System.`out`.println("This string is printed with System.out.println!")
This sample source is saved on GitHub. https://github.com/6in/jnim-sample
As for the contents of the sample source, refer to the UUID library published in the Maven repository and call the Java class that generates the UUID from Nim to generate the UUID.
https://mvnrepository.com/artifact/com.fasterxml.uuid/java-uuid-generator
Please set JAVA_HOME because jnim will check the JAVA_HOME environment variable and get the location of the JVM.
File folder | Explanation |
---|---|
sample.nimble | nimble file |
sample.nim | Main source |
uuid_classes.nim | Java Class definition for jnim |
gradlew | gradle wrapper for *nix |
gradlew.bat | gradle wapper for windows |
build.gradle | gradle definition file |
jars/ | The Jar file you want to use will be copied here |
Use Gradle (Wrapper) to get the Jar from Maven Central and store the Jar file including the dependencies in one folder.
Add the library you want to use to the dependencies of build.gradle.
build.gradle
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Groovy library project to get you started.
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.7.1/userguide/building_java_projects.html
*/
plugins {
id 'java-library'
}
repositories {
// Use JCenter for resolving dependencies.
jcenter()
}
//Add the library you want to use
dependencies {
compile group: 'com.fasterxml.uuid', name: 'java-uuid-generator', version: '4.0.1'
}
//Copy Jar defined in dependencies and related Jar to jars
task collectJars(type: Copy) {
into "jars"
from configurations.runtime
}
When you execute the defined custom task (collectJars), a Jars folder is created and contains two Jar files.
> nimble collectJars
> dir jars
jnim-sample\jars
12/13/2020 02:46 PM <DIR> .
12/13/2020 02:46 PM <DIR> ..
12/13/2020 02:46 PM 37,457 java-uuid-generator-4.0.1.jar
12/13/2020 02:46 PM 41,424 slf4j-api-1.7.29.jar
2 File(s) 78,881 bytes
2 Dir(s) 252,181,385,216 bytes free
Define the class and method you want to use as follows.
For static methods, define {.static.}
.
uuid_classes.nim
import jnim
#Definition of Java class to be used(Must be defined one by one)
jclass java.util.UUID * of JVMObject:
proc toString*(): string
jclass com.fasterxml.uuid.impl.RandomBasedGenerator * of JVMObject:
proc generate*(): UUID
jclass com.fasterxml.uuid.impl.NameBasedGenerator * of JVMObject:
proc generate*(name: string): UUID
jclass com.fasterxml.uuid.Generators * of JVMObject:
proc randomBasedGenerator*(): RandomBasedGenerator {.`static`.}
proc nameBasedGenerator*(): NameBasedGenerator {.`static`.}
In Java, when calling a class in another Jar, it is necessary to set the path to the Jar file in the environment variable CLASSPATH
or set the Java startup option CLASSPATH
, but jnim Since it does not refer to the environment variable CLASSPATH
, it must be passed as an option to the argument of the JVM initialization function initJNI
.
Specifically, specify the path to Jar in the format -Djava.class.path =/path/to/jar
.
In the sample below, get the Jar
file stored in the jars
folder, assemble the string concatenated with the file separator, and call initJNI
.
The generated UUID is output by calling the static method of the Generators class defined in uuid_classes.nim
.
sample.nim
import os
import jnim
import strutils
import ./uuid_classes
# jars/Enumerate the jar files contained in
var paths: seq[string] = @[]
for f in walkDir("jars"):
paths.add f.path
when defined(windows):
const sep = ";"
else:
const sep = ":"
# Initialize JVM with JVMOption
initJNI(JNIVersion.v1_8, @["-Djava.class.path=" & paths.join(sep)])
# call method
var randomBased = Generators.randomBasedGenerator().generate().toString()
var nameBased = Generators.nameBasedGenerator().generate("name").toString()
echo fmt"{ randomBased = }"
echo fmt"{ nameBased = }"
The SLF4J log is displayed, but it was confirmed that both ramdomBased
and nameBased
were acquired.
> nimble run
Verifying dependencies for [email protected]
Info: Dependency on jnim@>= 0.5.1 already satisfied
Verifying dependencies for [email protected]
Building sample/sample.exe using c backend
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
randomBased = 7a95887d-b10b-4008-9588-7db10bc008e2
nameBased = 6ae99955-2a0d-5dca-94d6-2e2bc8b764d3
When calling a Java method from a Nim thread, I had to call initJNIThread
at the beginning of the threading process to attach it to the already initialized JVM.
#Call from thread
proc threadFunc(param: tuple[a, b: int]) {.thread, gcsafe.} =
#This call is absolutely necessary(Attach to a VM that has already been initialized)
initJNIThread()
defer:
#Call at the end
deinitJNIThread()
var randomBased = ""
var nameBased = ""
for x in param.a..param.b:
randomBased = Generators.randomBasedGenerator().generate().toString()
nameBased = Generators.nameBasedGenerator().generate("name").toString()
echo fmt"{ randomBased = }"
echo fmt"{ nameBased = }"
block:
defer:
echo "end threads"
var thr: array[0..1, Thread[tuple[a, b: int]]]
echo "start threads"
thr[0].createThread(threadFunc, (1, 1000))
thr[1].createThread(threadFunc, (1, 1000))
sleep(1000)
echo "wait threads"
joinThreads(thr)
For build, specify the threads switch and threadAnalysis switch.
reference: I translated the Nim manual into Japanese https://qiita.com/tauplus/items/80afbd47f3a44158ea1f
nim c --threads:on --threadAnalysis:off -r sample
Output result
start threads
randomBased = c0466bea-6769-49c1-866b-ea6769e9c170
nameBased = 6ae99955-2a0d-5dca-94d6-2e2bc8b764d3
randomBased = 9032c0ad-1afc-4bfe-b2c0-ad1afc4bfe47
nameBased = 6ae99955-2a0d-5dca-94d6-2e2bc8b764d3
wait threads
end threads
I created a sample that uses an existing Java library using jnim. As a feature,
java.class.path
)It is troublesome to write the definition of the Java class you want to use one by one, so I thought it would be nice to have something like c2nim that automatically creates a definition file from the Java class. ..
Also, even if there is a library you want to use, not all functions are required, so it is good to create a Gradle library project, define a simple class that wraps the library, and call it from Nim. I feel like that. (By the way, it would be better to make it Fat Jar)
But well, if you want to use Java classes up to that point, you can create Java/Groovy/Kotlin/Scala/Clojure.
Since there is a jexport macro that can create a Java class implementation in Nim, it seems that it can also handle callbacks from Java.
type
MyObjData = ref object
a: int
MyObj = ref object of JVMObject
data: MyObjData
jexport MyObjSub extends MyObj:
proc new = super()
proc stringMethod(): string =
"Nim"
Recommended Posts