🙆

Androidアプリ開発: 複雑なC++関数のJNI呼び出し

2024/07/21に公開

はじめに

Androidアプリ開発においてJNI(Java Native Interface)を使用することで、C++をJavaやKotlinから利用することが可能です。ここでは、stable-diffusion.cppの具体的なC++関数を例にJNIを通じてKotlinから呼び出す方法を解説します。

C++関数の定義

以下に示すのは、sd_ctx_t*型のポインタを返すC++関数です。この関数は、様々なパラメータを受け取り、画像生成のためのコンテキストを設定します。ここではパラメータの意味などは理解する必要はありません。複数の文字列パラメータと数値、およびブール値を受け取り、何らかの処理を行った後に整数値を返します。

 sd_ctx_t* new_sd_ctx(const char* model_path,
                            const char* vae_path,
                            const char* taesd_path,
                            const char* control_net_path_c_str,
                            const char* lora_model_dir,
                            const char* embed_dir_c_str,
                            const char* stacked_id_embed_dir_c_str,
                            bool vae_decode_only,
                            bool vae_tiling,
                            bool free_params_immediately,
                            int n_threads,
                            enum sd_type_t wtype,
                            enum rng_type_t rng_type,
                            enum schedule_t s,
                            bool keep_clip_on_cpu,
                            bool keep_control_net_cpu,
                            bool keep_vae_on_cpu);

JNIメソッドの宣言と実装

このC++関数をKotlinからアクセス可能にするために、次のようなJNIメソッドを宣言し実装します。

extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_myapp_MainActivity_newSdCtx(JNIEnv *env, jobject obj, 
    jstring modelPath, jstring vaePath, jstring taesdPath, jstring controlNetPath,
    jstring loraModelDir, jstring embedDir, jstring stackedIdEmbedDir,
    jboolean vaeDecodeOnly, jboolean vaeTiling, jboolean freeParamsImmediately,
    jint nThreads, jint wtype, jint rngType, jint schedule,
    jboolean keepClipOnCpu, jboolean keepControlNetCpu, jboolean keepVaeOnCpu) {

    const char* model_path = env->GetStringUTFChars(modelPath, nullptr);
    // Repeat for other strings...

    sd_ctx_t* ctx = new_sd_ctx(model_path, /* other params */);
    env->ReleaseStringUTFChars(modelPath, model_path);
    // Repeat for other strings...

    return reinterpret_cast<jlong>(ctx);
}

Kotlinからの関数呼び出し

Kotlin側でこのJNI関数を呼び出すために、以下のように宣言します。

class MainActivity : AppCompatActivity() {
    external fun newSdCtx(modelPath: String, vaePath: String, taesdPath: String,
                          controlNetPath: String, loraModelDir: String, embedDir: String,
                          stackedIdEmbedDir: String, vaeDecodeOnly: Boolean, vaeTiling: Boolean,
                          freeParamsImmediately: Boolean, nThreads: Int, wtype: Int,
                          rngType: Int, schedule: Int, keepClipOnCpu: Boolean,
                          keepControlNetCpu: Boolean, keepVaeOnCpu: Boolean): Long

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // ここで newSdCtx を呼び出す
    }
}

C++コードからLogcat出力

AndroidのC++コード(NDKを使用したネイティブコード)からLogcatにログを出力するには、Androidのログシステムである __android_log_print 関数を使用します。これにより、JavaやKotlinでの Log クラスと同様に、ログを出力することが可能です。

まず、C++のビルド設定で liblog ライブラリへのリンクを追加する必要があります。具体的には、CMakeLists.txt でこの設定を行います。

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that CMake needs to locate.
              log )

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library included in the NDK.
                       ${log-lib} )

ログを出力するためには、android/log.h ヘッダファイルをインクルードする必要があります。このヘッダはNDKに含まれており、ログ関連のマクロと関数が定義されています。その後、ログを出力するには __android_log_print 関数を使用します。

#include <android/log.h>

__android_log_print(ANDROID_LOG_INFO, "MyAppTag", "Processing completed.");
__android_log_print(ANDROID_LOG_ERROR, "MyAppTag", "Failed with error: %s", errorMessage);

より簡単にログを出力するために、マクロを定義して使用することが一般的です。以下のように定義できます:

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MyAppTag", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MyAppTag", __VA_ARGS__)
LOGI("Starting processing");
LOGE("Failed with error: %s", errorMessage);

まとめ

C++で定義された複雑な関数をKotlinからJNIを通じて呼び出す方法を説明しました。これにより、アプリのパフォーマンスを向上させることができ、より複雑な操作を効率的に処理することが可能になります。

Discussion