How to create an object with JNI?

JavaAndroidJava Native-InterfaceAndroid Ndk

Java Problem Overview


I need to implement some functions into an Android application using NDK and thus JNI.

Here's the C code, with my concerns, that I wrote:

#include <jni.h>
#include <stdio.h>
	
jobject
Java_com_example_ndktest_NDKTest_ImageRef(JNIEnv* env, jobject obj, jint width, jint height, jbyteArray myArray)
{
	jint i;
	jobject object;
	jmethodID constructor;
	jobject cls;
	cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest/Point");

//what should put as the second parameter? Is my try correct, according to what
//you can find in .java file? I used this documentation: http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp16027

	constructor = (*env)->GetMethodID(env, cls, "<init>", "void(V)");
//http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp16660
//Again, is the last parameter ok?

	object = (*env)->NewObject(env, cls, constructor, 5, 6);
//I want to assign "5" and "6" to point.x and point.y respectively.
   	return object;
}    

My problems are more or less explained inside the code. Maybe also: is the return type of the function (jobject) ok?

Now the NDKTest.java:

package com.example.ndktest;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;

public class NDKTest extends Activity {
    /** Called when the activity is first created. */
	public native Point ImageRef(int width, int height, byte[] myArray);
	public class Point
	{
		
		Point(int myx, int myy)
		{
			x = myx;
			y = myy;
		}
		
		int x;
		int y;
	}
	
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    	
    	 super.onCreate(savedInstanceState);
         TextView tv = new TextView(this);
         byte[] anArray = new byte[3];
         for (byte i = 0; i < 3; i++)
        	 anArray[i] = i;
         Point point = ImageRef(2, 3, anArray);
         tv.setText(String.valueOf(point.x));
         	setContentView(tv);     
    }
    
    
    
    static
    {
       System.loadLibrary("test");
    }
}

When I try to run the code, it doesn't work.

Java Solutions


Solution 1 - Java

Since Point is an inner class, the way to get it would be

jclass cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest$Point");

The $ convention for inner classes is not really clearly documented in the authoritative specs, but is entrenched in so much working code that it's unlikely to change. Still, it would feel somewhat more robust if you restricted your JNI code to work with top-level classes.

You want a constructor that takes two ints as arguments. The signature for that is (II)V, so:

constructor = (*env)->GetMethodID(env, cls, "<init>", "(II)V");

Next time, include some error handling in your code, such that you'll have a clue which part of it doesn't work!

Solution 2 - Java

The specification is correct, but a bit misleading in this case. GetMethodID requires a method name and a method signature. The specification says:

> To obtain the method ID of a constructor, supply <init> as the method name and void (V) as the return type.

Note that it says return type, not signature. Although void(V) looks superficially similar to a signature, the specification is telling you that the signature must specify a void (that is, V) return type.

The correct signature for a no-argument constructor is ()V. If the constructor has arguments, they should be described between the parentheses, as other commenters have noted.

Solution 3 - Java

Some problems with your code.

First, why are you creating your own Point class rather than using library-provided android.graphics.Point?

Second, the class spec for nested classes is different - it would be "com/example/ndktest/NDKTest$Point". Class nesting is different from packages.

Third, I don't think JNI lets you create instances of nonstatic nested classes. You need to pass the nesting class object' this pointer at object creation - there's no such argument.

Finally, while I've seen the guidance to use "void(V)" as a constructor method signature, but this is out of line with the rest of method signatures; normally, a method with two int parameters and void return type would be "(II)V".

As a side note, I found it much cleaner to pass primitive types and arrays of primitive typed from NDK to Java. Object creation/access is messy and hard to debug.

Solution 4 - Java

In JNI you can always use the javap tool for finding method signatures. Just run javap -s com.example.ndktest.NDKTest and copy the method signatures from the output.

Solution 5 - Java

Three steps should create the Point object with JNI:

jobject
Java_com_example_ndktest_NDKTest_ImageRef(JNIEnv* env, jobject obj, jint width, jint height, jbyteArray myArray)
{
    ...
    jclass cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest$Point");
    jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "void(V)");
    jobject object = (*env)->NewObject(env, cls, constructor, obj, 5, 6);
    ...
}

Solution 6 - Java

I use:

jclass clazz = env->FindClass ( "ISOInfoEx" );
jobject obj = env->AllocObject ( clazz );

This will create an object of the class ISOInfoEx. In my case i use it later in code as a structure.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpmichnaView Question on Stackoverflow
Solution 1 - Javahmakholm left over MonicaView Answer on Stackoverflow
Solution 2 - JavapburkaView Answer on Stackoverflow
Solution 3 - JavaSeva AlekseyevView Answer on Stackoverflow
Solution 4 - JavaChristian AbergerView Answer on Stackoverflow
Solution 5 - JavaBangynoView Answer on Stackoverflow
Solution 6 - JavaIDarkView Answer on Stackoverflow