Qt C++からJava(Android VM)を制御する
Controll Java(Android VM) from Qt C++
Androidアプリは基本的にJava言語やそれをもとにしたKotlin言語で開発するのが前提で、Andoid NDKよりもAndroid SDKのほうが簡単に実装できることが多かったりします。Android版QtはNDKによるC++言語が用いられていますが、もちろんJavaコードとのやりとりが可能です。
C++とJavaを相互に連携するにはJNI(Java Native Interface)を使用します。JNIには特有の決まり事や癖が多いので、細かいことは
Android NDKプログラミングの専門書に任せるとして、ここではQtプログラミングという観点からテクニックをご紹介します。
Qtには「androidextras」というAndroid拡張ライブラリが本体とは別に提供されており、"pro"ファイルに宣言を追加することで、JNIのラッパークラスを使用できるようになります。
android{
QT += androidextras
}
では、JniTest.javaというJavaクラスを例に、Qtと連携する実例を記載していきます。
JniTest.java
package com.tnksoft.helloqt;
import java.io.*;
import java.net.*;
import android.util.Log;
public class JniTest
{
private native void error(long handle, String errmsg);
private native void complete(long handle, byte[] result);
private long _handle;
public JniTest(long handle)
{
_handle = handle;
}
public static boolean Initialize(int v)
{
Log.d("QTJ", "initialized.");
return true;
}
public void begin()
{
Log.d("QTJ", "begin was called.");
}
public void download(String path)
{
byte[] result = new byte[256];
complete(_handle, null);
}
}
JNIインターフェイス「JNIEnv」を取得するには「
QAndroidJniEnvironment」を使用します。jclassを扱うには「
QAndroidJniObject」を作成し、QAndroidJniEnvironmentオブジェクトを指定した上で、クラスやメソッドIDを確保します。
createclass.cpp
// JNI関連のファイルをインクルード
#include <QtAndroidExtras/QAndroidJniObject>
#include <QtAndroidExtras/QAndroidJniEnvironment>
void MainView::MainView()
{
QAndroidJniEnvironment env;
// Javaクラス名の定義
const char* JniTestClassName = "com/tnksoft/helloqt/JniTest";
// JniTestクラスのオブジェクトを作成する。第二引数以降はコンストラクタに渡すためのもので、
// 引数にはこのクラスのポインタを64bit整数値に変換したものを指定している。
JniTestClass = new QAndroidJniObject(JniTestClassName, "(J)V", reinterpret_cast<jlong>(this));
// クラスに登録されているメソッドのIDを取得して、メンバ変数として記録しておく。
testClass = env->GetObjectClass(JniTestClass->object());
_begin = env->GetMethodID(testClass, "begin", "()V");
_download = env->GetMethodID(testClass, "download", "(Ljava/lang/String;)V");
// Javaから呼び出されたときに実行するC言語関数を登録。
// "メソッド名", "シグニチャ", "C言語関数ポインタ"の配列
JNINativeMethod pmethods[] {
{"error", "(JLjava/lang/String;)V", reinterpret_cast<void *>(MainView::errorCallback)},
{"complete", "(J[B)V", reinterpret_cast<void *>(MainView::completeCallback)},
};
// Qtドキュメントには詳細について言及されていないが、
// これはJNI関数「RegisterNatives」のラッパーである。
env->RegisterNatives(testClass, pmethods, 2);
// 静的メソッドを呼び出す。引数は見本のためのダミー数値
jboolean res = QAndroidJniObject::callStaticMethod<jboolean>(JniTestClassName, "initialize", "(I)Z", 4);
}
void MainView::onCallJni()
{
QAndroidJniEnvironment env;
// 戻り値がない(void)のJavaメソッドを呼び出す
// 例えば、戻り値がintの関数を呼び出したいのなら、callIntMethodを使用する。
env->CallVoidMethod(JniTestClass->object(), _begin);
}
jcharはJavaのchar型であり、UTF16の2バイト文字が格納されるため、QStringに変換するには「QString::fromUtf16」を使用します。
errorcb.cpp
void MainView::errorCallback(JNIEnv *env, jobject thiz, jlong handle, jstring errmsg)
{
Q_UNUSED(thiz);
MainView *nw = reinterpret_cast<MainView*>(handle);
const jchar* c = env->GetStringChars(errmsg, nullptr);
QString msg = QString::fromUtf16(c);
// QMLにシグナルを送信
emit nw->error(msg);
}
逆にQStringからJNIのjstring型に変換するには「
QAndroidJniObject::fromString」を使用します。
fromstr.cpp
QAndroidJniObject jmsg = QAndroidJniObject::fromString("Hello from Qt C++.");
env->CallVoidMethod(JniTestClass->object(), _download, jmsg.object<jstring>());
jbytearrayなどのJNI配列型はVMでの参照先であり、C言語のポインタではないので、適時ポインタとして取得する必要があります。これは、JNIのバイト配列をQt Quickアプリでよく使用されるバイト配列管理クラス「QByteArray」に変換する例です。
compcb.cpp
void MainView::completeCallback(JNIEnv *env, jobject thiz, jlong handle, jbyteArray res)
{
Q_UNUSED(thiz);
MainView *mw = reinterpret_cast<MainView*>(handle);
// nullでなければバイト配列に変換
if(res){
jboolean cpy;
jsize sz = env->GetArrayLength(res);
jbyte *buf = env->GetByteArrayElements(res, &cpy);
QByteArray *ba = new QByteArray(reinterpret_cast<char*>(buf), sz);
env->ReleaseByteArrayElements(res, buf, 0);
// サンプルではこれ以上必要ないので削除している
delete ba;
}
emit mw->complete();
}
こちらからダウンロードできるQtプロジェクトでは、QtActivityを拡張したクラスで表示されたQMLボタンを押すと、C++とJava間でのデータのやりとりが実行されます。
2018/11/14