Skip to content

JNI 异常处理

源:JNI Exception Handling

JNI 异常处理机制与 Java 异常不同。Native 代码中不支持 try-catch,异常必须手动检查和处理。理解 JNI 异常处理对于编写健壮的 Native 代码至关重要。

JNI 异常机制

异常不会自动传播

在 Java 中,异常会自动向上传播:

java
// Java 代码
void method1() {
    method2();  // 如果抛出异常,会自动传播
}

void method2() {
    throw new Exception("Error");  // 自动传播到 method1
}

但在 JNI 中,异常不会自动传播

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionDemo_nativeMethod")
fun nativeMethod(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    // 调用可能抛出异常的 Java 方法
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "dangerousMethod", "()V")
    
    // 调用方法,可能抛出异常
    jniEnv.CallVoidMethod!!(env, thiz, methodId)
    
    // ❌ 异常不会自动传播!
    // 如果 dangerousMethod 抛出异常,程序会继续执行
    // 但 JNI 函数可能会失败或返回 null
    
    println("This line will still execute even if exception occurred!")
}

检查异常

ExceptionOccurred

检查是否有未处理的异常:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionCheck_checkException")
fun checkException(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    // 调用可能抛出异常的方法
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "riskyMethod", "()I")
    val result = jniEnv.CallIntMethod!!(env, thiz, methodId)
    
    // 检查是否有异常
    val exception: jthrowable? = jniEnv.ExceptionOccurred!!(env)
    
    if (exception != null) {
        println("Exception occurred!")
        
        // 必须清除异常才能继续使用 JNI 函数
        jniEnv.ExceptionClear!!(env)
        
        // 处理异常...
    } else {
        println("No exception, result: $result")
    }
}

ExceptionCheck

更轻量的异常检查(推荐):

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionCheck_quickCheck")
fun quickCheck(env: CPointer<JNIEnvVar>, thiz: jobject): jint {
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "calculate", "()I")
    val result = jniEnv.CallIntMethod!!(env, thiz, methodId)
    
    // ✅ ExceptionCheck 更快,不会创建局部引用
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        jniEnv.ExceptionClear!!(env)
        return -1  // 错误码
    }
    
    return result
}

TIP

ExceptionCheckExceptionOccurred 更快,因为它只返回 boolean,不创建异常对象的局部引用。

清除异常

ExceptionClear

必须清除异常后才能继续调用 JNI 函数:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ClearException_process")
fun process(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "step1", "()V")
    
    // 调用可能抛出异常的方法
    jniEnv.CallVoidMethod!!(env, thiz, methodId)
    
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        // ✅ 必须清除异常
        jniEnv.ExceptionClear!!(env)
        
        println("Exception cleared, continuing...")
        
        // 现在可以安全调用其他 JNI 函数
        val method2Id = jniEnv.GetMethodID!!(env, clazz, "step2", "()V")
        jniEnv.CallVoidMethod!!(env, thiz, method2Id)
    }
}

WARNING

在清除异常前调用大多数 JNI 函数会导致未定义行为。只有少数函数(如 ExceptionOccurredExceptionDescribeExceptionClear)可以在有异常时调用。

抛出异常

Throw - 抛出现有异常对象

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ThrowException_throwExisting")
fun throwExisting(env: CPointer<JNIEnvVar>, thiz: jobject, exception: jthrowable) {
    val jniEnv = env.pointed.pointed!!
    
    // 抛出传入的异常对象
    jniEnv.Throw!!(env, exception)
    
    // 函数仍会继续执行!
    // 异常会在返回 Java 后抛出
}

ThrowNew - 创建并抛出新异常

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ThrowException_throwNew")
fun throwNew(env: CPointer<JNIEnvVar>, thiz: jobject, value: jint): jint {
    val jniEnv = env.pointed.pointed!!
    
    if (value < 0) {
        // 查找异常类
        val exceptionClass = jniEnv.FindClass!!(
            env, 
            "java/lang/IllegalArgumentException"
        )
        
        // ✅ 抛出新异常
        jniEnv.ThrowNew!!(env, exceptionClass, "Value must be non-negative".cstr.ptr)
        
        // 返回错误值(虽然会被异常覆盖)
        return -1
    }
    
    return value * 2
}

常用异常类

kotlin
@OptIn(ExperimentalForeignApi::class)
object JNIExceptions {
    // 抛出 NullPointerException
    fun throwNPE(env: CPointer<JNIEnvVar>, message: String) {
        val jniEnv = env.pointed.pointed!!
        val clazz = jniEnv.FindClass!!(env, "java/lang/NullPointerException")
        jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
    }
    
    // 抛出 IllegalArgumentException
    fun throwIllegalArgument(env: CPointer<JNIEnvVar>, message: String) {
        val jniEnv = env.pointed.pointed!!
        val clazz = jniEnv.FindClass!!(env, "java/lang/IllegalArgumentException")
        jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
    }
    
    // 抛出 IllegalStateException
    fun throwIllegalState(env: CPointer<JNIEnvVar>, message: String) {
        val jniEnv = env.pointed.pointed!!
        val clazz = jniEnv.FindClass!!(env, "java/lang/IllegalStateException")
        jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
    }
    
    // 抛出 RuntimeException
    fun throwRuntime(env: CPointer<JNIEnvVar>, message: String) {
        val jniEnv = env.pointed.pointed!!
        val clazz = jniEnv.FindClass!!(env, "java/lang/RuntimeException")
        jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
    }
    
    // 抛出 IOException
    fun throwIOException(env: CPointer<JNIEnvVar>, message: String) {
        val jniEnv = env.pointed.pointed!!
        val clazz = jniEnv.FindClass!!(env, "java/io/IOException")
        jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
    }
}

获取异常信息

读取异常消息

kotlin
@OptIn(ExperimentalForeignApi::class)
fun getExceptionMessage(env: CPointer<JNIEnvVar>): String? {
    val jniEnv = env.pointed.pointed!!
    
    // 获取异常对象
    val exception = jniEnv.ExceptionOccurred!!(env) ?: return null
    
    // 清除异常(必须在使用 JNI 函数前)
    jniEnv.ExceptionClear!!(env)
    
    // 获取 Throwable 类
    val throwableClass = jniEnv.FindClass!!(env, "java/lang/Throwable")
    
    // 调用 getMessage() 方法
    val getMessageMethod = jniEnv.GetMethodID!!(
        env, 
        throwableClass, 
        "getMessage", 
        "()Ljava/lang/String;"
    )
    
    val messageObj = jniEnv.CallObjectMethod!!(env, exception, getMessageMethod)
    val message = (messageObj as? jstring)?.toKString(env)
    
    // 清理
    jniEnv.DeleteLocalRef!!(env, exception)
    jniEnv.DeleteLocalRef!!(env, messageObj)
    
    return message
}

打印异常堆栈

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionUtil_printStackTrace")
fun printStackTrace(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    // 调用可能抛出异常的方法
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "dangerousOp", "()V")
    jniEnv.CallVoidMethod!!(env, thiz, methodId)
    
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        // ✅ 打印异常堆栈到标准错误输出
        jniEnv.ExceptionDescribe!!(env)
        
        // 清除异常
        jniEnv.ExceptionClear!!(env)
    }
}

异常处理模式

模式1:传播异常到 Java

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Pattern1_propagate")
fun propagate(env: CPointer<JNIEnvVar>, thiz: jobject, input: jstring): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // 调用 Java 方法
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(
        env, clazz, "process", "(Ljava/lang/String;)Ljava/lang/String;"
    )
    
    val result = jniEnv.CallObjectMethod!!(env, thiz, methodId, input) as? jstring
    
    // ✅ 直接返回,异常会传播到 Java
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        return null  // 或返回默认值
    }
    
    return result
}

模式2:捕获并转换异常

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Pattern2_convert")
fun convert(env: CPointer<JNIEnvVar>, thiz: jobject, value: jint): jint {
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "calculate", "(I)I")
    val result = jniEnv.CallIntMethod!!(env, thiz, methodId, value)
    
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        // 获取原始异常信息
        val message = getExceptionMessage(env)
        
        // 清除原异常
        jniEnv.ExceptionClear!!(env)
        
        // ✅ 抛出新的异常类型
        val newExClass = jniEnv.FindClass!!(env, "java/lang/RuntimeException")
        jniEnv.ThrowNew!!(
            env, 
            newExClass, 
            "Calculation failed: $message".cstr.ptr
        )
        
        return -1
    }
    
    return result
}

模式3:吞掉异常(谨慎使用)

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Pattern3_swallow")
fun swallow(env: CPointer<JNIEnvVar>, thiz: jobject): jboolean {
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "tryOperation", "()V")
    
    jniEnv.CallVoidMethod!!(env, thiz, methodId)
    
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        // 记录日志
        println("Operation failed, ignoring...")
        
        // ✅ 清除异常,不向 Java 传播
        jniEnv.ExceptionClear!!(env)
        
        return JNI_FALSE  // 返回失败标志
    }
    
    return JNI_TRUE  // 成功
}

最佳实践

实践1:总是检查异常

kotlin
@OptIn(ExperimentalForeignApi::class)
// ✅ 好的模式
@CName("Java_com_example_BestPractice_safeCall")
fun safeCall(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    // 查找类
    val clazz = jniEnv.FindClass!!(env, "com/example/MyClass")
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        println("Class not found")
        return
    }
    
    // 查找方法
    val methodId = jniEnv.GetMethodID!!(env, clazz, "myMethod", "()V")
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        println("Method not found")
        return
    }
    
    // 调用方法
    jniEnv.CallVoidMethod!!(env, thiz, methodId)
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        println("Method call failed")
        return
    }
}

实践2:使用辅助函数

kotlin
@OptIn(ExperimentalForeignApi::class)
// 辅助函数:安全调用 JNI 方法
inline fun <T> safeJNICall(
    env: CPointer<JNIEnvVar>,
    onError: () -> T,
    block: () -> T
): T {
    val jniEnv = env.pointed.pointed!!
    val result = block()
    
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        jniEnv.ExceptionClear!!(env)
        return onError()
    }
    
    return result
}

// 使用示例
@CName("Java_com_example_Helper_useHelper")
fun useHelper(env: CPointer<JNIEnvVar>, thiz: jobject): jint {
    val jniEnv = env.pointed.pointed!!
    
    return safeJNICall(env, onError = { -1 }) {
        val clazz = jniEnv.GetObjectClass!!(env, thiz)
        val methodId = jniEnv.GetMethodID!!(env, clazz, "getValue", "()I")
        jniEnv.CallIntMethod!!(env, thiz, methodId)
    }
}

实践3:RAII 风格异常管理

kotlin
@OptIn(ExperimentalForeignApi::class)
class JNIExceptionScope(private val env: CPointer<JNIEnvVar>) {
    private val jniEnv = env.pointed.pointed!!
    private var hasException = false
    
    fun checkAndClear(): Boolean {
        if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
            hasException = true
            jniEnv.ExceptionClear!!(env)
            return true
        }
        return false
    }
    
    fun hadException(): Boolean = hasException
}

@CName("Java_com_example_RAII_process")
fun process(env: CPointer<JNIEnvVar>, thiz: jobject): jint {
    val scope = JNIExceptionScope(env)
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    if (scope.checkAndClear()) return -1
    
    val methodId = jniEnv.GetMethodID!!(env, clazz, "step1", "()I")
    if (scope.checkAndClear()) return -1
    
    val result = jniEnv.CallIntMethod!!(env, thiz, methodId)
    if (scope.checkAndClear()) return -1
    
    return result
}

常见错误

错误1:忘记检查异常

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
@CName("Java_com_example_Bad_noCheck")
fun noCheck(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.FindClass!!(env, "NonExistentClass")
    // clazz 可能为 null,且有异常pending
    
    // 继续使用会崩溃或产生未定义行为
    val methodId = jniEnv.GetMethodID!!(env, clazz, "method", "()V")
}

错误2:异常未清除就调用 JNI 函数

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
@CName("Java_com_example_Bad_noClean")
fun noClear(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.GetObjectClass!!(env, thiz)
    val methodId = jniEnv.GetMethodID!!(env, clazz, "bad", "()V")
    jniEnv.CallVoidMethod!!(env, thiz, methodId)
    
    // 假设抛出异常
    if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
        // ❌ 未清除异常就调用其他 JNI 函数
        val method2 = jniEnv.GetMethodID!!(env, clazz, "another", "()V")
        // 未定义行为!
    }
}

错误3:抛出异常后继续执行关键代码

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
@CName("Java_com_example_Bad_continueAfterThrow")
fun continueAfterThrow(env: CPointer<JNIEnvVar>, thiz: jobject, ptr: Long) {
    val jniEnv = env.pointed.pointed!!
    
    if (ptr == 0L) {
        JNIExceptions.throwNPE(env, "Null pointer")
        
        // ❌ 异常抛出后函数仍会继续执行!
        // 不应该继续访问无效指针
        val data = ptr.toCPointer<ByteVar>()
        data!![0] = 0  // 崩溃!
    }
}

// ✅ 正确
@CName("Java_com_example_Good_returnAfterThrow")
fun returnAfterThrow(env: CPointer<JNIEnvVar>, thiz: jobject, ptr: Long) {
    val jniEnv = env.pointed.pointed!!
    
    if (ptr == 0L) {
        JNIExceptions.throwNPE(env, "Null pointer")
        return  // ✅ 立即返回
    }
    
    val data = ptr.toCPointer<ByteVar>()
    data!![0] = 0  // 安全
}

正确的异常处理是编写健壮 JNI 代码的关键。始终检查异常、及时清除、合理传播或转换异常,能够避免大部分运行时问题。