Michał Kazimierz Kowalczyk

weblog

Necessitas: convenient solution for communication between Qt application and Java


This is my first article about Qt / Necessitas / Android / JNI, so if you will find some error or have some ideas, please, contact me. I didn't use these technologies before. Everything what I present, works with my software configuration: Qt 4.8.0, Necessitas 0.3.4, Android 3.2.

In this article I will present my solution for using Java in Necessitas projects. As far as most of you know, to use Java code in C++ applications and vice-versa, you need to use Java Native Interface (JNI). This technology is used in Necessitas projects and you can find it for example in file qtmain_android.cpp located in: necessitas/Android/Qt/{{VERSION_OF_QT}}/armeabi-v7a/src/android/cpp. In my case, {{VERSION_OF_QT}} is 480. This file is included to your projects during building process.

We can say that qtmain_android.cpp is the most important file for JNI in your project because it contains JNI_OnLoad(.). This function is the only one which is allowed to load any Java class, so this is the only place in your application where you can load these yours. Of course, it's possible to load Java classes in other functions, but you are limited to use only some of these from java.* package.

I found an article (little bit vague for me) which deliver some solution for this problem: How to use Java from Qt/C++ in Necessitas.

There is no code example but fortunately I found this: QT in Android -- Example for accessing the GPS Service.

In both cases, authors are changing qtmain_android.cpp file. This approach has two disadvantages:

  1. qtmain_android.cpp is common for all projects, so all your projects require all classes that you load there,
  2. you can use only one version of constructor of your class,
  3. qtmain_android.cpp is part of Necessitas and it will be probably changed in newer versions.

Let's deal with first two disadvantages. It's hard to create special class which would be good for all possible applications ((-: ) so we need to find another solution. Let's make little changes in qtmain_android.cpp file.

Firstly, add this line before JNI_OnLoad(.) definition (of course outside bodies of functions!):

extern int JavaClassesLoader (JNIEnv* env);

Secondly, put this code into JNI_OnLoad(.) defintion:

    if (!JavaClassesLoader (m_env)){
        __android_log_print (ANDROID_LOG_FATAL, "Qt", "Couldn't register user defined classes!");
        return -1;
    }

Notice, that we use m_env variable so we need to put it below this line:

m_env = uenv.nativeEnvironment;

Thirdly, we need to remove static key-words at this two lines:

static JavaVM *m_javaVM = NULL;
static JNIEnv *m_env = NULL;

And that's all what we need to change in qtmain_android.cpp file.

Now, let's add file which will load our Java classes. In NecessitasQtCreator choose File -> New File or Project -> (Files and Classes) C++ -> C++ Source File -> Choose... Set a Name to JavaClassesLoader.cpp, click Continue and after Done. Fill your file with this code:

#include <jni.h>
#include <android/log.h>

jobject classPointer;

int JavaClassesLoader (JNIEnv* env){
{
const char* className = "pl/ekk/mkk/necessitas/YourClassName";
jclass clazz=env->FindClass(className);
if (!clazz)
{
__android_log_print(ANDROID_LOG_FATAL,"Qt", "Unable to find class '%s'", className);
return JNI_FALSE;
}
jmethodID constr = env->GetMethodID(clazz, "<init>", "()V");
if(!constr) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "Unable to find a constructor for class '%s'", className);
return JNI_FALSE;
}
jobject obj = env->NewObject(clazz, constr);
classPointer = env->NewGlobalRef(obj);
}
return JNI_TRUE;
}

I guess that you already know that to use multiple Java classes in your project, you just need to create other jobject classPointer, copy-paste code of JavaClassesLoader(.) function (without last line!) and change value of constant className.

To use different constructor than version without arguments, you need to set different method signatures (see more here). To set arguments values, just add them to NewObject (.) method.

But where should you place your Java file? In your project folder you have android/src/ branch. This is right localisation for your files. Notice, that your classes are inside packages, so you need to create folder trees corresponding to packages that you are using. After you copy your files you need to add them to Necessitas project (Add Existing Files).

Now we can use your Java classes in your project. Open your C++ filein which you want use JNI, put this four lines at its beginning:

#include <jni.h>
#include <qDebug>
extern JavaVM *m_javaVM;
extern jobject classPointer;

This will give us a way to use variables from previous files. To call JNI functions, you need to use this code:

JNIEnv* env;
if (m_javaVM -> AttachCurrentThread (&env, NULL) < 0){
    qCritical() << "AttachCurrentThread failed";
    return;
}

jclass applicationClass = env -> GetObjectClass (classPointer);

if (applicationClass){
    //Communication between C++ and JAVA!
}

m_javaVM -> DetachCurrentThread ();

That's all! Now you can use your Java classes in more customized way! (-:

I hope that I explained it quite clearly. If your C++ files cannot find variables from different files that we changed, please clean your project (Build -> Clean Project...).

If you still have some problem, I attach small example.

If you want, you can try to use #ifdef, #define and #endif in qtmain_android.cpp to easily turn on and turn off using JavaClassesLoader(.) in your projects.

But... still we have a third disadvantage... I have to admit that I didn't found a good solution for that. Here, I present an effect of my research, I hope that someone will find some good way to deal with it.

It is possible to access to Java Virtual Machine without changing qtmain_android.cpp file (so we don't need to do third step).

The biggest problem is how to use JavaClassesLoader(.) function outside qtmain_android.cpp file, or in another words, outside JNI_OnLoad(.) definition. You can find an explanation of this problem here: FindClass failed.

I tried to use this solutions: Using JNI from a Native Activity, but unfortunately FindClass method can't find java.lang.ClassLoader, so it seems to be useless for us.

Well, for now, that's all. If anyone will find some solution for third disadvantage or want to discuss about my solution for first one, please contact me.

MKK

Hosted on eKK.pl.