C++
Android
JNI

久しぶりにJNIを触って感動した時のメモ

久しぶりにJNIを触ってみたらだいぶやりやすくなっていたので感動しました :sparkles:

メモがてらツラツラ書いてみました。


そもそもJNIとは?


Java Native Interface (JNI) Javaプラットフォームにおいて、Javaで記述されたプログラムと、他のプログラミング言語(たとえばCやC++など)で書かれた、実際のCPU上で動作するコード(ネイティブコード)とを連携するためのインタフェース仕様である


Wikipedia より


環境構築

大まかには以下の手順になります。


  1. jni用のディレクトリ作成

  2. Kotlin/Java側のネイティブ呼び出し用のクラスを作る

  3. CMakeLists.txtを作成

  4. ネイティブ側実装


  5. app/build.gradleに設定追加

CMakeLists.txt を作成してbuild.gradleに記述するだけで

コンパイルして使えるとは。。おじさん感動しました :sparkles:

(昔はAndroid.mk作ってだねえ。。)

1. jni用のディレクトリ作成

デフォルトだと src/main/cpp が使われているっぽいが、

分かりやすければどこでも良いかと個人的には思います。

今回は src/main/java/xxxxx/jni を作成しました。

2. Kotlin/Java側のネイティブ呼び出し用のクラスを作る

例としてネイティブから hello の文字列を返すメソッドを持ったクラスを作成してみます。


jni/SampleNative.kt

class SampleNative {

external fun hello(): String

companion object {
init {
System.loadLibrary("sample-native")
}
}
}


↑で指定した sample-native は作成される共有ライブラリ名を指定してます。

3. CMakeLists.txtを作成


jni/CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

add_library(
sample-native
SHARED
sample-native.cpp)

find_library(
log-lib
log)

target_link_libraries(
sample-native
${log-lib})


4. ネイティブ側実装


jni/sample-native.cpp

#ifndef __SAMPLE_NATIVE_H_

#define __SAMPLE_NATIVE_H_

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

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL
Java_xxx_xxx_xxx_xxx_jni_SampleNative_hello(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

#ifdef __cplusplus
}
#endif

#endif // __SAMPLE_NATIVE_H_


Java_xxx_xxx_xxx_xxx_jni_SampleNative_helloxxx にはパッケージ名が入ります。

5. app/build.gradleに設定追加


app/build.gradle

android {

defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake { path "src/main/java/xxx/jni/CMakeLists.txt" }
}
}

これで晴れてビルドできるようになりました :tada:

使う時には普通のメソッドを呼び出す様に使えます。

val sample = SampleNative()

sample.hello() // Hello from C++


Tips

実装中に個人的に便利だと思ったものをピックアップしました。



  • Log出力に関して

    #define TAG               "SampleNative"
    
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)

    // LOGD("numbers %d %d", num1, num2);

    ↑の様にdefineしておくと便利




  • オブジェクト生成

    #if defined(DEBUG) || defined(_DEBUG)
    
    #define __ASSERT__(e) assert(e)
    #else
    #define __ASSERT__(e)
    #endif // DEBUG

    /**
    * 引数なしコンストラクタを呼び出しクラスオブジェクトを生成<br>
    *
    * @param env JNIEnv
    * @param classPath クラスパス
    * @return jobject
    */

    jobject newObject(JNIEnv *env, const char *classPath)
    {
    jclass cls = env->FindClass(classPath);
    __ASSERT__(cls != NULL);
    // コンストラクタ取得
    jmethodID mid = env->GetMethodID(cls, "<init>", "()V");
    __ASSERT__(mid != NULL);
    jobject jobj = env->NewObject(cls, mid);
    // 後片付け
    env->DeleteLocalRef(cls);
    return jobj;
    }

    /**
    * java.util.ArrayListクラスのインスタンス取得
    *
    * @param env JNIEnv
    * @return インスタンス
    */

    jobject newArrayList(JNIEnv *env)
    {
    return newObject(env, "java/util/ArrayList");
    }

    JNI側でJava/Kotlin側オブジェクト生成はよく使うので↑の様にメソッド作っておくと便利




バッドノウハウ

JUnitができない!



  • Android JUnit Testing for Native Libraries

    どうしてもJUnitだとホストのPC環境のアーキテクチャでビルドするので

    UnsatisfiedLinkError が発生してしまう。

    頑張って↑のリンク先の様にビルドするか、instrumentedテストでやる




参考URL