Let's say I have a Java application that has the following class,
class Adder {
public int add(int a, int b) { return a + b; }
}
If I load into Java a shared library (.so file) using System.loadLibrary("libABC.so"), can a native method in libABC invoke adder.add(1,2) where adder is an instantiation of Adder? If yes, an example/pointer in the right direction would be very much appreciated.
You have Adder class with add() method
class Adder {
public int add(int a, int b) { return a + b; }
}
For example, we have an instance named mAdder in class Demo.
public class Demo {
native void nativeEntry();
Adder mAdder = new Adder();
public static void main(String[] args){
System.loadLibrary("JNIBridge");
Demo demo = new Demo();
demo.nativeEntry();
}
}
In JNI, demo object is stored in thiz pointer.
Use GetObjectField() to get mAdder from thiz.
Use CallIntMethod() to invoke add() method of mAdder.
#include <jni.h>
#include <stdio.h>
//use command
//javah -jni Demo
//to generate jni method declaration
JNIEXPORT void JNICALL Java_Demo_nativeEntry(JNIEnv *env, jobject thiz) {
//thiz is the calling object
//in java main(), we call: demo.nativeEntry()
//so, thiz is demo object
//get class Demo
jclass demoCls = (*env)->FindClass(env, "Demo");
//get id of mAdder in Demo
jfieldID adderField = (*env)->GetFieldID(env, demoCls, "mAdder", "LAdder;");
//get object mAdder from object demo
jobject adderObject = (*env)->GetObjectField(env, thiz, adderField);
//get class Adder
jclass adderCls = (*env)->FindClass(env, "Adder");
//get id of method add in Adder
jmethodID addMethod = (*env)->GetMethodID(env, adderCls, "add", "(II)I");
// (II)I: is signature of method Adder.add()
// use command:
//javap -s -p Adder
//to get method signature
//call method add of object mAdder
jint sum = (*env)->CallIntMethod(env, adderObject, addMethod, 10, 20);
//sum = add(10,20)
printf("sum = %d\n", sum);
}
You can get full code at here.
Yes, and often even if add is a private method.
Related
I'm writing JNI staff in C11 and have a question about strictly-conforming on-heap object creation.
JNI API provides a function to do this with the following signature:
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
As specified in the 6.5.2.2(p7) Standard
The ellipsis notation in a function prototype declarator causes
argument type conversion to stop after the last declared parameter.
Arguments corresponding to the ellipsis notation should be explicitly converted to the expected type in order for the code to be conforming. Consider the following case:
public class Event{
public final int eventType;
public final String meta;
public Event(int eventType, String meta){
this.eventType = eventType;
this.meta = meta;
}
}
What types of the arguments I should convert the parameters corresponding to the ellipsis to?
I can guess that it should look as follows:
jclass event_class = ((*env)->FindClass)(env, "f/q/c/n/Event");
jmethodID ctor = (*env)->GetMethodID(
env,
event_class,
"<init>",
"(ILjava/lang/String;)V"
);
array_element = (*env)->NewObject(
env,
event_class,
ctor,
(jint) 0, (jobject) NULL //corresponds to the ellipsis
);
The types of the arguments are deduced from the method you are calling.
In your case, it is the constructor of the Event class that expects an int and a String.
So it would look like this:
jstring metaStr = (*env)->NewStringUTF(env, "hello");
jobject array_element = (*env)->NewObject(
env,
event_class,
ctor,
(jint)4711, metaStr
);
Test
To perform a brief test, we could write a class that calls a native C function that creates the desired Event object, initializes it, and returns it to the calling Java side.
This Java program would look like this:
import f.q.c.n.Event;
public class JNIEventTest {
static {
System.loadLibrary("native");
}
private native Event retrieveNativeEvent();
public static void main(String[] args) {
JNIEventTest jniEventTest = new JNIEventTest();
Event event = jniEventTest.retrieveNativeEvent();
System.out.println("eventType: " + event.eventType);
System.out.println("meta: " + event.meta);
}
}
The native C side would look like this then:
#include "JNIEventTest.h"
JNIEXPORT jobject JNICALL Java_JNIEventTest_retrieveNativeEvent(JNIEnv *env, jobject thisObject) {
jclass event_class = ((*env)->FindClass)(env, "f/q/c/n/Event");
jmethodID ctor = (*env)->GetMethodID(
env,
event_class,
"<init>",
"(ILjava/lang/String;)V"
);
jstring eventStr = (*env)->NewStringUTF(env, "hello");
jobject array_element = (*env)->NewObject(
env,
event_class,
ctor,
(jint)4711, eventStr
);
return array_element;
}
The debug output in the console then looks like this:
eventType: 4711
meta: hello
I have:
public class MyEntity {
private String _myEntityType;
public String get_myEntityType() {
return _myEntityType;
}
public void set_myEntityType(String _myEntityType) {
this._myEntityType = _myEntityType;
}
}
Then :
public class MyObjectEntity extends MyEntity {
public MyObjectEntity() {
super();
}
private String _myObjectDescription;
public String get_myObjectDescription() {
return _myObjectDescription;
}
public void set_myObjectDescription(String _myObjectDescription) {
this._myObjectDescription = _myObjectDescription;
}
}
Now I start getting into JNI.
public class MyPers {
// Load the 'my-pers-lib' library on application startup.
static {
System.loadLibrary("my-pers-lib");
}
public native Long myPersInit(MyEntity myEntity);
}
Then :
#include <jni.h>
#include <android/log.h>
extern "C"
JNIEXPORT jobject JNICALL
Java_my_ca_my_1gen_1lib_1pers_c_1libs_1core_RevPers_myPersInit(JNIEnv *env, jobject instance,
jobject myEntity) {
jclass myEntityClazz = env->GetObjectClass(myEntity);
/** START GET STRING **/
const char *myEntityType;
// and the get_myObjectDescription() method
jmethodID get_myObjectDescription = env->GetMethodID
(myEntityClazz, "get_myObjectDescription", "()Ljava/lang/String;");
// call the get_myObjectDescription() method
jstring s = (jstring) env->CallObjectMethod
(myEntity, get_myObjectDescription);
if (s != NULL) {
// convert the Java String to use it in C
myEntityType = env->GetStringUTFChars(s, 0);
__android_log_print(ANDROID_LOG_INFO, "MyApp", "get_myObjectDescription : %s\n",
myEntityType);
env->ReleaseStringUTFChars(s, myEntityType);
}
/** END GET STRING **/
return myEntityGUID;
}
I start it all up :
MyObjectEntity myObjectEntity = new MyObjectEntity();
myObjectEntity.set_myObjectDescription("A Good Day To Find Answers To Life's Important Questions.");
MyPers revPers = new MyPers();
Log.v("MyApp", "GUID : " + myPers.myPersInit(myObjectEntity));
The error I get is :
JNI CallObjectMethodV called with pending exception java.lang.NoSuchMethodError: no non-static method "Lmy_app/MyEntity;.get_myObjectDescription()Ljava/lang/String;"
QUESTION
How can I go about calling a method of a subclass / child class of an Java Object passed to JNI jobject, myEntity in this case?
extern "C"
JNIEXPORT jobject JNICALL
Java_myPersInit(JNIEnv *env, jobject instance, jobject myEntity)
Thank you all in advance.
How can I go about calling a method of a subclass / child class of an
Java Object passed to JNI jobject, myEntity in this case?
The question does not make sense. Objects do not have subclasses, classes do. Whatever the class of a given object happens to be defines the methods that you may invoke on that object. You seem to be trying to ask how to invoke methods of a subtype of the declared type of the native method parameter, but the premise of such a question runs counter to the actual fact that you do so exactly the same way you invoke any other instance method from JNI.
Consider: in Java, you would downcast to the subtype and then, supposing the cast succeeded, you would invoke the wanted method normally. In JNI, on the other hand, object references are not differentiated by pointed-to object type (they are all jobjects), so no casting is involved; you just go straight to invoking the wanted method normally.
Of course, a JNI error will occur if the class of the target object does not provide such a method. Whether it's a good idea to do what you describe is an entirely separate question.
I faced with the next problem: I can not do anything with byte[] (jbyteArray) in C code. All functions that work with array in JNI cause JNI DETECTED ERROR IN APPLICATION: jarray argument has non-array type. What's wrong with my code?
C:
#include <stdio.h>
#include <jni.h>
static jstring convertToHex(JNIEnv* env, jbyteArray array) {
int len = (*env)->GetArrayLength(env, array);// cause an error;
return NULL;
}
static JNINativeMethod methods[] = {
{"convertToHex", "([B)Ljava/lang/String;", (void*) convertToHex },
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
jclass cls = (*env)->FindClass(env, "com/infomir/stalkertv/server/ServerUtil");
(*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0]) );
return JNI_VERSION_1_4;
}
ServerUtil:
public class ServerUtil {
public ServerUtil() {
System.loadLibrary("shadow");
}
public native String convertToHex(byte[] array);
}
Main Activity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ServerUtil serverUtil = new ServerUtil();
byte[] a = new byte[]{1,2,3,4,5};
String s = serverUtil.convertToHex(a);
}
}
My environment:
Android Studio 2.0
Experimental Gradle plugin 0.7.0
JAVA 1.8
NDK r11b
Windows 10 x64
Thanks in advance!
The second argument passed to your function isn't a jbyteArray.
Per the JNI documentation, the arguments passed to a native function are:
Native Method Arguments
The JNI interface pointer is the first argument to native methods. The
JNI interface pointer is of type JNIEnv. The second argument differs
depending on whether the native method is static or nonstatic. The
second argument to a nonstatic native method is a reference to the
object. The second argument to a static native method is a reference
to its Java class.
The remaining arguments correspond to regular Java method arguments.
The native method call passes its result back to the calling routine
via the return value.
Your jstring convertToHex(JNIEnv* env, jbyteArray array) is missing the second jclass or jobject argument, so you're treating either a jobject or jclass argument and a jbyteArray.
Your native method signature is incorrect. It should be
static jstring convertToHe(JNIEnv *env, jobject thiz, jbytearray array)
I have a function already implemented in cpp with prototype
MyFunction(int size, int (* callback)(UINT16* arg1, UINT16* arg2));
Second argument is a function pointer which must be implemented in java. How can i implement that function?
Also how can i call MyFunction in JNI? Please help
Try this
Java
import java.util.*;
public class JNIExample{
static{
try{
System.loadLibrary("jnicpplib");
}catch(Exception e){
System.out.println(e.toString());
}
}
public native void SetCallback(JNIExample jniexample);
public static void main(String args[]){
(new JNIExample()).go();
}
public void go(){
SetCallback(this);
}
//This will be called from inside the native method
public String Callback(String msg){
return "Java Callback echo:" +msg;
}
}
In C++ native:
#include "JNIExample.h"
JNIEXPORT void JNICALL Java_JNIExample_SetCallback (JNIEnv * jnienv, jobject jobj, jobject classref)
{
jclass jc = jnienv->GetObjectClass(classref);
jmethodID mid = jnienv->GetMethodID(jc, "Callback","(Ljava/lang/String;)Ljava/lang/String;");
jstring result = (jstring)jnienv->CallObjectMethod(classref, mid, jnienv->NewStringUTF("hello jni"));
const char * nativeresult = jnienv->GetStringUTFChars(result, 0);
printf("Echo from Setcallback: %s", nativeresult);
jnienv->ReleaseStringUTFChars(result, nativeresult);
}
The idea here is calling a method in Java through its class instance. In case you do not know the name of java function in advance then function name can be passed as parameter as well.
For more information: http://www.tidytutorials.com/2009/07/java-native-interface-jni-example-using.html
In your C++ native code you can simply define a function call Java callback implementation and then pass the function pointer to your native library -- like this:
native c++:
// link func
callback_link(int size, int (* callback)(UINT16* arg1, UINT16* arg2)){
//jni->call Java implementation
}
// call your lib
MyFunction(int size, int (* callback_link)(UINT16* arg1, UINT16* arg2));
Output of: javap -s SomeClass
public org.someapp.SomeClass$_data data;
Signature: Lorg/someapp/SomeClass$_data;
Definition of SomeClass in Java:
class SomeClass
{
private class _data {
byte[] something = new byte[1234];
}
public _data data;
}
Definition of native function in Java:
public static native int NativeFunction(SomeClass something);
Java implementation:
SomeClass x = new SomeClass();
NativeInterface.NativeFunction(x);
However, when the following code is executed:
JNIEXPORT jint JNICALL Java_org_someapp_NativeInterface_NativeFunction(JNIEnv* env, jobject obj, jobject someobject) {
jclass some_class = (*env)->GetObjectClass(env, someobject);
jfieldID data = (*env)->GetFieldID(env, some_class, "data", "Lorg/someapp/SomeClass$_data");
}
Java throws a "NoSuchFieldError;: data" exception on the GetFieldID call. I don't get it.. The signature is just fine (copied straight from javap).
Note that getting the field ID for a simpler variable, like an unsigned short (with signature "S") works just fine.
I have a feeling that "Lorg/someapp/SomeClass$_data" should be "Lorg/someapp/SomeClass$_data;". Note the semicolon.