2010-11-09 10 views
19

Supponiamo che sto incorporando JVM di Sun in un'applicazione C++. Attraverso JNI chiamo un metodo Java (il mio), che a sua volta chiama un metodo nativo che ho implementato in una libreria condivisa.Cosa succede quando lancio un'eccezione C++ da un metodo Java nativo?

Cosa succede se questo metodo nativo genera un'eccezione C++?

modifica: il compilatore è gcc 3.4.x, jvm è il sole di 1.6.20.

+1

Se lo si lascia propagare, probabilmente si ottiene una risposta "panica" della VM. Perché non provarlo? –

+0

Quale piattaforma? Potrebbe essere specifico del compilatore ... –

+0

@ Michael: Pensavo che "Java" fosse una piattaforma? – sbi

risposta

5

All'interno della letteratura JNI, la parola eccezione sembra essere utilizzata esclusivamente per fare riferimento alle eccezioni Java. Gli eventi imprevisti nel codice nativo sono indicati come errori di programmazione. JNI esplicitamente non richiede che le JVM controllino gli errori di programmazione. Se si verifica un errore di programmazione, il comportamento non è definito. Differenti JVM possono comportarsi diversamente.

È responsabilità del codice nativo tradurre tutti gli errori di programmazione in codici di ritorno o eccezioni Java. Le eccezioni Java non vengono lanciate immediatamente dal codice nativo. Possono essere in attesa di, emessi solo una volta che il codice nativo ritorna al chiamante Java. Il codice nativo può verificare le eccezioni in sospeso con ExceptionOccurred e cancellarle con ExceptionClear.

3

Immagino che la tua JVM si bloccherà. Le eccezioni native C++ non si propagano in Java attraverso JNI. Uno dei motivi è che JNI è un'interfaccia C e C non conosce le eccezioni C++.

Quello che devi fare è catturare le eccezioni C++ prima di entrare nel livello C del tuo codice JNI, e rendere la funzione JNI C restituire un codice di errore. Quindi è possibile verificare il codice di errore in Java e lanciare un'eccezione Java, se necessario.

+4

Puoi lanciare un'eccezione java nel codice nativo usando la funzione ThrowNew. Non è necessario aggiungere codici di errore quando esiste una soluzione in stile java. – josefx

+0

Hai ragione. Non sapevo di ThrowNew. Ma prima devi ancora cogliere l'eccezione C++. – Dima

3

L'etichettare come comportamento non definito. La propagazione delle eccezioni al codice C (che è ciò che sta eseguendo la JVM) è un comportamento indefinito.

Su Windows, i compilatori devono utilizzare Structured Exception Handling di Microsoft per implementare le eccezioni, quindi le eccezioni C++ saranno "sicure" trattate con il codice C. Tuttavia,, il codice C è non scritto con eccezioni in mente, in modo da ottenere un arresto anomalo se si è fortunati, e perdite di stato e risorse incoerenti, se non si è.

Su altre piattaforme, beh, non lo so, ma non può essere più carina. Quando scrivo il codice JNI, avvolgo ogni funzione C++ in un blocco try: anche se non lo faccio throw, potrei comunque ottenere alcune delle eccezioni standard (std::bad_alloc mi viene in mente, ma anche altre sono possibili).

2

JNI utilizza funzioni c per interfacciarsi con codice nativo. C non può gestire le eccezioni correttamente poiché non è consapevole della loro esistenza. Quindi devi prendere le eccezioni nel tuo codice nativo e convertirle in eccezioni java o il tuo jvm si bloccherà. (Funziona poiché l'eccezione java viene lanciata solo quando il codice nativo ritorna a java)

23

Il compilatore Java non comprende le eccezioni C++, quindi dovrai lavorare con entrambe le eccezioni Java e C++. Fortunatamente, non è eccessivamente complicato. Per prima cosa abbiamo un'eccezione C++ che ci dice se si è verificata un'eccezione Java.

#include <stdexcept> 
//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) {} 
}; 

e una funzione per generare un'eccezione C++ se un'eccezione Java è già in atto:

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

abbiamo anche un'eccezione C++ per lanciare nuove eccezioni Java:

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

Abbiamo anche bisogno di una funzione per invertire le eccezioni C++ e sostituirle con le eccezioni Java

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

Con le funzioni di cui sopra, è facile utilizzare le eccezioni ibride Java/C++ nel codice C++, come mostrato di seguito.

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

} 

Se siete veramente ambiziosi, è possibile tenere traccia di un StackTraceElement[] delle vostre funzioni più grandi, e ottenere uno stacktrace parziale. Il metodo di base è di assegnare a ciascuna funzione uno StackTraceElement e, come vengono chiamati, spingere un puntatore su di essi su un "callstack" locale del thread e quando ritornano, spegne il puntatore. Quindi, modificare il costruttore di NewJavaException per creare una copia di tale stack e passarlo a setStackTrace.

Problemi correlati