AndroidでネイティブからJavaを呼び出す


Table of Contents

1 ネイティブコードからJavaメソッドを呼ぶ

以下の手順でJavaメソッドを呼び出します。

1. FindClassでクラスを取得する2. GetMethodIDでメソッドIDを取得する3. Call[戻り値の型]Methodでメソッドを呼び出す

JNIのインターフェースはJNIEnvのメンバ関数ポインタ経由で呼び出します。

(*env)->Func(env, …)

android-17の場合は以下の場所にJNIインターフェースが定義されています。

ndk/platforms/android-17/arch-arm/usr/include/jni.h

1.1 FindClassで指定するクラス名

com.example.android.MainActivityというように、パッケージ名とクラス名を指定する必要があります。

加えて、.を/に置き換える必要があります。

(*env)->FindClass(env, “com/example/android/MainActivity”);

1.2 GetMethodIDにメソッドのシグネチャを指定

GetMethodIDはクラスとメソッド名だけでなく、メソッドのシグネチャを指定する必要があります。

(*env)->GetMethodID(env, [FindClassで取得したクラス], “helloWorld”, “()V”);

上記の”()V”がシグネチャです。

シグネチャはメソッドの引数と戻り値から規則的に生成されるものです。

 

この規則を覚えていれば問題ないのですが、忘れてしまった場合(というか覚えるのがしんどい)にjavapコマンドを用いることで、メソッドのシグネチャを確認できます。

$ javap -s -classpath bin/classes com.example.android.MainActivityPicked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8Compiled from “MainActivity.java”public class com.example.android.MainActivity extendsandroid.app.Activity { public static final java.lang.String gTAG; Signature: Ljava/lang/String; public com.example.android.MainActivity(); Signature: ()V public void onCreate(android.os.Bundle); Signature: (Landroid/os/Bundle;)V public void helloWorld(); Signature: ()V}

1.3 Call[戻り値の型]Methodについて

メソッド名とシグネチャからメソッドIDを取得したので、すでに戻り値がメソッドIDで判別できそうですが、Call[戻り値]Methodoで戻り値の型を明示した関数呼び出しが必要になります。

以下はその一部です。

void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, …);void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);

Method、MethodV、MethodAはJavaメソッドへの引数の渡し方が異なるだけです。

1.4 ネイティブコードからJavaメソッドを呼び出すコード

Java側のコードは以下の通りです。

package com.example.android;import android.app.Activity;import android.os.Bundle;import android.util.Log;public class MainActivity extends Activity{ public static final String gTAG = “hello”; private native void callFromNative(); static { System.loadLibrary(“hello”); } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); callFromNative(); } public void helloWorld() { Log.d(gTAG, “Hello, World”); }}

ネイティブ側のコードは以下の通りです。

callFromNative関数からhelloWorldメソッドを呼び出します。

#include <stdio.h>#include <com_example_android_MainActivity.h>#include <android/log.h>#define JNI_METHOD_NAME(name) Java_com_example_android_MainActivity_##name#define JNI_DEFINE_METHOD(type, name, args…) JNIEXPORT type JNICALL JNI_METHOD_NAME(name)(JNIEnv *env, jobject object, ##args)#define JNI_GET_ENV() env#define JNI_GET_OBJECT() object#define JNI_CALL(name, args…) (*env)->name(env, ##args)#define JNI_CALL_METHOD(name, id, args…) (*env)->name(env, object, id, ##args)#define JNI_LOG(args…) __android_log_print(ANDROID_LOG_INFO, “hello”, ##args)#define JAVA_CLASS_NAME “com/example/android/MainActivity”JNI_DEFINE_METHOD(void, callFromNative){ jclass c = JNI_CALL(FindClass, JAVA_CLASS_NAME); jmethodID id = JNI_CALL(GetMethodID, c, “helloWorld”, “()V”); if (id == 0) { JNI_LOG(“cannot find method helloworld ()V”); return; } JNI_CALL_METHOD(CallVoidMethod, id);}

2 ネイティブスレッドからJavaメソッドを呼ぶ

Javaのスレッドからではなく、ネイティブプログラムを開始した場合、以下の手順でJavaメソッドを呼び出します。

1. JavaVMを作成する2. AttachCurrentThreadでJavaVMにアタッチする3. FindClassでクラスを取得する4. GetMethodIDでメソッドIDを取得する5. Call[戻り値の型]Methodでメソッドを呼び出す6. DetachCurrentThreadでJavaVMをデタッチする7. JavaVMを破棄する

Androidの場合、MainActivity経由でネイティブ関数を呼ぶので、1と2の処理が必要ありません。

ところがMainActivity経由呼ばれたネイティブ関数内で別のネイティブスレッドを作成した場合は異なり、2のAttachCurrentThreadが必要になります。

 

AttachCurrentTh
readはJavaVM構造体のメンバ関数ポインタとして定義されています。

typedef const struct JNIInvokeInterface* JavaVM;<snip>struct JNIInvokeInterface {<snip> jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);<snip>};

JavaVMはJNIEnv構造体のメンバ関数ポインタGetJavaVMから取得できます。

jint (*GetJavaVM)(JNIEnv*, JavaVM**);

さらにjobjectをネイティブスレッドから参照できるようにJNIEnv構造体のメンバ関数ポインタNewGlobalRefを使用します。

NewGlobalRefを用いることでjobjectがグローバル参照可能になります。NewGlobalRefしたjobjectはDeleteGlobalRefで解放されます。

jobject (*NewGlobalRef)(JNIEnv*, jobject);void (*DeleteGlobalRef)(JNIEnv*, jobject);

以上を踏まえ、以下の手順でネイティブスレッドからJavaメソッドを呼び出します。

1. Javaからネイティブ関数を呼び出す2. ネイティブ関数でFindClassとGetMethodIDで取得したメソッドIDを 外部変数に保存する3. ネイティブ関数でNewGlobalRefしたjobjectを外部変数に保存する4. ネイティブ関数でGetJavaVMしたJavaVMを外部変数に保存する5. pthread_createでネイティブスレッドを生成する6. 生成されたネイティブスレッドにて、外部変数のJavaVMを AttachCurrentThreadして、JNIEnvを取得する7. 生成されたネイティブスレッドにて、外部変数のメソッドIDと jobjectからJavaメソッドを呼び出す8. 生成されたネイティブスレッドにて、NewGlobalRefしたjobjectを DeleteGlobalRefで削除する9. 生成されたネイティブスレッドにて、AttachCurrentThreadしたJavaVM をDetachCurrentThreadでデタッチする

2.1 サンプルプログラム

Javaのコードは以下の通りです。

package com.example.android;import android.app.Activity;import android.os.Bundle;import android.util.Log;public class MainActivity extends Activity{ public static final String gTAG = “hello”; private native void callFromNative(); private native void callFromNativeThread(); static { System.loadLibrary(“hello”); } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); callFromNative(); callFromNativeThread(); } public void helloWorld() { Log.d(gTAG, “Hello, World”); }}

ネイティブのコードは以下の通りです。

#include <stdio.h>#include <com_example_android_MainActivity.h>#include <android/log.h>#include <pthread.h>#define JNI_METHOD_NAME(name) Java_com_example_android_MainActivity_##name#define JNI_DEFINE_METHOD(type, name, args…) JNIEXPORT type JNICALL JNI_METHOD_NAME(name)(JNIEnv *env, jobject object, ##args)#define JNI_GET_ENV() env#define JNI_GET_OBJECT() object#define JNI_CALL(name, args…) (*env)->name(env, ##args)#define JNI_CALL_METHOD(name, id, args…) (*env)->name(env, object, id, ##args)#define JNI_ATTACH(env) (*gJavaVM)->AttachCurrentThread(gJavaVM, &env, NULL);#define JNI_DETACH() (*gJavaVM)->DetachCurrentThread(gJavaVM)#define JNI_LOG(args…) __android_log_print(ANDROID_LOG_INFO, “hello”, ##args)#define JAVA_CLASS_NAME “com/example/android/MainActivity”static JavaVM *gJavaVM;static jobject gObject;static jmethodID gMethodID;static pthread_t gThread;JNI_DEFINE_METHOD(void, callFromNative){ jclass c = JNI_CALL(FindClass, JAVA_CLASS_NAME); jmethodID id = JNI_CALL(GetMethodID, c, “helloWorld”, “()V”); if (id == 0) { JNI_LOG(“cannot find method helloworld ()V”); return; } JNI_CALL_METHOD(CallVoidMethod, id);}static void *runNativeThread(void *data){ JNIEnv *env; jobject object = gObject; JNI_ATTACH(env); JNI_CALL_METHOD(CallVoidMethod, gMethodID); JNI_CALL(DeleteGlobalRef, gObject); JNI_DETACH(); return NULL;}JNI_DEFINE_METHOD(void, callFromNativeThread){ jclass c; int retval; c = JNI_CALL(FindClass, JAVA_CLASS_NAME); gMethodID = JNI_CALL(GetMethodID, c, “helloWorld”, “()V”); gObject = JNI_CALL(NewGlobalRef, JNI_GET_OBJECT()); JNI_CALL(GetJavaVM, &gJavaVM); retval = pthread_create(&gThread, NULL, runNativeThread, NULL); if (retval < 0) JNI_LOG(“pthread_create error”);}

実行するとlogcatで以下の出力が得られます。

06-11 00:37:25.315: D/hello(7555): Hello, World06-11 00:37:25.319: D/hello(7555): Hello, World

Android | Linux | SDL - Narrow Escape