在 JNI 代码中抛出异常的最佳方法?

Best way to throw exceptions in JNI code?

提问人:Chris R 提问时间:10/24/2008 更新时间:12/29/2018 访问量:45100

问:

我想要一种一致且简单的方法来在 JNI 代码中抛出异常;处理链式异常的东西(隐式地来自 env->ExceptionOccurred 方法,或显式地通过参数,无论哪种方式都很好),并且每次我想这样做时都可以节省我查找构造函数的时间。以上所有内容最好是用 C 语言,尽管我可以根据需要从 C++ 翻译它。

SO 上有人有这样的东西可以分享吗?

异常 java-native-interface

评论

1赞 android.weasel 8/31/2012
通过“处理链式异常”,您的代码会在从 Java 返回到 C++ 时注意到捕获 Java 级异常,将其包装在其他异常中,并将该新异常从 C++ 扔回 Java?

答:

57赞 Steven M. Cherry 10/28/2008 #1

我们只是为要抛出的每种类型的异常编写实用程序方法。以下是一些示例:

jint throwNoClassDefError( JNIEnv *env, char *message )
{
    jclass exClass;
    char *className = "java/lang/NoClassDefFoundError";

    exClass = (*env)->FindClass( env, className);
    if (exClass == NULL) {
        return throwNoClassDefError( env, className );
    }

    return (*env)->ThrowNew( env, exClass, message );
}

jint throwNoSuchMethodError(
        JNIEnv *env, char *className, char *methodName, char *signature )
{

    jclass exClass;
    char *exClassName = "java/lang/NoSuchMethodError" ;
    LPTSTR msgBuf;
    jint retCode;
    size_t nMallocSize;

    exClass = (*env)->FindClass( env, exClassName );
    if ( exClass == NULL ) {
        return throwNoClassDefError( env, exClassName );
    }

    nMallocSize = strlen(className) 
            + strlen(methodName)
            + strlen(signature) + 8;

    msgBuf = malloc( nMallocSize );
    if ( msgBuf == NULL ) {
        return throwOutOfMemoryError
                ( env, "throwNoSuchMethodError: allocating msgBuf" );
    }
    memset( msgBuf, 0, nMallocSize );

    strcpy( msgBuf, className );
    strcat( msgBuf, "." );
    strcat( msgBuf, methodName );
    strcat( msgBuf, "." );
    strcat( msgBuf, signature );

    retCode = (*env)->ThrowNew( env, exClass, msgBuf );
    free ( msgBuf );
    return retCode;
}

jint throwNoSuchFieldError( JNIEnv *env, char *message )
{
    jclass exClass;
    char *className = "java/lang/NoSuchFieldError" ;

    exClass = (*env)->FindClass( env, className );
    if ( exClass == NULL ) {
        return throwNoClassDefError( env, className );
    }

    return (*env)->ThrowNew( env, exClass, message );
}

jint throwOutOfMemoryError( JNIEnv *env, char *message )
{
    jclass exClass;
    char *className = "java/lang/OutOfMemoryError" ;

    exClass = (*env)->FindClass( env, className );
    if ( exClass == NULL ) {
        return throwNoClassDefError( env, className );
    }

    return (*env)->ThrowNew( env, exClass, message );
}

这样一来,就很容易找到它们,你的代码完成编辑器将帮助你输入它们,并且你可以传递简单的参数。

我相信您可以扩展它以处理链式异常或其他更复杂的方法。这足以满足我们的需求。

评论

34赞 Tim Sylvester 9/19/2010
刚刚找到这个,谢谢。但是,错误条件不会导致无限递归和不可避免的堆栈溢出吗?我承认,这真的不应该发生,但这似乎不是处理它的适当方式。如果这不起作用,也许可以依靠 和 或 其他东西。throwNoClassDefErrorjava.lang.errorabort()
0赞 Stevens Miller 2/14/2012
是的,我也看到了。同意。我无法让我的 ThrowNew() 调用执行任何操作,即使它们返回 NULL(即成功)。没有什么是容易的......
2赞 amigo 12/14/2012
对不起,但谁能解释一下为什么在找不到“java/lang/NoClassDefFoundError”类的情况下,throwNoClassDefError 函数不会落入无限递归?
1赞 steinybot 5/7/2015
我很好奇你如何处理这些方法的结果值?如果您尝试抛出异常并失败,那么我们该怎么办?
1赞 Kurovsky 6/30/2015
我认为 throwNoClassDefError() 函数不是必需的,因为如果找不到类,FindClass() 函数会抛出 NoClassDefFoundError 本身。
30赞 Java42 3/22/2012 #2

我只用了 2 行:

 sprintf(exBuffer, "NE%4.4X: Caller can %s %s print", marker, "log", "or");
 (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/Exception"), exBuffer);

生产:

 Exception in thread "main" java.lang.Exception: NE0042: Caller can log or print.

评论

5赞 android.weasel 8/31/2012
我被告知捕获java.lang.Exception被认为是不良做法:我在我想要一般JNI失败案例的地方抛出com.mycompany.JniException。
33赞 deltamind106 9/9/2015
@android.weasel:伙计,这是 StackOverflow 上的示例代码,用于说明 ThrowNew API。它不打算成为任务关键型服务器中的生产代码。让这家伙休息一下......
0赞 Sam Ginrich 1/7/2022
从洞察力来看,人们所需要的正是接受不是由复杂的例外概念的自我目的驱动的:)
7赞 android.weasel 8/31/2012 #3

我的代码从 Java 开始,调用 C++,然后再次调用 Java 进行查找、获取和设置字段值等操作。

如果有人寻找C++方法找到此页面,我将继续研究:

我现在正在做的是用C++ try/catch块包装我的JNI方法体,

JNIEXPORT void JNICALL Java_com_pany_jni_JNIClass_something(JNIEnv* env, jobject self)
{
    try
    {
        ... do JNI stuff
        // return something; if not void.
    }
    catch (PendingException e) // (Should be &e perhaps?)
    {
        /* any necessary clean-up */
    }
}

其中 PendingException 是简单声明的:

class PendingException {};

在从 C++ 调用任何 JNI 后,我将调用以下方法,因此,如果 Java 异常状态指示错误,我将立即保释并让正常的 Java 异常处理将(本机方法)行添加到堆栈跟踪中,同时让 C++ 有机会在展开时进行清理:

PendingException PENDING_JNI_EXCEPTION;
void throwIfPendingException(JNIEnv* env)
{
    if (env->ExceptionCheck()) {
        throw PENDING_JNI_EXCEPTION;
    }
}

对于失败的 env->GetFieldId() 调用,我的 Java 堆栈跟踪如下所示:

java.lang.NoSuchFieldError: no field with name='opaque' signature='J' in class Lcom/pany/jni/JniClass;
  at com.pany.jni.JniClass.construct(Native Method)
  at com.pany.jni.JniClass.doThing(JniClass.java:169)
  at com.pany.jni.JniClass.access$1(JniClass.java:151)
  at com.pany.jni.JniClass$2.onClick(JniClass.java:129)
  at android.view.View.performClick(View.java:4084)

如果我调用一个 Java 方法,它会抛出:

 java.lang.RuntimeException: YouSuck
  at com.pany.jni.JniClass.fail(JniClass.java:35)
  at com.pany.jni.JniClass.getVersion(Native Method)
  at com.pany.jni.JniClass.doThing(JniClass.java:172)

我无法从 C++ 中将 Java 异常包装在另一个 Java 异常中,我认为这是您问题的一部分 - 我没有发现需要这样做 - 但如果我这样做了,我要么使用围绕本机方法的 Java 级包装器来做到这一点,要么只是扩展我的异常抛出方法以采用 jthrowable 并将 env->ThrowNew() 调用替换为丑陋的东西: 不幸的是,Sun 没有提供一个 jthrowable 的 ThrowNew 版本。

void impendNewJniException(JNIEnv* env, const char *classNameNotSignature, const char *message)
{
    jclass jClass = env->FindClass(classNameNotSignature);
    throwIfPendingException(env);
    env->ThrowNew(jClass, message);
}

void throwNewJniException(JNIEnv* env, const char* classNameNotSignature, const char* message)
{
    impendNewJniException(env, classNameNotSignature, message);
    throwIfPendingException(env);
}

我不会考虑缓存(异常)类构造函数引用,因为异常不应该是通常的控制流机制,所以它们是否慢也没关系。我想无论如何查找都不会很慢,因为 Java 可能会为这种事情做自己的缓存。

3赞 Canato 12/29/2018 #4

对于需要更多解释的人,我会给出一个更完整和笼统的答案,就像我以前需要的那样。

首先,最好使用 a 设置方法,以便 IDE 会要求 try/catch。Throw Exception

public native int func(Param1, Param2, Param3) throws IOException;

因此,我决定结束。IOExceptionException

JNIEXPORT int JNICALL Java_YourClass_func
(int Param1, int Param2, int Param3) {
    if (Param3 == 0) { //something wrong
        jclass Exception = env->FindClass("java/lang/Exception");
        env->ThrowNew(Exception, "Can't divide by zero."); // Error Message
    }
    return (Param1+Param2)/Param3;
}