2010-11-09 56 views
19

Giả sử tôi đang nhúng JVM của Sun vào ứng dụng C++. Thông qua JNI tôi gọi một phương thức Java (của riêng tôi), mà lần lượt gọi một phương thức bản địa mà tôi đã thực hiện trong một thư viện được chia sẻ.Điều gì sẽ xảy ra khi tôi ném ngoại lệ C++ từ một phương thức Java gốc?

Điều gì sẽ xảy ra nếu phương pháp gốc này ném ngoại lệ C++?

chỉnh sửa: trình biên dịch là gcc 3.4.x, jvm là 1.6.20 của mặt trời.

+1

Nếu bạn để nó phổ biến, bạn có thể nhận được phản hồi "hoảng loạn" VM. Tại sao không chỉ thử nó? –

+0

Nền tảng nào? Có thể là trình biên dịch cụ thể ... –

+0

@Michael: Tôi nghĩ "Java" là một nền tảng? – sbi

Trả lời

5

Trong tài liệu JNI, từ ngoại lệ dường như chỉ được sử dụng để chỉ các ngoại lệ Java. Sự kiện không mong muốn trong mã gốc được gọi là lỗi lập trình. JNI rõ ràng không yêu cầu JVM để kiểm tra lỗi lập trình. Nếu xảy ra lỗi lập trình, hành vi không xác định. Các JVM khác nhau có thể hoạt động khác nhau.

Trách nhiệm của mã gốc là dịch tất cả các lỗi lập trình thành mã trả lại hoặc ngoại lệ Java. Các ngoại lệ Java không bị ném ngay lập tức từ mã gốc. Chúng có thể là đang chờ xử lý, chỉ được ném khi mã gốc trả lại cho người gọi Java. Mã gốc có thể kiểm tra các ngoại lệ đang chờ xử lý với ExceptionOccurred và xóa chúng bằng ExceptionClear.

3

Tôi đoán JVM của bạn sẽ bị lỗi. Các ngoại lệ Native C++ không lan truyền vào Java thông qua JNI. Một lý do cho điều đó là JNI là một giao diện C, và C không biết gì về ngoại lệ C++.

Những gì bạn phải làm là nắm bắt ngoại lệ C++ trước khi bạn truy cập vào lớp C của mã JNI của bạn và làm cho hàm JNI C trả về mã lỗi. Sau đó, bạn có thể kiểm tra mã lỗi bên trong Java và ném một ngoại lệ Java nếu cần thiết.

+4

Bạn có thể ném một ngoại lệ java trong mã gốc bằng cách sử dụng chức năng ThrowNew. Không cần thêm mã lỗi khi giải pháp kiểu java tồn tại. – josefx

+0

Bạn nói đúng. Tôi không biết về ThrowNew. Nhưng bạn vẫn cần nắm bắt ngoại lệ C++ trước. – Dima

3

Tôi sẽ gắn nhãn đó là hành vi không xác định. Tuyên truyền các trường hợp ngoại lệ trở lại mã C (đó là những gì đang chạy JVM) là hành vi không xác định.

Trên Windows, trình biên dịch phải sử dụng Xử lý ngoại lệ có cấu trúc của Microsoft để triển khai ngoại lệ, vì vậy ngoại lệ C++ sẽ được "an toàn" được liên kết thông qua mã C. Tuy nhiên, mã C là không được viết với ngoại lệ trong tâm trí, vì vậy bạn sẽ gặp sự cố nếu bạn may mắn, và rò rỉ trạng thái và tài nguyên không phù hợp nếu bạn không có.

Trên các nền tảng khác, tốt, tôi không biết, nhưng nó không thể đẹp hơn. Khi tôi viết mã JNI, tôi bọc mọi hàm C++ trong khối try: ngay cả khi tôi không throw, tôi vẫn có thể nhận được một số ngoại lệ tiêu chuẩn (std::bad_alloc đến với tâm trí, nhưng những người khác cũng có thể).

2

JNI sử dụng các hàm c để giao tiếp với mã gốc. C không thể xử lý các ngoại lệ một cách chính xác vì nó không nhận thức được sự tồn tại của chúng. Vì vậy, bạn phải nắm bắt các ngoại lệ trong mã Native của bạn và chuyển đổi chúng sang ngoại lệ java hoặc jvm của bạn sẽ sụp đổ. (Điều này làm việc kể từ khi ngoại lệ java chỉ được ném khi mã gốc trả lại cho java)

23

Trình biên dịch Java không hiểu ngoại lệ C++, vì vậy bạn sẽ phải làm việc với cả hai ngoại lệ Java và C++. May mắn thay, điều đó không quá phức tạp. Đầu tiên chúng ta có một ngoại lệ C++ cho chúng ta biết nếu một ngoại lệ Java đã xảy ra.

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

và một chức năng để ném một ++ ngoại lệ C nếu một ngoại lệ của Java đã được tại chỗ:

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

chúng tôi cũng có một C++ ngoại lệ cho ném ngoại lệ Java mới:

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

Chúng ta cũng cần một hàm để nuốt các ngoại lệ C++ và thay thế chúng bằng ngoại lệ 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"); 
    } 
} 

Với các chức năng trên, thật dễ dàng để sử dụng các ngoại lệ lai Java/C++ trong mã C++ của bạn, như được hiển thị bên dưới.

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

} 

Nếu bạn thực sự đầy tham vọng, nó có thể theo dõi một StackTraceElement[] chức năng lớn hơn của bạn, và nhận được một stacktrace một phần. Phương pháp cơ bản là cung cấp cho mỗi hàm StackTraceElement và khi chúng được gọi, hãy đẩy con trỏ đến một hàm "callstack" cục bộ và khi chúng trở lại, hãy tắt con trỏ. Sau đó, thay đổi hàm tạo của NewJavaException để tạo bản sao của ngăn xếp đó và chuyển nó tới setStackTrace.

Các vấn đề liên quan