3

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 ?

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • 1
    What is your `jvm` object? A structure? A class? How do you fill it in? In general, you can't cache values such the JVM `env` value. Nor can you cache objects or method ids unless you create a [global reference](http://stackoverflow.com/questions/112603/what-is-jni-global-reference). – Andrew Henle May 27 '16 at 22:04
  • It's a `struct jvm { JNIEnv * env; jobject this; jclass thisClass; methodID getId; };`, the field are filled after the loading of the library, after a java call to a native init function. – Nicolas Scotto Di Perto May 27 '16 at 22:10
  • First off, which character set and encoding are you using for the C strings, the system default or a specific one? – Tom Blodget May 28 '16 at 16:18

1 Answers1

1

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)

Community
  • 1
  • 1
Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • Thank you I didn't know about that. I've modified the code so now the structure contains only a pointer to the JVM and a global reference to the object. Each time I call the JNI from "a pure C context", I call `AttachCurrentThread` before. I still get an error: `JNIEnv *env; jint rs = (*jvm.jvm)->AttachCurrentThread(jvm.jvm, &env, NULL); assert(rs == JNI_OK); rs = (*jvm.jvm)->DetachCurrentThread(jvm.jvm); assert (rs == JNI_OK); `, the assert is triggered by the call to `DetachCurrentThread`... – Nicolas Scotto Di Perto May 28 '16 at 06:16
  • What do you mean by "a pure C context"? Per the [Java API documentation](http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html): "A native thread attached to the VM must call DetachCurrentThread() to detach itself before exiting. A thread cannot detach itself if there are Java methods on the call stack." You may not need to call `DetachCurrentThread()`. Print out the value of `env` in your initial JNI function that kicks off the loading of your plugin, and then again where you call `DetachCurrentThread()`. It might be the same because it's the same thread. – Andrew Henle May 28 '16 at 12:00