有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我有一个带有私有子类的类。我想在JNI包装器中创建该子类的一个实例并返回它。我在谷歌上搜索并试图让它工作,但没有成功(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函数的异常访问冲突

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

嵌套类的构造函数现在被正确调用


共 (4) 个答案

  1. # 1 楼答案

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

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

  2. # 2 楼答案

    请注意@user2340939的答案:

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

    由于java认为我的第一个参数是类父级,所以我在弱本地引用方面出错

    如果可以,可以将内部类设置为静态,这样就不需要在构造函数中放置父类签名,也不需要在创建对象时在构造函数中传递父类

  3. # 3 楼答案

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

    此外,Java源代码经过了轻微修改,删除了大量处理其他JNI使用的其他源代码。然而,这应该是一个起点。JNI有一些最佳实践,比如缓存字段标识符,在本例中被忽略。这里是some best practices using JNI from IBM

    在这个源代码示例中,想法是有一个类helloworld,它包含一个内部类ExportedFuncs,它将有各种方法作为从动态链接库(DLL)导出的一组本机C函数的接口。这个内部类将有自己的内部类ExportedData,这将是一个只包含数据的类

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

    JNI需要一个完全限定的类名

    请注意,在下面的JNI原生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"

    带有内部类的Java和JNI的简单示例

    假设一个简单的示例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;
    }
    

    本机JNI代码的函数签名是使用helloworld类上的javah实用程序生成的。您可能还会发现javap实用程序的输出也很有用

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

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

  4. # 4 楼答案

    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
    }
    

    对于NewObject,不是一个内部类

    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类