TLDR: I'm getting a NPE when I call a Java method from C and nothing obvious is jumping out as the cause.
I have written some JNI code on Linux to call the system call poll for me (to get notifications from an attached device).
Most of it is working pretty well; my C methods are getting called, I'm getting a string from Java, returning a pointer (as an int... I know, so sue me!), and successfully passing that to other methods. I've got a lot of printfs to verify this.
The problem is happening in my C method Java_NativePoller_poll below. The line that does CallVoidMethod seems to work, as the printf which follows is called, but the Java method I'm attempting to call never gets called, and a NullPointerException is then thrown.
Here's the Java code:
public class NativePoller {
public interface NativePollEventHandler {
void handleEvent();
}
/* Opens a file to prepare to poll its status */
public native int watchFile(String fileName, NativePollEventHandler handler);
/* Poll a file previously opened */
public synchronized native void poll(int fd);
/* Clean up */
public native void stopWatching(int fd);
}
And the C code in question:
struct Poller {
struct pollfd fd;
jobject handler;
jclass objclass;
jmethodID method;
};
int wasEx(JNIEnv* env) {
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex) {
printf("Got an exception!");
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
return 1;
}
return 0;
}
JNIEXPORT jint JNICALL Java_NativePoller_watchFile
(JNIEnv* env, jobject nativePoller, jstring fileName, jobject handler) {
(*env)->ExceptionClear(env);
const char* file = (*env)->GetStringUTFChars(env, fileName, NULL);
if (wasEx(env)) return -1;
int fd = open(file, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open %s for reading (errno=%d)\n", file, errno);
(*env)->ReleaseStringUTFChars(env, fileName, file);
if (wasEx(env)) return -1;
return fd;
}
(*env)->ReleaseStringUTFChars(env, fileName, file);
if (wasEx(env)) return -1;
struct Poller* poller = malloc(sizeof(struct Poller));
poller->fd.fd = fd;
poller->fd.events = POLLIN;
poller->handler = handler;
jclass objclass = (*env)->GetObjectClass(env, handler);
if (wasEx(env)) return -1;
jmethodID method = (*env)->GetMethodID(env, objclass, "handleEvent", "()V");
if (wasEx(env)) return -1;
printf("Found method %p in class %p\n", method, objclass);
poller->objclass = objclass;
poller->method = method;
printf("Returning poller %p which has descriptor %d\n", poller, poller->fd.fd);
return (int)poller;
}
JNIEXPORT void JNICALL Java_NativePoller_poll
(JNIEnv* env, jobject nativePoller, jint pollerAddress) {
struct Poller* poller = (struct Poller*)pollerAddress;
printf("Polling for %p (%d)\n", poller, poller->fd.fd);
int ret = poll(&poller->fd, 1, 1);
if (ret > 0) {
printf("Got something! Events is %08X", poller->fd.events);
if (poller->fd.events & POLLIN) {
if (poller->method != 0) {
printf("Calling: CallVoidMethod(%p, %p, %p)...\n", env, poller->handler, poller->method);
(*env)->ExceptionClear(env);
(*env)->CallVoidMethod(env, poller->handler, poller->method);
if (wasEx(env)) return;
printf("Called.\n");
}
}
}
}
JNIEXPORT void JNICALL Java_NativePoller_stopWatching
(JNIEnv* env, jobject nativePoller, jint fileDescriptor) {
struct Poller* poller = (struct Poller*)fileDescriptor;
close(poller->fd.fd);
free(poller);
}
(Sorry the code isn't super clean -- I still have a bit of refactoring to do.)
Here's the code that's calling it:
watchFd = nativePoller.watchFile(ROOT_PATH + pinPath + "value", this::triggerEvent);
//...
private void triggerEvent() {
LOG.info("Event triggered!");
}
All my printf output looks like this:
Found method 0x63d03b48 in class 0x63d02860
Returning poller 0x63d036a8 which has descriptor 18
Polling for 0x63d036a8 (18)
Got something! Events is 00000001
Calling: CallVoidMethod(0x63d03d3c, 0x630d0aa8, 0x63d03b48)...
Got an exception!
Exception in thread "Thread-12" java.lang.NullPointerException
at my.package.NativePoller.poll(Native Method)
at my.package.GPIOPinImpl.run(GPIOPinImpl.java:116)
at java.lang.Thread.run(Thread.java:745)
Where's this NPE coming from?
Thanks!
You are reusing jobject handler and jclass objclass across method calls. This is illegal unless the references are made global.
See this answer for details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With