QT and JAVA Native Interface

QT and JAVA Native Interface

WHAT IS JAVA NATIVE INTERFACE?

 

Java Native Interface (JNI) is a neat thing. It allows running Java code from native applications and libraries written in other languages.

Qt has provided several classes which make using JNI pretty straightforward, amongst which we will single out QAndroidJniEnvironment and QAndroidJniObject class. This article will mostly concentrate on how to use the QAndroidJniObject class which provides APIs to call Java code from C++,which can be very useful if you want to use both Qt and Android classes for Android development. For example – you define a C++ class which instantiates a Java class that registers some Android sensors, gathers data from them and returns it back to the C++ class which can then display that data in a convenient way using QML.

 

 

RUNNING JAVA CODE FROM C++

 

At this point I will assume that you have written the required Java class. To use it from the C++ class, put the .java file in the subfolder path /src/com/mycompanyname/myappname starting from the location of AndroidManifest.xml. Furthermore, add the Android Extras module in the project file:

 

QT += androidextras
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
DISTFILES += android/AndroidManifest.xml
OTHER_FILES += android/src/com/mycompanyname/myappname/myjavaclass.java

 

Before I get into details on how to setup the interface, there are a few important points that have to be covered:

  •  always use fully-qualified class names, such as “java/lang/Class”
  •  method signatures are structured as (A)R, where A is the argument type and R is the return type*
  •  all object types are returned as a QAndroidJniObject

 

Let’s get started then – to initialize a Java class use the following syntax:

 

// Instantiate the Java class 
QAndroidJniObject m_javaClass = QAndroidJniObject ("com/mycompanyname/myappname/myjavaclassname");

In some cases, it is very useful to be able to use the main activity of the application or the application context inside the Java class. To get them, use the appropriate functions from the QtAndroid namespace and send them as arguments to the Java class constructor:

 

// Instantiate the java class, send current application context and activity as arguments to the constructor 
m_javaClass = QAndroidJniObject ("com/mycompanyname/myappname/myjavaclassname",
                                 "(Landroid/content/Context;Landroid/app/Activity;)V", 
                                 QtAndroid::androidContext().object<jobject>(), 
                                 QtAndroid::androidActivity().object<jobject>());

Congratulations, the Java class is now up and running, but what if you want to call a specific function inside that class? To call a function in Java class that takes no arguments and does not return anything, use:

 

m_javaClass.callMethod<void>("javafunctionname");

To call some more complex functions, you need to supply the correct function signature*. It is important to note that array types in the signature have the ‘[‘ suffix and fully-qualified types have the ‘L’ prefix and ‘;’ suffix.

 

For example, to call a function in Java class that takes a string argument and returns an integer, use:

 

jint integerVariable = m_javaClass.callMethod<jint>("javafunctionname", "Ljava/lang/String;)I", 
                                                    "stringargument");

 

CALLBACK A C++ FUNCTION FROM JAVA

 

That’s it, you just learned how to run your Java code from C++ using some Qt classes. But wait, let’s turn things over a bit. What if you want to call native C++ code from Java? To do that, create a corresponding function declaration in Java and prefix it with the native keyword. Besides that, you need to map the Java native function to a function in your C++ code. Luckily, there is a useful function called RegisterNatives() which can be used through the QAndroidJniEnvironment object.

First declare an array of JNI native functions that reside in the Java class:

 

// An array of JNI native methods in java code 
JNINativeMethod methods[] = {
       {"firstNativeFunction", "(DDLjava/lang/String;FI)V", reinterpret_cast<void*>(cppFunctionOne)},
       {"secondNativeFunction", "(I)V", reinterpret_cast<void*>(cppFunctionTwo)}
};

Here, firstNativeFunction and secondNativeFunction represent native functions in Java code:

 

public native void firstNativeFunction (double a, double b, String c, float d, int e);

public native void secondNativeFunction(int f);

and cppFunctionOne and cppFunctionTwo are static C++ functions:

 

static void cppFunctionOne (JNIEnv  *env, jobject obj, jdouble a, jdouble b, jstring c, jfloat d, 
                            jint e) {
  qDebug() << "Inside first C++ function";
}

static void cppFunctionTwo (JNIEnv *env, jobject obj, jint f) {
  qDebug() << "Inside second C++ function";
}

Register them in the following way:

 

QAndroidJniEnvironment env;
jclass objectClass = env->GetObjectClass(m_javaClass.object<jobject>());

env->RegisterNatives(objectClass, methods, sizeof (methods) / sizeof(methods[0]));
env->DeleteLocalRef(objectClass);

Because most objects received from Java are local references, they are only valid in the scope you received them. It is good practice to delete that reference after the registration is complete. And that’s it, you can now run your Java code from C++ and vice versa without too much hassle.

 

*Here is a list of tables containing specific JNI types from the official Qt documentation:

 

JNI object types:

Type Signature
jobject Ljava/lang/Object;
jclass Ljava/lang/Class;
jstring Ljava/lang/String;
jthrowable Ljava/lang/Throwable;
jobjectArray [Ljava/lang/Object;
jarray [<type>

 

 

 

 

 

 

JNI primitive types:

Type Signature
jboolean Z
jbyte B
jchar C
jshort S
jint I
jlong J
jfloat F
jdouble D

 

 

 

 

 

 

 

 

Other types:

Type Signature
void V
Custom type L<fully-qualified-name>;

Marko Jovic
No Comments

Post a Comment

Comment
Name
Email
Website