Java 使用有效指针从 C 调用方法后出现 JNI NullPointerException

Java JNI NullPointerException after calling method from C with valid pointers

TLDR:当我从 C 调用 Java 方法时遇到了 NPE,但没有明显的跳出原因。

我在 Linux 上编写了一些 JNI 代码来为我调用系统调用 poll(从连接的设备获取通知)。

大部分都运行良好;我的 C 方法被调用,我从 Java 获取一个字符串,返回一个指针(作为 int... 我知道,所以起诉我!),并成功地将其传递给其他方法.我有很多 printf 来验证这一点。

问题出现在我下面的 C 方法 Java_NativePoller_poll 中。 CallVoidMethod 似乎 行,因为 之后的 printf 被称为 ,但是 Java 我尝试调用的方法永远不会被调用,然后抛出 NullPointerException

这是 Java 代码:

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

有问题的 C 代码:

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

(抱歉,代码不是很干净——我还有一些重构工作要做​​。)

调用它的代码如下:

watchFd = nativePoller.watchFile(ROOT_PATH + pinPath + "value", this::triggerEvent);

//...

private void triggerEvent() {
    LOG.info("Event triggered!");
}

我的所有 printf 输出如下所示:

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)

这个 NPE 是从哪里来的?

谢谢!

您正在跨方法调用重复使用 jobject handlerjclass objclass。这是非法的,除非引用是全局的。

详情见