JNI中GetMethodID参数列表的枚举类型签名问题
问题:JNI调用Kotlin枚举构造AppState时出现NoSuchMethodError?
问题场景
这是一个谷歌搜索都很难找到解决方案的边缘案例。我在Kotlin中定义了如下枚举类:
package myapp public enum State { STATE_SUCCEEDED, STATE_FROZEN, STATE_WAITING, STATE_IN_PROGRESS, STATE_FAILED }
又定义了包含该枚举成员变量的AppState类:
package myapp class AppState ( val state: State )
之后我在C代码中尝试创建AppState实例:
jclass cls = (*env)->FindClass(env, AppState_ClassPath); if (cls == NULL) { __android_log_print(ANDROID_LOG_INFO, "DEBUG", "AppState class is null"); } jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "(Lmyapp/State;)V"); return (*env)->NewObject( env, cls, mid, enumState );
但从Kotlin调用该C代码时出现错误:
JNI DETECTED ERROR IN APPLICATION: JNI NewStringUTF called with pending exception java.lang.NoSuchMethodError: no non-static method "Lmyapp/AppState;.<init>(Lmyapp/State;)V"
我以为是枚举没有公共构造方法导致的,但不清楚该如何在签名中正确处理这个枚举。
附上javap输出结果:
public final class myapp.AppState { public myapp.AppState(myapp.State); descriptor: (Lmyapp/State;)V public final myapp.State getState(); descriptor: ()Lmyapp/State; }
以及枚举转换函数代码:
jobject getState(JNIEnv* env, enum State state) { char path[64]; char* stateName; switch(state) { case STATE_SUCCEEDED: stateName = "STATE_SUCCEEDED"; break; case STATE_FROZEN: stateName = "STATE_FROZEN"; break; case STATE_WAITING: stateName = "STATE_WAITING"; break; case STATE_IN_PROGRESS: stateName = "STATE_IN_PROGRESS"; break; default: stateName = "STATE_FAILED"; }; snprintf(path, sizeof(path) - 1,"[L%s;", State_ClassPath); jclass cls = (*env)->FindClass(env, State_ClassPath); jfieldID fid = (*env)->GetStaticFieldID(env, cls , stateName, path); jobject stateObj = (*env)->GetStaticObjectField(env, cls, fid); return stateObj; }
解决方案
问题根源
错误提示里的NoSuchMethodError是误导性异常,真正的问题出在枚举转换函数getState中:你给枚举常量的字段签名写错了!
你用snprintf生成的[Lmyapp/State;是State数组的类型签名,但Kotlin枚举的每个常量都是Lmyapp/State;(单个枚举对象)类型,这导致GetStaticFieldID获取不到正确的字段ID,进而返回的stateObj是无效对象(或null)。当你把这个无效对象传给NewObject时,JNI的异常链触发了后续的错误提示,掩盖了真正的问题。
修正步骤
- 修正枚举常量的类型签名
把getState函数中生成签名的代码改成单个枚举对象的签名:
// 替换原来的snprintf行,直接使用正确的签名 const char* enumSig = "Lmyapp/State;";
或者如果State_ClassPath是"myapp/State",也可以用snprintf生成正确的签名:
snprintf(path, sizeof(path) - 1, "L%s;", State_ClassPath);
添加JNI调用的异常检查
在每个JNI调用后检查结果,避免异常积累导致错误混淆:修正后的完整getState函数
jobject getState(JNIEnv* env, enum State state) { const char* stateName; switch(state) { case STATE_SUCCEEDED: stateName = "STATE_SUCCEEDED"; break; case STATE_FROZEN: stateName = "STATE_FROZEN"; break; case STATE_WAITING: stateName = "STATE_WAITING"; break; case STATE_IN_PROGRESS: stateName = "STATE_IN_PROGRESS"; break; default: stateName = "STATE_FAILED"; } jclass cls = (*env)->FindClass(env, State_ClassPath); if (cls == NULL) { __android_log_print(ANDROID_LOG_ERROR, "JNI", "找不到State类"); return NULL; } // 使用单个枚举对象的正确签名 jfieldID fid = (*env)->GetStaticFieldID(env, cls, stateName, "Lmyapp/State;"); if (fid == NULL) { __android_log_print(ANDROID_LOG_ERROR, "JNI", "找不到枚举常量%s", stateName); (*env)->DeleteLocalRef(env, cls); return NULL; } jobject stateObj = (*env)->GetStaticObjectField(env, cls, fid); (*env)->DeleteLocalRef(env, cls); return stateObj; }
额外验证
从javap的输出可以确认,AppState的构造方法签名(Lmyapp/State;)V完全正确,只要你传递的enumState是有效的枚举对象,NewObject就能正常创建AppState实例。
内容的提问来源于stack exchange,提问作者Lee Sylvester
相关产品推荐
相关产品推荐

