LoginSignup
0
2

More than 1 year has passed since last update.

fbjni を使ってみる (1): ビルド設定と簡単なメソッド

Posted at

はじめに

Java と C++ を連携させるためには JNI を使うのが基本だけど、あまり使いやすいものではない。これを少し簡単にするために Meta が fbjni というライブラリを公開している。使ってみたところ、次の点が便利に感じた。

  • C++ から Java のメソッドを呼ぶときに、長い Java のメソッドシグニチャ文字列を書く必要がない。
  • Java から C++ の関数を呼ぶときに Java のパッケージ名、クラス名、メソッド名をくっつけた長い C++ 関数名書く必要がない。
  • Java の String と C++ の std::string の変換を自動的にやってくれる。
  • 一部を Java, 一部を C++ で実装した様なハイブリッドクラスというものを作れ Java と C++ の密な連携がやりやすい。
  • std::function を内包した Java のオブジェクトを C++ で作って Java に渡しておいて、後で Java から C++ にコールバックできる。

ドキュメント はしっかり書いてある部分と全く書いてない部分があるので、何回かに分けて解説してみる。初回は Android で使う場合のビルド設定をして文字列を返す関数を C++ で書いて Java から呼んでみる。以後、Android で使う場合の話しかしないが fbjni 自体に Android への依存はない。

なお Meta 製というと、Meta の都合ですぐに deprecated になったりするのではと心配になるかもしれないが Android 版 ReactNative の実装にしっかり使われているのでしばらくは大丈夫そう。

環境

  • Android Studio 2022.1.1
  • fbjni 0.3.0
  • macOS Ventura 13.1

プロジェクト作成

NDK を使って Java(Kotlin) から C++ を呼び出すので "Native C++" テンプレートでプロジェクトを作る。

スクリーンショット 2023-01-29 17.19.45.png

プロジェクト名などは適当に。

スクリーンショット 2023-01-29 17.21.08.png

スクリーンショット 2023-01-29 17.22.05.png

"Native C++" テンプレートで作ったプロジェクトは NDK を使うための設定が完了していて JNI を使って Java(Kotlin) から C++ を呼び出す簡単なサンプルになっている。ここでその内容を確認しておく。

  • app/build.gradleexternalNativeBuild ブロックによって CMake が起動される。
  • CMake は app/src/main/cpp/CMakeLists.txt の内容に従って app/src/main/cpp/native-lib.cpp をコンパイルして libfbjniapplicatioin1.so 共有ライブラリをビルドする。
  • native-lib.cppjstring Java_com_example_fbjniapplicatioin1_MainActivity_stringFromJNI(JNIEnv*,jobject) という関数を定義している。この関数は Java の文字列オブジェクトを生成して返す。この関数は Java(Kotlin) からは MainActivity クラスの stringFromJNI として呼び出せる。
  • MainActivity クラスは Kotlin のコンパニオンオブジェクトを使ってクラスのロード時に libfbjniapplicatioin1.so をロードする。そして onCreate 時に stringFromJNI を呼んで C++ から受け取った文字列を表示する。

今回は JNI を直接使用している C++ の stringFromJNI の実装を fbjni を使ったものに置き換えていく。

ビルド設定

fbjni の追加

まずは依存先に fbjni を追加する。

--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,6 +39,9 @@ android {
     buildFeatures {
         viewBinding true
     }
+    dependencies {
+        implementation 'com.facebook.fbjni:fbjni:0.3.0'
+    }
 }

 dependencies {

prefab の有効化

fbjni を使う場合は C++ の共有オブジェクト(今回の場合は libfbjniapplicatioin1.so のこと) に fbjni の C++ のライブラリをリンクする必要がある。その設定を簡単に行うための prefab という仕組みを有効にする。

diff --git a/app/build.gradle b/app/build.gradle
index ec46e05..3e5e64b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -17,6 +17,9 @@ android {
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }

+    buildFeatures {
+        prefab true
+    }
     buildTypes {
         release {
             minifyEnabled false

C++ ランタイムの変更

CMake のデフォルト設定では C++ ランタイムとして c++_static が使われる。 これを c++_dynamic に変更する。

diff --git a/app/build.gradle b/app/build.gradle
index 3e5e64b..e76c4f3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -15,6 +15,12 @@ android {
         versionName "1.0"

         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+        externalNativeBuild {
+            cmake {
+                arguments "-DANDROID_STL=c++_shared"
+            }
+        }
     }

     buildFeatures {

fbjni の C++ のライブラリのリンク

CMake の設定を変更して C++ の共有ライブラリに fbjni の C++ のライブラリをリンクする。prefab を有効にしたおかげでこのステップが簡単になっている。

diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt
index f4f7ac0..3a9f03c 100644
--- a/app/src/main/cpp/CMakeLists.txt
+++ b/app/src/main/cpp/CMakeLists.txt
@@ -36,6 +36,8 @@ find_library( # Sets the name of the path variable.
         # you want CMake to locate.
         log)

+find_package(fbjni REQUIRED CONFIG)
+
 # Specifies libraries CMake should link to your target library. You
 # can link multiple libraries, such as libraries you define in this
 # build script, prebuilt third-party libraries, or system libraries.
@@ -45,4 +47,5 @@ target_link_libraries( # Specifies the target library.

         # Links the target library to the log library
         # included in the NDK.
-        ${log-lib})
\ No newline at end of file
+        ${log-lib}
+        fbjni::fbjni)

fbjni への移行

stringFromJNI の書き換え

JNI では Java の static native (Kotlin だと external) メソッドを実装する C++ 側の関数をパッケージ名とクラス名とメソッド名を含んだ長い名前の external "C" リンケージの C 言語の関数として実装していた。fbjni ではこれを次の様なクラスの static メンバ関数として native-lib.cpp に実装する。

native-lib.cpp
#include <fbjni/fbjni.h>

class MainActivity : public facebook::jni::JavaClass<MainActivity> {
public:
    static constexpr auto kJavaDescriptor = "Lcom/example/fbjniapplicatioin1/MainActivity;";

    static std::string stringFromJNI(facebook::jni::alias_ref<facebook::jni::JClass> clazz) {
        return std::string{"Hello from fbjni"};
    }
};

fbjni は kJavaDescriptor 定数によってこのクラスが Java の com.example.fbjniapplicatioin1.MainActivity クラスの static native メソッドを実装することを知る。C++ のクラス名は分かりやすさのため Java のクラス名と揃えてあるが必ずしもそうする必要はない。そして jstring ではなく std::string を返すことができる!

stringFromJNI を Java VM へ登録する

ここまでではまだ fbjni は Java の MainActivigy#stringFromJNI と C++ の MainActivity::stringFromJNI が対応していることを知らない。同じ名前のものを自動的に対応づけてくれたりはしない。fbjni では明示的に登録して関連付けをする必要がある。そのための関数を追加して、C++ の MainActivity クラスは完成。

native-lib.cpp
#include <fbjni/fbjni.h>

class MainActivity : public facebook::jni::JavaClass<MainActivity> {
public:
    static constexpr auto kJavaDescriptor = "Lcom/example/fbjniapplicatioin1/MainActivity;";

    static std::string stringFromJNI(facebook::jni::alias_ref<facebook::jni::JClass> clazz) {
        return std::string{"Hello from fbjni"};
    }

    static void registerNatives() {
        javaClassStatic()->registerNatives({
            makeNativeMethod("stringFromJNI", MainActivity::stringFromJNI)
        });
    }
};

javaClassStatic は親クラスの関数、makeNativeMethod はマクロなので名前空間修飾はいらない。

この MainActivity::registerNativesJNI_Onload を利用してこの共有ライブラリがロードされるとき、すなわち Java(Kotlin) の MainActivity クラスがロードされ、そのコンパニオンオブジェクトによって libfbjniapplicatioin1.so がロードされるときに実行されるようにする。これを加えて、もともとあった jstring Java_com_example_fbjniapplicatioin1_MainActivity_stringFromJNI(JNIEnv*,jobject) を削除して native-lib.cpp は次のようになる。

native-lib.cpp
#include <jni.h>
#include <string>
#include <fbjni/fbjni.h>

class MainActivity : public facebook::jni::JavaClass<MainActivity> {
public:
    static constexpr auto kJavaDescriptor = "Lcom/example/fbjniapplicatioin1/MainActivity;";

    static std::string stringFromJNI(facebook::jni::alias_ref<facebook::jni::JClass> clazz) {
        return std::string{"Hello from fbjni"};
    }

    static void registerNatives() {
        javaClassStatic()->registerNatives({
            makeNativeMethod("stringFromJNI", MainActivity::stringFromJNI)
        });
    }
};

extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    return facebook::jni::initialize(vm, [] {
        MainActivity::registerNatives();
    });
}

これを実行して MainActivity に "Hello from fbjni" と表示されれば成功。native-lib.cpp 自体は fbjni 導入前より大きくなってしまったが、stringFromJNI の実装はずっとシンプルになった。また簡単にするため C++ の MainActivity クラスはこのファイルの中に実装したが、もちろん別ファイル(.cpp/.h)に分けても良い。

プロジェクトファイルは https://github.com/yamoridon/FBJNIApplicatioin1 にあります。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2