You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的异常链触发了后续的错误提示,掩盖了真正的问题。

修正步骤

  1. 修正枚举常量的类型签名
    把getState函数中生成签名的代码改成单个枚举对象的签名:
// 替换原来的snprintf行,直接使用正确的签名
const char* enumSig = "Lmyapp/State;";

或者如果State_ClassPath是"myapp/State",也可以用snprintf生成正确的签名:

snprintf(path, sizeof(path) - 1, "L%s;", State_ClassPath);
  1. 添加JNI调用的异常检查
    在每个JNI调用后检查结果,避免异常积累导致错误混淆:

  2. 修正后的完整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 19:10:15