Try calling Nim from Java via JNI

Overview

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.

JNI, not JNA

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.

procedure

Java side

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]));
    }
}

Nim side

proc hello_nim(s:cstring) : cstring  {.exportc.} = 
    #Concatenate strings and return
    var ss:string = ""
    ss = "hello " & $s
    return ss

C side

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);
}

Build script

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

File state after compilation

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

Then, the execution result

Iyahoooooooooooo

$ java -Djava.library.path=. HelloJNI "Nim Nim Nim"
hello Nim

Recommended Posts

Try calling Nim from Java via JNI
Try calling synchronized methods from multiple threads in Java
Try calling JavaScript in Java
Call Java library from C with JNI
Calling java from C ++ on Android NDK
Try calling the CORBA service in Java 11+
Try running a Kubernetes Job from Java
Call Java methods from Nim using jnim
Try calling Watson NLU that seems to support Japanese from the Java SDK
Use native libraries from Scala via Java CPP + Java
Try Java 8 Stream
Try accessing the dataset from Java using JZOS
Roughly try Java 9
I tried calling Java / Objective-C native code from Flutter
Pessimistic lock on NW ABAP from Java via JCo
Changes from Java 8 to Java 11
Sum from Java_1 to 100
Eval Java source from Java
Access API.AI from Java
From Java to Ruby !!
Try Java return value