What happens when I throw a C++ exception from a native Java method?

前端 未结 5 1692
时光取名叫无心
时光取名叫无心 2020-12-08 07:22

Suppose I\'m embedding Sun\'s JVM in a C++ application. Through JNI I call a Java method (my own), which in turns calls a native method I implemented in a shared library.

5条回答
  •  盖世英雄少女心
    2020-12-08 08:04

    The Java compiler doesn't understand C++ exceptions, so you'll have to work with both Java and C++ exceptions. Luckily, that's not overly complicated. First we have a C++ exception that tells us if a Java exception has occurred.

    #include 
    //This is how we represent a Java exception already in progress
    struct ThrownJavaException : std::runtime_error {
        ThrownJavaException() :std::runtime_error("") {}
        ThrownJavaException(const std::string& msg ) :std::runtime_error(msg) {}
    };
    

    and a function to throw an C++ exception if a Java exception is already in place:

    inline void assert_no_exception(JNIEnv * env) {
        if (env->ExceptionCheck()==JNI_TRUE) 
            throw ThrownJavaException("assert_no_exception");
    }
    

    we also have a C++ exception for throwing new Java exceptions:

    //used to throw a new Java exception. use full paths like:
    //"java/lang/NoSuchFieldException"
    //"java/lang/NullPointerException"
    //"java/security/InvalidParameterException"
    struct NewJavaException : public ThrownJavaException{
        NewJavaException(JNIEnv * env, const char* type="", const char* message="")
            :ThrownJavaException(type+std::string(" ")+message)
        {
            jclass newExcCls = env->FindClass(type);
            if (newExcCls != NULL)
                env->ThrowNew(newExcCls, message);
            //if it is null, a NoClassDefFoundError was already thrown
        }
    };
    

    We also need a function to swallow C++ exceptions and replace them with Java exceptions

    void swallow_cpp_exception_and_throw_java(JNIEnv * env) {
        try {
            throw;
        } catch(const ThrownJavaException&) {
            //already reported to Java, ignore
        } catch(const std::bad_alloc& rhs) {
            //translate OOM C++ exception to a Java exception
            NewJavaException(env, "java/lang/OutOfMemoryError", rhs.what()); 
        } catch(const std::ios_base::failure& rhs) { //sample translation
            //translate IO C++ exception to a Java exception
            NewJavaException(env, "java/io/IOException", rhs.what()); 
    
        //TRANSLATE ANY OTHER C++ EXCEPTIONS TO JAVA EXCEPTIONS HERE
    
        } catch(const std::exception& e) {
            //translate unknown C++ exception to a Java exception
            NewJavaException(env, "java/lang/Error", e.what());
        } catch(...) {
            //translate unknown C++ exception to a Java exception
            NewJavaException(env, "java/lang/Error", "Unknown exception type");
        }
    }
    

    With the above functions, it's easy to use Java/C++ hybrid exceptions in your C++ code, as shown below.

    extern "C" JNIEXPORT 
    void JNICALL Java_MyClass_MyFunc(JNIEnv * env, jclass jc_, jstring param)
    {
        try { //do not let C++ exceptions outside of this function
    
            //YOUR CODE BELOW THIS LINE.  HERES SOME RANDOM CODE
            if (param == NULL) //if something is wrong, throw a java exception
                 throw NewJavaException(env, "java/lang/NullPointerException", "param");            
            do_stuff(param); //might throw java or C++ exceptions
            assert_no_exception(env); //throw a C++ exception if theres a java exception
            do_more_stuff(param); //might throw C++ exceptions
            //prefer Java exceptions where possible:
            if (condition1) throw NewJavaException(env, "java/lang/NullPointerException", "condition1");
            //but C++ exceptions should be fine too
            if (condition0) throw std::bad_alloc("BAD_ALLOC");
            //YOUR CODE ABOVE THIS LINE.  HERES SOME RANDOM CODE
    
        } catch(...) { //do not let C++ exceptions outside of this function
            swallow_cpp_exception_and_throw_java(env);
        } 
    
    }
    

    If you're really ambitious, it's possible to keep track of a StackTraceElement[] of your bigger functions, and get a partial stacktrace. The basic method is to give each function a StackTraceElement, and as they're called, push a pointer to them onto a thread-local "callstack" and when they return, pop the pointer off. Then, alter the constructor of NewJavaException to make a copy of that stack, and pass it to setStackTrace.

提交回复
热议问题