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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/230689/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:48:22  来源:igfitidea点击:

Best way to throw exceptions in JNI code?

javaexception-handlingjava-native-interface

提问by Chris R

I'd like a consistent and simple way to throw exceptions in JNI code; something that handles chained exceptions (implicitly from the env->ExceptionOccurred method, or explicitly by parameters, either way is good) and saves me looking up constructors every time I want to do this. All of the above is preferably in C, although I could translate it from C++ at need.

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

Does anyone on SO have something like this that they can share?

SO上的任何人都可以分享这样的东西吗?

采纳答案by Steven M. Cherry

We just code utility methods for each of the types of exceptions we want to throw. Here are some examples:

我们只是为我们想要抛出的每种类型的异常编写实用方法。这里有些例子:

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

That way, it's easy to find them, your code-completion editor will help you to type them in, and you can pass simple parameters.

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

I'm sure you could expand this to handle chained exceptions, or other more complicated approaches. This was enough to meet our needs.

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

回答by Java42

I simply use 2 lines:

我只是使用 2 行:

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

Produces:

产生:

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

回答by android.weasel

My code starts in Java, invokes C++, which then invokes Java back again for things like finding, getting, and setting field values.

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

In case someone looking for a C++ approach finds this page, I'll plough on with this:

如果有人寻找 C++ 方法找到了这个页面,我会继续研究这个:

What I'm now doing is wrapping my JNI method bodies up with a C++ try/catch block,

我现在正在做的是用 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 */
    }
}

where PendingException is declared trivially:

其中 PendingException 被简单地声明:

class PendingException {};

and I'm invoking the following method after invoking any JNI from C++, so if the Java exception status indicates an error, I'll bail immediately and let the normal Java exception handling add the (Native method) line to the stack trace, while giving the C++ the opportunity to clean up while unwinding:

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

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

My Java stack trace looks like this for a failed env->GetFieldId() call:

对于失败的 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(JniClass.java:151)
  at com.pany.jni.JniClass.onClick(JniClass.java:129)
  at android.view.View.performClick(View.java:4084)

and pretty similar if I call up to a Java method that throws:

如果我调用抛出的 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)

I can't talk to wrapping the Java exception within another Java exception from within C++, which I think is part of your question - I've not found the need to do that - but if I did, I'd either do it with a Java-level wrapper around the native methods, or just extend my exception-throwing methods to take a jthrowable and replace the env->ThrowNew() call with something ugly: it's unfortunate Sun didn't provide a version of ThrowNew that took a jthrowable.

我无法谈论将 Java 异常包装在 C++ 中的另一个 Java 异常中,我认为这是您问题的一部分 - 我没有发现需要这样做 - 但如果我这样做了,我要么用围绕本机方法的 Java 级包装器,或者只是扩展我的异常抛出方法以获取 jthrowable 并用丑陋的东西替换 env->ThrowNew() 调用:不幸的是 Sun 没有提供一个版本的 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);
}

I wouldn't consider caching (exception) class constructor references because exceptions aren't supposed to be a usual control flow mechanism, so it shouldn't matter if they're slow. I imagine look-up isn't terribly slow anyway, since Java presumably does its own caching for that sort of thing.

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

回答by Canato

I will put a more complete and general answer for who need a little bit more explanations like I need before.

对于需要像我以前需要的更多解释的人,我将提供更完整和更一般的答案。

First is nice to set your method with a Throw Exceptionso the IDE will ask for try/catch.

首先用 a 设置你的方法很好,Throw Exception这样 IDE 就会要求 try/catch。

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

I decide for IOExceptionover Exceptionbecause of this.

因为这个,我决定IOException结束。Exception

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