2013/01/02

AndroidのNative移植について、補足



件の記事[Androidで、目指せ脱ざんねん] ですが、
Native移植をするためにはあの記事の情報だけでは実は不足なのです。
当方の都合により一部説明削っておりました...
すいません...
まとめておきますので、もし必要ならご参照ください...



作法がC++とCで異なる模様

jniで用いるnativeコードの書き方ですが、C++とCで書き方を変えないといけない模様です。
拡張子cでコンパイルの通ったコードも、拡張子cppに変更するととたんにエラーになります...
AndroidのnativeソースコードはほとんどがC++で書かれているため、
以下はC++記述に限った作法の説明になります。
本当は間にVMが入る

記事中で掲示したソフト構成図ですが、もう少し正しく書くとこうなります。
Javaライブラリの下にVM層があります。


ライブラリロード時

カスタムしたライブラリの関数を呼び出す前に、
Java側でライブラリのロード処理が必要です。
Javaで以下の記述を追加します。

・MtpDevice.java  ([app]/src/android/mtp/custom/MtpDevice.java)
static {
System.loadLibrary("mtpcustom");
}
view raw MtpDevice.java hosted with ❤ by GitHub

ライブラリロードの指示を受けて、VMがNativeモジュール(ここではsoファイル生成に使用するソースファイル群)に実装されたJNI_OnLoad関数を呼びます。NativeモジュールはここでJavaから呼び出されるべきメソッドをVMに登録するため。AndroidRuntime::registerNatieMethodsを呼びだします。この関数でメソッドを登録することで初めてJavaからJNI以下の呼び出せるようになります。


実装は以下のようになります。 オリジナルではJNI_OnLoad関数はandroid_mtp_MediaPlayer.cppに実装されていましたが、
移植にあたってこのファイルは不要として移植対象から外したため、
移植するandroid_mtp_MtpDevice.cppに実装しなおしています。
(コピペですが... )

・android_mtp_MtpDevice.cpp
// 元はandroid_media_MediaPlayer.cppに実装されていた関数
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
// 中略
if (register_android_mtp_MtpDevice(env) < 0) {
ALOGE("ERROR: MtpDevice native registration failed");
goto bail;
}
// 中略
}
int register_android_mtp_MtpDevice(JNIEnv *env)
{
// 中略
return AndroidRuntime::registerNativeMethods(env,
"android/mtp/MtpDevice", gMethods, NELEM(gMethods));
}


ここで、第三引数に渡すgMethodsはJNINativeMethod型構造体の配列になります。
今回の場合は以下の内容で渡しています。

static JNINativeMethod gMethods[] = {
{"native_open", "(Ljava/lang/String;I)Z",
(void *)android_mtp_MtpDevice_open},
{"native_close", "()V", (void *)android_mtp_MtpDevice_close},
{"native_get_device_info", "()Landroid/mtp/MtpDeviceInfo;",
(void *)android_mtp_MtpDevice_get_device_info},
{"native_get_storage_ids", "()[I", (void *)android_mtp_MtpDevice_get_storage_ids},
{"native_get_storage_info", "(I)Landroid/mtp/MtpStorageInfo;",
(void *)android_mtp_MtpDevice_get_storage_info},
{"native_get_object_handles","(III)[I",
(void *)android_mtp_MtpDevice_get_object_handles},
{"native_get_object_info", "(I)Landroid/mtp/MtpObjectInfo;",
(void *)android_mtp_MtpDevice_get_object_info},
{"native_get_object", "(II)[B",(void *)android_mtp_MtpDevice_get_object},
{"native_get_thumbnail", "(I)[B",(void *)android_mtp_MtpDevice_get_thumbnail},
{"native_delete_object", "(I)Z", (void *)android_mtp_MtpDevice_delete_object},
{"native_get_parent", "(I)J", (void *)android_mtp_MtpDevice_get_parent},
{"native_get_storage_id", "(I)J", (void *)android_mtp_MtpDevice_get_storage_id},
{"native_import_file", "(ILjava/lang/String;)Z",
(void *)android_mtp_MtpDevice_import_file},
// kassy_kz add start
{"native_do_device_shutter", "()Landroid/mtp/MtpDeviceInfo;",
(void *)android_mtp_MtpDevice_do_device_shutter},
{"native_set_shutter_speed", "(I)Z",
(void *)android_mtp_MtpDevice_set_shutter_speed},
{"native_set_aperture", "(I)Z",
(void *)android_mtp_MtpDevice_set_aperture},
// kassy_kz add end
};


JNINativeMethodについて

JNINativeMethod型構造体のルールはちょっと特殊なのでここで簡単に解説をば...
JNINativeMethod
const char* name Javaから呼んで欲しい関数の名前
const char* signature 関数の引数と戻り値の型
void* fnPtr 実際に呼び出すNaive関数の関数ポインタ

特殊なのは第二引数。文字列リテラルをぶっこむことで引数・戻り値の型を指定しますが、
指定の仕方が特殊です。

文字列リテラルの括弧の中に引数の型、括弧の後ろに戻り値の型を指定します。
型の指定の仕方は以下のとおり
文字 型(Javaから) 型(Nativeから)
I Int型 jint
J long型 jlong
Z boolean型 jboolean
V void型 void
F float型 jfloat
[B (直後の文字Bの)バイト配列 jbytearray
Ljava/lang/String(L + クラス名) java.lang.String型 jobject


機能呼び出し時

上記登録作業を済ませれば、Java側から無事呼び出すことが可能になります。
ここから先は先の記事で紹介したとおりです。

・Java部
public MtpDeviceInfo doDeviceShutter() {
return native_do_device_shutter();
}
view raw MtpDevice.java hosted with ❤ by GitHub
・JNI部
// シャッターをきるコマンド、 android_mtp_MtpDevice_get_device_infoをほぼまるパク
static jobject
android_mtp_MtpDevice_do_device_shutter(JNIEnv *env, jobject thiz)
{
MtpDevice* device = get_device_from_object(env, thiz);
if (!device) {
ALOGD("android_mtp_MtpDevice_get_device_info device is null");
return NULL;
}
MtpDeviceInfo* deviceInfo = device->doDeviceShutter();
if (!deviceInfo) {
ALOGD("android_mtp_MtpDevice_get_device_info deviceInfo is null");
return NULL;
}
env->NewObject(clazz_deviceInfo, constructor_deviceInfo);
return null;
}
・Native部
// シャッターを切るコマンド getDeviceInfo()をほぼまるパク
MtpDeviceInfo* MtpDevice::doDeviceShutter() {
Mutex::Autolock autoLock(mMutex);
mRequest.reset();
if (!sendRequest(0x910f))
return NULL;
if (!readData())
return NULL;
MtpResponseCode ret = readResponse();
if (ret == MTP_RESPONSE_OK) {
MtpDeviceInfo* info = new MtpDeviceInfo;
info->read(mData);
return info;
}
return NULL;
}
view raw MtpDevice.cpp hosted with ❤ by GitHub

0 件のコメント:

コメントを投稿