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