JNI GetMethodID不适用于内部类的构造函数

时间:2014-08-18 12:13:13

标签: java c constructor java-native-interface wrapper

我有一个私有子类。我想在JNI包装器中创建该子类的实例并返回它。我用Google搜索并尝试使其工作但没有成功(methodID为null)。有什么建议吗?

JNIEXPORT jobject JNICALL Java_some_Class_some_Jni_Method(JNIEnv *env, jobject this) {
        jclass cls = (*env)->FindClass(env, "someClass$someSubclass");
        if (cls == NULL)
            printf("jclass error.");

        jmethodID methodID = (*env)->GetMethodID(env, cls, "<init>", "()V"); // -> problem!
        if (methodID == NULL)
            printf("jmethodID error.");

        jobject obj = (*env)->NewObject(env, cls, methodID);
        if (obj == NULL)
            printf("jobject error.");

        return obj;
}

EDIT1:添加类定义:

public class someClass 
{ 
    private class someSubclass {    

        public someSubclass() {
        }
    ...
    }
...
}

EDIT2:好的,我发现你需要GetMethodID签名中的父类,所以在我的例子中:jmethodID methodID = (*env)->GetMethodID(env, cls, "<init>", "(LsomeClass;)V");

但现在我使用NewObject函数获得EXCEPTION_ACCESS_VIOLATION。

EDIT3:我还需要为NewObject函数添加调用类对象/指针:jobject obj = (*env)->NewObject(env, cls, methodID, this);

现在可以正确调用嵌套类的构造函数。

4 个答案:

答案 0 :(得分:5)

我想为这个问题提供一个更复杂的答案。以下是我正在与JNI一起学习如何使用它的一些实验的简化版本。这个例子更多的是探索如何使用JNI访问对象和字段,而不是建议使用它。

此外,Java源代码稍作修改,删除了其他一些处理其他JNI用途的源代码。然而,这应该提供一个起点。 JNI有一些最佳实践,例如缓存字段标识符,在此示例中将被忽略。以下是some best practices using JNI from IBM

在这个来自该源的示例中,我们的想法是创建一个类helloworld,其中包含一个内部类ExportedFuncs,它将具有各种方法,这些方法充当一组的接口。从动态链接库(DLL)导出的本机C函数。这个内部类反过来会有自己的内部类ExportedData,它只是一个数据类。

创建ExportedFuncs对象时,它将使用JNI进行本机调用以获取ExportedData类的实例。

假设一个带有封装内部类的简单示例Java类。这个例子有一个内部类,它有一个内部类。

public class helloworld {
    private class ExportedFuncs
    {
        // declare our private, data only class with some fields
        private class ExportedData
        {
            int theInt;
            String theString;
        }
        public native ExportedData getExportedData();
        ExportedData theExportedData;
        // constructor for the ExportedFuncs class which gets a copy of the data
        ExportedFuncs()
        {
            theExportedData = getExportedData();  // get an object through native method
        }
    }

    ExportedFuncs myExportedFuncs = new ExportedFuncs();

    // ....   other fields and methods of the helloworld class follows
}

JNI原生C函数看起来

JNIEXPORT jobject JNICALL Java_helloworld_00024ExportedFuncs_getExportedData (JNIEnv *env, jobject obj)
{
    jfieldID fid = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, obj), "theExportedData", "Lhelloworld$ExportedFuncs$ExportedData;");
    jobject newObj = 0;
    jclass cls = (*env)->FindClass(env, "Lhelloworld$ExportedFuncs$ExportedData;");

    // Get the Method ID of the constructor for this inner class.
    // There are two things to notice about this GetMethodID() function call.
    // First, the constructor is requested by specifying the special string "<init>"
    // Second, the signature of the constructor includes the enclosing class in the signature.
    // Also there are no arguments for this constructor. if there were then they would need to be included between the parenthesis
    // for example "(Lhelloworld$ExportedFuncs;I)V" for a single int arg.
    jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(Lhelloworld$ExportedFuncs;)V");
    if (NULL == midInit) return NULL;

    // Call the class constructor to allocate a new instance.  the default constructor has no arguments.
    newObj = (*env)->NewObject(env, cls, midInit);

    // now lets set some values in our new object and return it.
    if (newObj) {
        jfieldID fidAge = (*env)->GetFieldID (env, cls, "theInt", "I");
        (*env)->SetIntField (env, newObj, fidAge, 127);
    }

    return newObj;
}

使用javah类上的helloworld实用程序生成本机JNI代码的函数签名。您也可以找到javap实用程序的输出。

顺便说一下,我觉得有趣的是内部类的本机方法的名称有五位数字字段00024,它是ANSI / ASCII表中美元符号($)的十六进制。美元符号用于JNI函数(例如GetFieldID())中使用的完全限定名称的内部类的分隔符。

我没有在这个人为的示例中使用包,因此本机C函数名没有包组件。通常会有。我遇到的一个问题是,该命名约定所使用的函数名长度的限制是什么。

请注意,GetFieldID()FindClass()函数都使用完全限定的类名"Lhelloworld$ExportedFuncs$ExportedData;",其内部类由美元符号($)分隔。

GetMethodID()函数必须包含任何内部类的父类。如果正在查找的方法位于主类helloworld内,则调用将如下所示:

jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "()V");

然而,由于我们想要构造内部类的内部类,我们需要为我们想要构造的内部类指定父类:

jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(Lhelloworld$ExportedFuncs;)V");

另一点是ExportedData类的构造函数是默认构造函数,它不带任何参数。如果存在参数,则需要将这些参数添加到GetMethodID()函数调用中使用的方法签名中。因此,如果使用了int的构造函数,则签名看起来像"(Lhelloworld$ExportedFuncs;I)V"

答案 1 :(得分:2)

您需要GetMethodID签名中的父类,因此在我的示例中: jmethodID methodID = (*env)->GetMethodID(env, cls, "<init>", "(LsomeClass;)V");

我还需要将调用类对象/指针添加到NewObject函数: jobject obj = (*env)->NewObject(env, cls, methodID, this);

答案 2 :(得分:1)

user2340939的回答帮助我找到了用内部类的整数参数构造一个新对象的正确方法。 这是我的奖励。

<强> JAVA

package xxx.test_jni;

public class myNDK {
    public myNDK() { Log.d("myNDK","myNDK constructor"); }
    public myNDK(int a) { Log.d("myNDK","myNDK constructor(int)"); }
    static {
        System.loadLibrary("myJNI");
    }

    public class myObj {
        int aaa;
        public myObj(){
            Log.d("myNDK","myObj()");
            this.aaa = 333;
        }
        public myObj(int aaa){
            Log.d("myNDK","myObj(int) " + aaa);
            this.aaa = aaa;
        }
        public int getAaa() {
            Log.d("myNDK","getAaa()");
            return aaa;
        }
    }
    public native myObj getmyObj1();
    public native myObj getmyObj2();
}

<强> CPP

JNIEXPORT jobject JNICALL Java_xxx_test_1jni_myNDK_getmyObj1
  (JNIEnv *env, jobject thiz){

  // Find inner class
  jclass innerCls = env->FindClass("xxx/test_jni/myNDK$myObj");
  if (innerCls == NULL) {
    LOGI("%s, FindClass nullptr\n", __func__);
    return NULL;
  }

  // Get Method ID myObj(), constructor
  jmethodID cnstrctr1 = env->GetMethodID(innerCls, "<init>", "(Lxxx/test_jni/myNDK;)V");
  if (cnstrctr == NULL) {
    LOGI("%s, GetMethodID nullptr\n", __func__);
    return NULL;
  }
  jobject obj1 = env->NewObject(innerCls, cnstrctr1, thiz);
  if (obj1 == NULL) {
    LOGI("%s, NewObject nullptr\n", __func__);
    return NULL;
  }

  // Get Method ID myObj(int), constructor
  jmethodID cnstrctr2 = env->GetMethodID(innerCls, "<init>", "(Lxxx/test_jni/myNDK;I)V");
  if (cnstrctr2 == NULL) {
    LOGI("%s, GetMethodID2 nullptr\n", __func__);
    return NULL;
  }
  jint a = 5;
  jobject obj2 = env->NewObject(innerCls, cnstrctr2, thiz, a);
  if (obj2 == NULL) {
    LOGI("%s, NewObject2 nullptr\n", __func__);
    return NULL;
  }

  return obj2; // or obj1
}

To NewObject, NOT 内部类

JNIEXPORT jobject JNICALL Java_xxx_test_1jni_myNDK_getmyObj2
  (JNIEnv *env, jobject thiz){

  jclass cls = env->FindClass("xxx/test_jni/myNDK");

  // Get Method ID myNDK(), constructor
  jmethodID cnstrctr1 = env->GetMethodID(cls, "<init>", "()V");
  jobject obj1 = env->NewObject(cls, cnstrctr1);
  // Get Method ID myNDK(int), constructor
  jmethodID cnstrctr2 = env->GetMethodID(cls, "<init>", "(I)V");
  jint a = 1;
  jobject obj2 = env->NewObject(cls, cnstrctr2, a);

  return obj2; // or obj1
}

但我还是想知道哪个文件告诉内部类的NewObject API必须添加parenter类?

答案 3 :(得分:0)

非常重要的一点是要注意@ user2340939的答案:

https://stackoverflow.com/a/25363953/6655884

我遇到有关弱本地引用的错误,因为Java认为我的第一个参数是父类。

如果可以的话,可以将内部类设为静态,然后不必将父类签名放入构造函数中,也无需在创建对象时在构造函数中传递父类