【发布时间】:2020-11-05 15:38:14
【问题描述】:
场景
Java 代码库使用 C++ 库。实现了一个 JNI 接口,以便有一个 API 来使用 Java 调用访问本机方法。
到目前为止,这样做的方式是通过一个函数 Java 端:
private static native void useFancyNativeFunction() throws SomeCustomException;
JNI头文件中对应的方法包括错误处理,在需要时会抛出Java异常:
try
{
// do amazing C++ things
}
catch ( const std::runtime_error& e )
{
jclass Exception = env->FindClass( "util/somestuff/SomeCustomException" );
env->ThrowNew( Exception, e.what() );
}
这意味着,如果在我们的本地代码的特定块中引发运行时异常,则会在由 JVM 封装的应用程序中引发并处理自定义 Java 异常。
问题
这引发了一个尚未解决的有趣讨论:
在这个上下文中抛出一个自定义异常意味着我们在 JNI 接口实现和我们特定的 Java 代码库之间创建了一个依赖关系。另一种方法是在本机引发通用 Java 异常,然后通过 Java 代码捕获它们,然后引发专门的异常。
所以在给定的场景中有两种选择:
- 本机引发专门的自定义 Java 异常:
// Java caller
try
{
useFancyNativeFunction();
}
catch( SomeCustomException e )
{
// treat custom exception directly here
}
// Java native call
private static native void useFancyNativeFunction() throws SomeCustomException;
// C++ JNI header
try
{
// do amazing C++ things
}
catch ( const std::runtime_error& e )
{
jclass Exception = env->FindClass( "util/somestuff/SomeCustomException" );
env->ThrowNew( Exception, e.what() );
}
- 本地抛出通用 Java 异常,在 Java 类中捕获它,作为专用异常重新抛出:
// Java caller
try
{
useFancyNativeFunction();
}
catch( RuntimeException e )
{
// catch generic, throw specialized, handle elsewhere
throw new SomeCustomException( e.getMessage() );
}
// Java native call
private static native void useFancyNativeFunction() throws RuntimeException;
// C++ JNI header
try
{
// do amazing C++ things
}
catch ( const std::runtime_error& e )
{
jclass Exception = env->FindClass( "java/lang/RuntimeException" );
env->ThrowNew( Exception, e.what() );
}
哪个是首选,为什么?
其他信息
- 在我们的例子中,这两个版本都没有缺点,因为只有一个 Java 代码库和一个 C++ 代码库。 JNI 接口不需要重复使用。
- 依赖只存在于接口本身,而不存在于库中。后者正在被我们重用,但不涉及 Java。
- 这两种选择都不可能对我们产生重大影响。我只是出于兴趣而询问。
【问题讨论】:
-
"在 JNI 接口实现和我们特定的 Java 代码库之间创建依赖关系" 这不一定是正确的做法,但我维护的 JNI(10 岁)和相应的 Java 代码有着千丝万缕的联系。我总是建议抛出一个特定的异常。
-
这也是我目前的感受,但是有理由支持和反对。 例如从本机角度来看异常类的路径只是一个字符串,因此是潜在的错误来源。通用异常的路径总是相同的。我正在寻找更多理由和/或最佳实践建议。
-
@Bathsheba 你有任何偏爱的理由吗?
-
它使 JNI 的行为更像普通的 Java 代码。我猜另一种方法是“存根”Java端的所有本机函数,捕获从JNI抛出的通用异常,然后将它们作为特定的异常类型重新抛出。有趣的是,我刚刚意识到您可以选择。我不这样做,因为我已经不得不调用 Java 代码。
-
是的,您的选择是选项 2。在我的帖子中。到目前为止,我看到的唯一原因是库(和 JNI)在不同的 Java 代码库中重用。因为在这种情况下,Java 代码库需要正确实现自定义异常。否则,JNI 接口将尝试抛出一个不存在的异常,因为它通过类路径查找它。
标签: java c++ exception java-native-interface