Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JNI GetMethodID not working for constructor of inner class

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.

like image 345
user2340939 Avatar asked Aug 18 '14 12:08

user2340939


2 Answers

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

like image 200
user2340939 Avatar answered Nov 15 '22 17:11

user2340939


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.

like image 37
Richard Chambers Avatar answered Nov 15 '22 17:11

Richard Chambers