JNI GetMethodID not working for constructor of inner class
Asked Answered
F

4

11

I have a class with a private subclass. I want to create an instance of that subclasss in a JNI wrapper and return it. I've googled and tried to make it work but with no success (methodID is null). Any suggestions?

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: adding class definition:

public class someClass 
{ 
    private class someSubclass {    

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

EDIT2: Ok I figured out you need parent class in the GetMethodID signature, so in my example: jmethodID methodID = (*env)->GetMethodID(env, cls, "<init>", "(LsomeClass;)V");

But now I get EXCEPTION_ACCESS_VIOLATION with NewObject function.

EDIT3: I also needed to add calling class object/pointer to the NewObject function: jobject obj = (*env)->NewObject(env, cls, methodID, this);

Constructor of nested class is now called properly.

Fons answered 18/8, 2014 at 12:13 Comment(2)
Without seeing the class definitions, it's impossible to tell whether you're just misspelling the function signature, or whether there's a real problem.Drypoint
I've added class definition.Fons
F
8

You need parent class in the GetMethodID signature, so in my example: jmethodID methodID = (*env)->GetMethodID(env, cls, "<init>", "(LsomeClass;)V");

And I also needed to add calling class object/pointer to the NewObject function: jobject obj = (*env)->NewObject(env, cls, methodID, this);

Fons answered 18/8, 2014 at 13:1 Comment(0)
P
8

I thought to provide a more involved answer to this question. The following is a simplified version of some experiments I am doing with JNI to learn how to use it. This example is more about exploring how to access objects and fields using JNI rather than to be a recommendation as to use.

Also the Java source is slightly modified removing quite a bit of other source dealing with other JNI uses. However this should provide a starting place. There are best practices for JNI such as caching of field identifiers which are being ignored in this example. Here are some best practices using JNI from IBM.

In this example taken from that source the idea was to have a class, helloworld, which contained an inner class, ExportedFuncs, which would have various methods which acted as an interface to a set of native C functions exported from a dynamic link library (DLL). This inner class would in turn have its own inner class, ExportedData, which would be a data only class.

When an ExportedFuncs object was created, it would do a native call using JNI to obtain an instance of an ExportedData class.

JNI requires a fully qualified class name

Notice in the JNI Native C source below that both the GetFieldID() and the FindClass() functions use a fully qualified class name of "Lhelloworld$ExportedFuncs$ExportedData;" which has the inner classes separated by the US dollar sign ($).

The GetMethodID() function must include the parent classes of any inner class. If the method being looked up was within the main class, helloworld, then the call would look like:

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

However since we are wanting to construct an inner class of an inner class we need to specify the parent classes for the inner class we want to construct as in:

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

One other point is that the constructor for the ExportedData class is the default constructor which does not take any arguments. If there were arguments then those would need to be added to the method signature used in the GetMethodID() function call. So if a constructor that took an int was being used then the signature would look like "(Lhelloworld$ExportedFuncs;I)V".

A simple example of Java and JNI with inner class

Assume a simple example Java class with an encapsulated inner class. This example has an inner class that has an inner class.

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
}

The JNI native C function would look

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

The function signature for the native JNI code was generated using the javah utility on the helloworld class. You may also find the output from the javap utility helpful as well.

By the way I thought it interesting that the name of the native method of the inner class has the numeric field of five digits, 00024, which is the hex for the US dollar sign ($) in the ANSI/ASCII table. The US dollar sign is used for the separator for inner classes in a fully qualified name used in JNI functions such as GetFieldID().

I am not using packages in this contrived example so there is no package component to the native C function name. Ordinarily there would be. And a question I have is what are the limits of the function name length used with that that naming convention.

Pooch answered 25/1, 2015 at 14:50 Comment(0)
S
1

user2340939's answer help me to find the right way to construct an new object with integer argument of inner class. Here is my reward.


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 an inner class

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
}

But I still want to know which document tells the NewObject API of inner class have to add parenter class?

Selfinduction answered 8/9, 2016 at 5:55 Comment(0)
B
0

Very important to pay attention for @user2340939's answer:

https://mcmap.net/q/984870/-jni-getmethodid-not-working-for-constructor-of-inner-class

I got errors about weak local references because java thought my first argument was the class parent.

If you can, you can make the inner class static and then you don't need to put the parent class signature in the constructor, neither pass the parent class in the constructor when creating the object

Betook answered 24/9, 2020 at 0:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.