当C函数返回一个非零错误代码时,我试图在一些JNI代码中调用JNIEnv::NewObject()。
事件的顺序如下:
为了使我能够抛出它,我试图构造的类是:
public final class HseException extends Exception {
private static final long serialVersionUID = 8995408998818557762L;
private final int errno;
private final Context ctx;
/* Only called from C */
HseException(final String message, final int errno, final Context ctx) {
super(message);
this.errno = errno;
this.ctx = ctx;
}
public Context getContext() {
return this.ctx;
}
public int getErrno() {
return this.errno;
}
public static enum Context {
NONE
}
}在我的代码中,我在全局结构中缓存类和构造函数的jclass和jmethodID,但是代码看起来如下:
globals.com.micron.hse.HseException.class =
(*env)->FindClass(env, "com/micron/hse/HseException");
globals.com.micron.hse.HseException.init = (*env)->GetMethodID(
env,
globals.com.micron.hse.HseException.class,
"<init>",
"(Ljava/lang/String;ILcom/micron/hse/HseException$Context;)V");
globals.com.micron.hse.HseException.Context.class =
(*env)->FindClass(env, "com/micron/hse/HseException$Context");
globals.com.micron.hse.HseException.Context.NONE = (*env)->GetStaticFieldID(
env,
globals.com.micron.hse.HseException.Context.class,
"NONE",
"Lcom/micron/hse/HseException$Context;");注意,上面的代码位于我库的JNI_OnLoad()函数中。这个函数完成时没有出错,所以这告诉我至少我的类和方法被正确加载了。
最后,这里是我的助手函数,在这里我抛出了我的自定义异常类型:
/* hse_err_t is a scalar type.
* hse_strerror() creates a string out of that scalar.
* hse_err_to_ctx() gets the enum context value embedded within the scalar.
* hse_err_to_errno() gets the errno value embedded within the scalar.
*/
jint
throw_new_hse_exception(JNIEnv *env, hse_err_t err)
{
assert(env);
assert(err);
const size_t needed_sz = hse_strerror(err, NULL, 0);
char *buf = malloc(needed_sz + 1);
if (!buf)
return (*env)->ThrowNew(
env,
globals.java.lang.OutOfMemoryError.class,
"Failed to allocate memory for error buffer");
hse_strerror(err, buf, needed_sz + 1);
const jstring message = (*env)->NewStringUTF(env, buf);
free(buf);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const int rc = hse_err_to_errno(err);
const enum hse_err_ctx ctx = hse_err_to_ctx(err);
jfieldID err_ctx_field = NULL;
switch (ctx) {
case HSE_ERR_CTX_NONE:
err_ctx_field = globals.com.micron.hse.HseException.Context.NONE;
break;
}
assert(err_ctx_field);
const jobject err_ctx_obj = (*env)->GetStaticObjectField(
env, globals.com.micron.hse.HseException.Context.class, err_ctx_field);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const jobject hse_exception_obj = (*env)->NewObject(
env,
globals.com.micron.hse.HseException.class,
globals.com.micron.hse.HseException.init,
message,
rc,
err_ctx_obj);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
return (*env)->Throw(env, (jthrowable)hse_exception_obj);
}我知道的一个事实是,(*env)->NewObject()调用是引发异常的原因,因为前后的异常检查都会告诉我这一点。(*env)->NewStringUTF()调用是成功的,并且包含它应该包含的字符串。还成功地检索了上下文字段。
我不理解的是为什么我要获得InstantiationException。Throws部分的JNIEnv::NewObject()标记为:
THROWS:
InstantiationException: if the class is an interface or an abstract class.
OutOfMemoryError: if the system runs out of memory.
Any exceptions thrown by the constructor.我的类不是一个接口,也不是一个抽象类,那么从哪里可以生成这个异常呢?奇怪的是,我发誓这以前起过作用,但是由于我是从零开始编写这些Java绑定的,所以我一直在覆盖提交并强制推进到我的分支。
任何帮助都是非常感谢的。不幸的是,异常上的getMessage()返回null,这一点都没有帮助。JVM也没有告诉我我做错了什么。
一个可能有用的细节是,当我尝试调用JNIEnv::ThrowNew()时(在将(Ljava/lang/String;)V构造函数放入同一个HseException类之后),jni_ThrowNew()分段错误,我不明白为什么。当我保存jclass时,类是有效的,而且我知道,由于我已经检查了指针,所以它所存储的内存不会以任何方式被覆盖。
所有这些代码所在的回购程序是:https://github.com/hse-project/hse-java。未完成的产品,但至少它是可构建和测试可以运行。如果有人决定克隆并构建回购程序,我将在这里重复以下说明:
meson build
ninja -C build
meson test -C build -t 0 KvsTest # I am using this test to exercise the code path我明天的目标是尝试以较小的方式重现这个问题。我还可以尝试查看OpenJDK代码,假设这是JNI接口所在的位置。如果我仔细看的话,我可能会找到生成异常的代码行。
编辑:我做了一个测试,在当前代码中添加了一个主函数和一个本机函数,其唯一目的是从C中抛出一个异常。代码看起来类似于:
private static native void throwException();
public static void main(String[] args) {
System.load("/path/to/.so");
throwException();
}本机功能的实现是:
void Java_com_micron_hse_Hse_throwException
(JNIEnv *env, jclass hse_cls)
{
(void)hse_cls;
/* Generate error */
hse_err_t err = hse_kvdb_txn_begin(NULL, NULL);
throw_new_hse_exception(env, err);
}这将在执行java -jar path/to/jar后打印以下内容
Exception in thread "main" com.micron.hse.HseException: lib/binding/kvdb_interface.c:1046: Invalid argument (22)
at com.micron.hse.Hse.throwException(Native Method)
at com.micron.hse.Hse.main(Hse.java:28)这正是我所期望的印刷,所以现在我要说我比我开始的时候更迷失了。由于某些原因,在我的测试上下文中,会引发InstantiationException。不确定使用JAR的应用程序是否会遇到相同的问题,或者它是否只是测试上下文问题。
编辑2:
将主方法从前面的编辑更改为以下内容,这正是我的测试所做的:
public static void main(String[] args) throws HseException {
try {
loadLibrary(Paths.get("/home/tpartin/Projects/hse-java/build/src/main/c/libhsejni-2.so"));
init();
final Kvdb kvdb = Kvdb.open(Paths.get("/media/hse-tests"));
final Kvs kvs = kvdb.kvsOpen("kvs");
kvs.delete((byte[])null);
kvs.close();
kvdb.close();
} finally {
// fini();
}
}并能够适当地抛出C中的异常。这一定意味着我的测试环境出了问题。
编辑3:另一条线索。在一次测试中,此问题将生成InstantiationException。在另一个测试中,此问题是jni_NewObject中的分段错误。
发布于 2022-01-19 23:23:48
我的问题是,我坚持jclass等人。引用时间太长了。
先前的问题:为什么我不应该在JNI中重用jclass和/或jmethodID?
Java文档:参考文献
JNI函数返回的所有Java对象都是本地引用。
感谢Andrew在问题的评论中指出了这一点。我强调了他在这个答复中的意见,并将把它作为答案。
https://stackoverflow.com/questions/70763746
复制相似问题