I really wanted to use Nim's pegs (PEGs) from Java, so I looked into how to call Nim functions via JNI.
Astonishing facts turned out ...
With jnim, it seems that you can create a shared library with just Nim files. After tomorrow, I will organize the procedure a little more.
jnim_call.nim
import jnim
import dynlib
#Return the argument as it is
proc Java_HelloJNI_hello*(env: JNIEnvPtr, me:jobject, arg:jstring ): jstring {.cdecl,exportc,dynlib.} =
return arg
Compile & run
$ nim c --app:lib --noMain -o:libHelloJNI.so jnim_call.nim
$ java -Djava.library.path=. HelloJNI "Can you call me"
Can you call me
~~ So, the following is a garbage article. ~~
In the above Nim source, when I converted the JNI method argument to jstring-> cstring-> string, for some reason the JVM dumped a core dump and terminated abnormally.
In the initial version, it didn't work unless I called NimMain first and initialized it, so it seems that this is the cause.
I thought I should call NimMain by myself, and when I wrote the following using the emit pragma, I was able to call it successfully without spitting a core dump! !!
jnim_call.nim
import jnim
import dynlib
#Return the argument as it is
proc Java_HelloJNI_hello*(env: JNIEnvPtr, me:jobject, arg:jstring ): jstring {.cdecl,exportc,dynlib.} =
#Initialize Nim
{.emit: """
NimMain();
""".}
#Argument String->cstring conversion
var argNim:cstring = env.GetStringUTFChars(env,arg,nil)
#Open at the end
defer:
env.ReleaseStringUTFChars(env,arg,argNim)
# cstring ->string conversion
var ss = "Hello " & $argNim
#Generate a string and exit
return env.NewStringUTF(env,ss)
Now you can call peg from Java.
※※※※ Addition even more ※※※※
When I called the JNI method about 10,000 times in a loop to see if I had to call NimMain every time, It terminated abnormally with the error "cannot register global variable; too many global variables". It seems that NimMain needs to be called only the first time, so I decided to use the global variables defined in jnim. With this support, even if you call the JNI method by Nim 100,000 times, no error will occur! !! !!
jnim_call.nim
import jnim
import dynlib
#Return the argument as it is
proc Java_HelloJNI_hello*(env: JNIEnvPtr, me:jobject, arg:jstring ): jstring {.cdecl,exportc,dynlib.} =
#Initialize Nim
if theEnv == nil :
{.emit: """
NimMain();
""".}
theEnv = env
#Argument String->cstring conversion
var argNim:cstring = env.GetStringUTFChars(env,arg,nil)
#Open at the end
defer:
env.ReleaseStringUTFChars(env,arg,argNim)
# cstring ->string conversion
var ss = "Hello " & $argNim
#Generate a string and exit
return env.NewStringUTF(env,ss)
In Nim's Forum, there was a post that a Segmentation fault occurred in a native call with jnim. The same phenomenon occurred in my article and the native method I created. As a solution, I was able to work around the problem by passing -d: noSignalHandler as a compiler option.
Describes the compile options for creating a native SO file (DLL) with jnim.
nim c --out:libHelloJNI.so \
--app:lib \
--nomain \
--threads:on \
-d:release \
-d:noSignalHandler \
hello.nim
Java->Nim I read @ shsnow23's this article and felt that I could call Java-> Nim, so I made a sample.
Considering the free memory after returning the character string from Nim, I thought it would be safer to use Java object creation in the JNI method, so I implemented it in JNI.
HelloJNI.java
//JNI test class
public class HelloJNI {
//Loading the library
static {
System.loadLibrary("HelloJNI");
}
//JNI method
private native String hello(String str);
//Main function for testing
public static void main(String[] args){
HelloJNI h = new HelloJNI();
System.out.println(h.hello(args[0]));
}
}
proc hello_nim(s:cstring) : cstring {.exportc.} =
#Concatenate strings and return
var ss:string = ""
ss = "hello " & $s
return ss
The header is automatically generated by javah, so you don't need to modify it.
HelloJNI.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: hello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloJNI_hello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
C (JNI) source. Convert the passed arguments to native types before calling the nim function. If you don't call a function called NimMain, Nim will not be initialized and a runtime error will occur. For how to implement JNI, I referred to here.
HelloJNI.c
#include "HelloJNI.h"
//Function definition on the Nim side
#include "nimcache/hello.h"
JNIEXPORT jstring JNICALL Java_HelloJNI_hello
(JNIEnv * env, jobject pThis, jstring arg)
{
//Call required
NimMain();
//Char argument*Conversion to
NCSTRING argNim = (*env)->GetStringUTFChars(env,arg,NULL);
//Call a Nim function
NCSTRING ret = hello_nim(argNim);
//Release the reserved memory
(*env)->ReleaseStringUTFChars(env,arg,argNim);
//Constructs a string from Nim's return value and returns
return (*env)->NewStringUTF(env,ret);
}
I can't make a makefile because I'm so weak ... NIM_HOME is the directory where you installed Nim.
build.sh
sharedlib=libHelloJNI.so
#Delete the previous product for the time being
rm -rf nimcache
rm ${sharedlib}
#Nim compilation & C source output The source is output to nimcache
nim c --noMain --noLinking --header:hello.h Hello.nim
#Nimcache with JNI C source/*.c also compile
gcc -shared -fPIC \
-I ${JAVA_HOME}/include \
-I ${JAVA_HOME}/include/linux \
-I ${NIM_HOME}/lib \
-o ${sharedlib} \
HelloJNI.c nimcache/*.c
#Java is also compiled
javac HelloJNI.java
#Test run
java -Djava.library.path=. HelloJNI
It will be in such a state.
.
├── compile.sh
├── HelloJNI.java
├── HelloJNI.class
├── HelloJNI.h
├── HelloJNI.c
├── Hello.nim
├── libHelloJNI.so
└── nimcache
├── Hello.c
├── Hello.o
├── hello.h
├── stdlib_system.c
└── stdlib_system.o
Iyahoooooooooooo
$ java -Djava.library.path=. HelloJNI "Nim Nim Nim"
hello Nim
Recommended Posts