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

use*_*939 8 c java java-native-interface constructor 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;
}
Run Code Online (Sandbox Code Playgroud)

EDIT1:添加类定义:

public class someClass 
{ 
    private class someSubclass {    

        public someSubclass() {
        }
    ...
    }
...
}
Run Code Online (Sandbox Code Playgroud)

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);

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

use*_*939 7

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

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


Ric*_*ers 6

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

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

在这个从该源获取的示例中,想法是拥有一个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
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

GetMethodID()函数必须包含任何内部类的父类.如果正在查找的方法是在主类中helloworld,那么调用将如下所示:

jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "()V");
Run Code Online (Sandbox Code Playgroud)

但是,由于我们想要构造内部类的内部类,我们需要为要构造的内部类指定父类,如下所示:

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

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