Search code examples
javacjava-native-interface

JNI convert a String to char *, returned by an instance method


in my program C is called from Java, C functions can be called outside of a Java context but sometimes need some Java ressources. I should explain what the program is... So the C library is a plugin system which can load C plugins, plugins sometimes need to access to Java ressources, for example to retrieve a String id.

So when the plugin system is loaded, an init function is called by the JNI so the JNIEnv * and jobject can be stored for further access by the plugins.

In the java class I have provided instance method to access to those ressources, for example I have method

private String getId() {
  return "Bryan";
}

The C plugin system have a function

char *get_id(char *id) {
  jobject jobj = (*jvm.env)->CallObjectMethod(jvm.env, jvm.this, jvm.getId);
  jstring jid = jobj;
  if (jid == NULL) 
    debug("get_id", RED "jid NULL");
   else 
     debug("get_id", RED "jid not null"); */
  debug("get_id", RED "in get_id, method called");
  const char *cid = (*jvm.env)->GetStringUTFChars(jvm.env, jid, NULL);
  debug("get_id", RED "converted to c string: %s", cid);
  strcpy(id, cid);
  debug("get_id", RED "string copied");
  (*jvm.env)->ReleaseStringUTFChars(jvm.env, jid, cid);
  debug("get_id", RED "string released");
  return id;
}

Where jvm is a structure containing the field env and obj corresponding to the JNIEnv * and jobject stored at initialization, and jvm.getId which is the methodID of the getId Java instance method inizialized at the same time. The debug macro is just a call to printf with a flush which help me to debug the program.

And that is the output after a call to get_id:

DEBUG IN plugin_system.c LINE 339:
In get_id
in get_id, calling method...
DEBUG IN plugin_system.c LINE 343:
In get_id
jid NULL
DEBUG IN plugin_system.c LINE 346:
In get_id
in get_id, method called
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007fb6bcb6cd70, pid=25254, tid=0x00007fb6974be700
#
# JRE version: OpenJDK Runtime Environment (8.0_92-b14) (build 1.8.0_92-b14)
# Java VM: OpenJDK 64-Bit Server VM (25.92-b14 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# V  [libjvm.so+0x675d70]
#
# Core dump written. Default location: /home/kowa/code/reseaux/projet/ringo/java/bin/core or core.25254
#
# An error report file with more information is saved as:
# /home/kowa/code/reseaux/projet/ringo/java/bin/hs_err_pid25254.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
[4]    25254 abort (core dumped)  java Jring Nick 9999 8888 1

As you can see the call to Java getId is (looks) successfull, but a core dump is triggered by GetStringUTFChars.

What's wrong ?


Solution

  • You can not cache the JVM's env. See Keeping a global reference to the JNIEnv environment, especially this answer:

    The JNI interface pointer (JNIEnv) is valid only in the current thread. Should another thread need to access the Java VM, it must first call AttachCurrentThread() to attach itself to the VM and obtain a JNI interface pointer. Once attached to the VM, a native thread works just like an ordinary Java thread running inside a native method. The native thread remains attached to the VM until it calls DetachCurrentThread() to detach itself.

    Updated link. The link in the other answer is stale.

    As far as the Java object, class, and method id, you need to obtain a global reference to those if you're going to cache them. See What is 'JNI Global reference' and Caching JNI objects and thread-safety (in Android)