android JNI(转)
原文:http://jinguo.iteye.com/blog/696185
Java Native Interface (JNI)標準是java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI 是本地編程接口,它使得在 Java 虛擬機 (VM) 內部運行的 Java 代碼能夠與用其它編程語言(如 C、C++ 和匯編語言)編寫的應用程序和庫進行交互操作。
?
1.從如何載入.so檔案談起
??? 由于Android的應用層的類都是以Java寫的,這些Java類編譯為Dex型式的Bytecode之后,必須靠Dalvik虛擬機(VM: Virtual Machine)來執行。VM在Android平臺里,扮演很重要的角色。
??? 此外,在執行Java類的過程中,如果Java類需要與C組件溝通時,VM就會去載入C組件,然后讓Java的函數順利地調用到C組件的函數。此時,VM扮演著橋梁的角色,讓Java與C組件能通過標準的JNI介面而相互溝通。
??? 應用層的Java類是在虛擬機(VM: Vitual Machine)上執行的,而C件不是在VM上執行,那么Java程式又如何要求VM去載入(Load)所指定的C組件呢? 可使用下述指令:
???? System.loadLibrary(*.so的檔案名);
??? 例如,Android框架里所提供的MediaPlayer.java類,含指令:
???? public class MediaPlayer{???
???????? static {
?????????????????? System.loadLibrary("media_jni");
????????????? }
???? }
???? 這要求VM去載入Android的/system/lib/libmedia_jni.so檔案。載入*.so之后,Java類與*.so檔案就匯合起來,一起執行了。
?
2.如何撰寫*.so的入口函數
??? ---- JNI_OnLoad()與JNI_OnUnload()函數的用途
當Android的VM(Virtual Machine)執行到System.loadLibrary()函數時,首先會去執行C組件里的JNI_OnLoad()函數。它的用途有二:
(1)告訴VM此C組件使用那一個JNI版本。如果你的*.so檔沒有提供JNI_OnLoad()函數,VM會默認該*.so檔是使用最老的 JNI 1.1版本。由于新版的JNI做了許多擴充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必須藉由JNI_OnLoad()函數來告知VM。
(2)由于VM執行到System.loadLibrary()函數時,就會立即先呼叫JNI_OnLoad(),所以C組件的開發者可以藉由JNI_OnLoad()來進行C組件內的初期值之設定(Initialization) 。
例如,在Android的/system/lib/libmedia_jni.so檔案里,就提供了JNI_OnLoad()函數,其程式碼片段為:
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayer-JNI"
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
??? JNIEnv* env = NULL;
??? jint result = -1;
?
??? if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
??????? LOGE("ERROR: GetEnv failed\n");
??????? goto bail;
??? }
?
assert(env != NULL);
?
??? if (register_android_media_MediaPlayer(env) < 0) {
??????? LOGE("ERROR: MediaPlayer native registration failed\n");
??????? goto bail;
??? }
?
??? if (register_android_media_MediaRecorder(env) < 0) {
??????? LOGE("ERROR: MediaRecorder native registration failed\n");
??????? goto bail;
??? }
?
??? if (register_android_media_MediaScanner(env) < 0) {
??????? LOGE("ERROR: MediaScanner native registration failed\n");
??????? goto bail;
}
?
??? if (register_android_media_MediaMetadataRetriever(env) < 0) {
??????? LOGE("ERROR: MediaMetadataRetriever native registration failed\n");
??????? goto bail;
??? }
?
??? /* success -- return valid version number */
??? result = JNI_VERSION_1_4;
bail:
??? return result;
}
此函數回傳JNI_VERSION_1_4值給VM,于是VM知道了其所使用的JNI版本了。此外,它也做了一些初期的動作(可呼叫任何本地函數),例如指令:
?? if (register_android_media_MediaPlayer(env) < 0) {
??????? LOGE("ERROR: MediaPlayer native registration failed\n");
??????? goto bail;
??? }
就將此組件提供的各個本地函數(Native Function)登記到VM里,以便能加快后續呼叫本地函數的效率。
JNI_OnUnload()函數與JNI_OnLoad()相對應的。在載入C組件時會立即呼叫JNI_OnLoad()來進行組件內的初期動 作;而當VM釋放該C組件時,則會呼叫JNI_OnUnload()函數來進行善后清除動作。當VM呼叫JNI_OnLoad()或 JNI_Unload()函數時,都會將VM的指針(Pointer)傳遞給它們,其參數如下:
jint JNI_OnLoad(JavaVM* vm, void* reserved) {???? }
jint JNI_OnUnload(JavaVM* vm, void* reserved){???? }
在JNI_OnLoad()函數里,就透過VM之指標而取得JNIEnv之指標值,并存入env指標變數里,如下述指令:
?
jint JNI_OnLoad(JavaVM* vm, void* reserved){
???? JNIEnv* env = NULL;
???? jint result = -1;
??? if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
????????? LOGE("ERROR: GetEnv failed\n");
????????? goto bail;
??? }
}
由于VM通常是多執行緒(Multi-threading)的執行環境。每一個執行緒在呼叫JNI_OnLoad()時,所傳遞進來的JNIEnv 指標值都是不同的。為了配合這種多執行緒的環境,C組件開發者在撰寫本地函數時,可藉由JNIEnv指標值之不同而避免執行緒的資料沖突問題,才能確保所 寫的本地函數能安全地在Android的多執行緒VM里安全地執行。基于這個理由,當在呼叫C組件的函數時,都會將JNIEnv指標值傳遞給它,如下:
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
??? if (register_android_media_MediaPlayer(env) < 0) {
?? }
}
這JNI_OnLoad()呼叫register_android_media_MediaPlayer(env)函數時,就將env指標值傳遞過 去。如此,在register_android_media_MediaPlayer()函數就能藉由該指標值而區別不同的執行緒,以便化解資料沖突的問 題。
例如,在register_android_media_MediaPlayer()函數里,可撰寫下述指令:
?????? if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
?????? }
查看是否已經有其他執行緒進入此物件,如果沒有,此執行緒就進入該物件里執行了。還有,也可撰寫下述指令:
?????? if ((*env)->MonitorExit(env, obj) != JNI_OK) {
??????? }
查看是否此執行緒正在此物件內執行,如果是,此執行緒就會立即離開。
?
3.registerNativeMethods()函數的用途
應用層級的Java類別透過VM而呼叫到本地函數。一般是仰賴VM去尋找*.so里的本地函數。如果需要連續呼叫很多次,每次都需要尋找一遍,會多 花許多時間。此時,組件開發者可以自行將本地函數向VM進行登記。例如,在Android的/system/lib/libmedia_jni.so檔案 里的代碼段如下:
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayer-JNI"
static JNINativeMethod gMethods[] = {
??? {"setDataSource",?? "(Ljava/lang/String;)V",
??? (void *)android_media_MediaPlayer_setDataSource},
??? {"setDataSource",??? "(Ljava/io/FileDescriptor;JJ)V",
(void *)android_media_MediaPlayer_setDataSourceFD},
??? {"prepare",?? "()V",???? (void *)android_media_MediaPlayer_prepare},
??? {"prepareAsync",?? "()V",?? (void *)android_media_MediaPlayer_prepareAsync},
??? {"_start",?? "()V",?? (void *)android_media_MediaPlayer_start},
??? {"_stop",?? "()V",??? (void *)android_media_MediaPlayer_stop},
??? {"getVideoWidth",?? "()I",????? (void *)android_media_MediaPlayer_getVideoWidth},
??? {"getVideoHeight",?? "()I",????? (void *)android_media_MediaPlayer_getVideoHeight},
??? {"seekTo",????????? "(I)V",????? (void *)android_media_MediaPlayer_seekTo},
??? {"_pause",????????? "()V",?????? (void *)android_media_MediaPlayer_pause},
??? {"isPlaying",??????? "()Z",?????? (void *)android_media_MediaPlayer_isPlaying},
??? {"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition},
??? {"getDuration",????? "()I",???? (void *)android_media_MediaPlayer_getDuration},
??? {"_release",???????? "()V",????? (void *)android_media_MediaPlayer_release},
??? {"_reset",????????? "()V",??????? (void *)android_media_MediaPlayer_reset},
??? {"setAudioStreamType","(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},
??? {"setLooping",????? "(Z)V",????? (void *)android_media_MediaPlayer_setLooping},
??? {"setVolume",????? "(FF)V",???? (void *)android_media_MediaPlayer_setVolume},
??? {"getFrameAt",???? "(I)Landroid/graphics/Bitmap;",
???????? (void *)android_media_MediaPlayer_getFrameAt},
??? {"native_setup",??? "(Ljava/lang/Object;)V",
?????????? (void *)android_media_MediaPlayer_native_setup},
??? {"native_finalize",???? "()V",??? (void *)android_media_MediaPlayer_native_finalize},
};
?
static int register_android_media_MediaPlayer(JNIEnv *env){
??? return AndroidRuntime::registerNativeMethods(env,
??????????????? "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
?
jint JNI_OnLoad(JavaVM* vm, void* reserved){
??? if (register_android_media_MediaPlayer(env) < 0) {
??????? LOGE("ERROR: MediaPlayer native registration failed\n");
??????? goto bail;
??? }
}
當VM載入libmedia_jni.so檔案時,就呼叫JNI_OnLoad()函數。接著,JNI_OnLoad()呼叫 register_android_media_MediaPlayer()函數。此時,就呼叫到 AndroidRuntime::registerNativeMethods()函數,向VM(即AndroidRuntime)登記 gMethods[]表格所含的本地函數了。簡而言之,registerNativeMethods()函數的用途有二:
(1)更有效率去找到函數。
(2)可在執行期間進行抽換。由于gMethods[]是一個<名稱,函數指針>對照表,在程序執行時,可多次呼叫registerNativeMethods()函數來更換本地函數之指針,而達到彈性抽換本地函數之目的。
?
4.Andoird 中使用了一種不同傳統Java JNI的方式來定義其native的函數。其中很重要的區別是Andorid使用了一種Java 和 C 函數的映射表數組,并在其中描述了函數的參數和返回值。這個數組的類型是JNINativeMethod,定義如下:
typedef struct {
const char* name;??????????? /*Java中函數的名字*/?????????
const char* signature;????? /*描述了函數的參數和返回值*/
void* fnPtr;?????????????? /*函數指針,指向C函數*/
} JNINativeMethod;
?
其中比較難以理解的是第二個參數,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
實際上這些字符是與函數的參數類型一一對應的。
"()" 中的字符表示參數,后面的則代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
具體的每一個字符的對應關系如下
字符 ?? Java類型 ???? C類型
V???? void???????? void
Z????? jboolean???? boolean
I?????? jint???????? int
J????? jlong??????? long
D????? jdouble?????? double
F????? jfloat??????????? float
B?? ??? jbyte??????????? byte
C????? jchar?????????? char
S????? jshort????????? short
?
數組則以"["開始,用兩個字符表示
[I???? jintArray????? int[]
[F???? jfloatArray??? float[]
[B???? jbyteArray??? byte[]
[C??? jcharArray??? ?? char[]
[S??? jshortArray?? ??? short[]
[D??? jdoubleArray ??? double[]
[J???? jlongArray??? ?? long[]
[Z??? jbooleanArray ??? boolean[]
上面的都是基本類型。如果Java函數的參數是class,則以"L"開頭,以";"結尾,中間是用"/" 隔開的包及類名。而其對應的C函數名的參數則為jobject. 一個例外是String類,其對應的類為jstring
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
如果JAVA函數位于一個嵌入類,則用$作為類名間的分隔符。
例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
?
Android JNI編程實踐
?
一、直接使用java本身jni接口(windows/ubuntu)
1.在Eclipsh中新建一個android應用程序。兩個類:一個繼承于Activity,UI顯示用。另一個包含native方法。編譯生成所有類。
jnitest.java文件:
package com.hello.jnitest;
?
import android.app.Activity;
import android.os.Bundle;
?
public class jnitest extends Activity {
??? /** Called when the activity is first created. */
??? @Override
??? public void onCreate(Bundle savedInstanceState) {
??????? super.onCreate(savedInstanceState);
?
??????? setContentView(R.layout.main);
?
??????? Nadd cal = new Nadd();
?
??????? setTitle("The Native Add Result is " + String.valueOf(cal.nadd(10, 19)));
??? }
}
Nadd.java文件:
package?com.hello.jnitest;
?
public?class?Nadd {
?
????static?{
??????? System.loadLibrary?("Nadd");
??? }
?
????public?native?int?nadd(int?a,?int?b);
?
}
以上在windows中完成。
2.使用javah命令生成C/C++的.h文件。注意類要包含包名,路徑文件夾下要包含所有包中的類,否則會報找不到類的錯誤。classpath參數指定到包名前一級文件夾,文件夾層次結構要符合java類的組織層次結構。
javah -classpath ../jnitest/bin com.hello.jnitest.Nadd
com_hello_jnitest_Nadd .h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_hello_jnitest_Nadd */
?
#ifndef _Included_com_hello_jnitest_Nadd
#define _Included_com_hello_jnitest_Nadd
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:???? com_hello_jnitest_Nadd
* Method:??? nadd
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_hello_jnitest_Nadd_nadd
(JNIEnv *, jobject, jint, jint);
?
#ifdef __cplusplus
}
#endif
#endif
3.編輯.c文件實現native方法。
com_hello_jnitest_Nadd.c文件:
#include <stdlib.h>
#include "com_hello_jnitest_Nadd.h"
JNIEXPORT jint JNICALL Java_com_hello_jnitest_Nadd_nadd(JNIEnv * env, jobject c, jint a, jint b)
{
?? return (a+b);
}
4.編譯.c文件生存動態庫。
arm-none-linux-gnueabi-gcc -I/home/a/work/android/jdk1.6.0_17/include -I/home/a/work/android/jdk1.6.0_17/include/linux -fpic -c com_hello_jnitest_Nadd.c
arm-none-linux-gnueabi-ld -T/home/a/CodeSourcery/Sourcery_G++_Lite/arm-none-linux-gnueabi/lib/ldscripts/armelf_linux_eabi.xsc -share -o libNadd.so com_hello_jnitest_Nadd.o
得到libNadd.so文件。
以上在ubuntu中完成。
5.將相應的動態庫文件push到avd的system/lib中:adb push libNadd.so /system/lib。若提示Read-only file system錯誤,運行adb remount命令,即可。
Adb push libNadd.so /system/lib
6.在eclipsh中運行原應用程序即可。
以上在windows中完成。
對于一中生成的so文件也可采用二中的方法編譯進apk包中。只需在工程文件夾中建libs\armeabi文件夾(其他文件夾名無效,只建立libs文件夾也無效),然后將so文件拷入,編譯工程即可。
二.使用NDK生成本地方法(ubuntu and windows)
1.安裝NDK:解壓,然后進入NDK解壓后的目錄,運行build/host-setup.sh(需要Make 3.81和awk)。若有錯,修改host-setup.sh文件:將#!/bin/sh修改為#!/bin/bash,再次運行即可。
2.在apps文件夾下建立自己的工程文件夾,然后在該文件夾下建一文件Application.mk和項project文件夾。
Application.mk文件:
APP_PROJECT_PATH := $(call my-dir)/project
APP_MODULES????? := myjni
3.在project文件夾下建一jni文件夾,然后新建Android.mk和myjni.c。這里不需要用javah生成相應的.h文件,但函數名要包含相應的完整的包、類名。
4.編輯相應文件內容。
Android.mk文件:
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#??????http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
?
include $(CLEAR_VARS)
?
LOCAL_MODULE??? := myjni
LOCAL_SRC_FILES := myjni.c
?
include $(BUILD_SHARED_LIBRARY)
?
myjni.c文件:
#include <string.h>
#include <jni.h>
?
jstring
Java_com_hello_NdkTest_NdkTest_stringFromJNI( JNIEnv* env,
????????????????????????????????????????????????? jobject thiz )
{
??? return (*env)->NewStringUTF(env, "Hello from My-JNI !");
}
myjni文件組織:
a@ubuntu:~/work/android/ndk-1.6_r1/apps$ tree myjni
myjni
|-- Application.mk
`-- project
??? |-- jni
??? |?? |-- Android.mk
??? |?? `-- myjni.c
??? `-- libs
??????? `-- armeabi
??????????? `-- libmyjni.so
?
4 directories, 4 files
5.編譯:make APP=myjni.
以上內容在ubuntu完成。以下內容在windows中完成。當然也可以在ubuntu中完成。
6.在eclipsh中創建android application。將myjni中自動生成的libs文件夾拷貝到當前工程文件夾中,編譯運行即可。
NdkTest.java文件:
package com.hello.NdkTest;
?
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
?
public class NdkTest extends Activity {
??? /** Called when the activity is first created. */
??? @Override
??? public void onCreate(Bundle savedInstanceState) {
??????? super.onCreate(savedInstanceState);
??????? TextView tv = new TextView(this);
??????? tv.setText( stringFromJNI() );
?????? setContentView(tv);
??? }
?
??? public native String stringFromJNI();
?
??? static {
?
??????? System.loadLibrary("myjni");
??? }
}
對于二中生成的so文件也可采用一中的方法push到avd中運行。
?
本篇將介紹在JNI編程中如何傳遞參數和返回值。
首先要強調的是,native方法不但可以傳遞Java的基本類型做參數,還可以傳遞更復雜的類型,比如String,數組,甚至自定義的類。這一切都可以在jni.h中找到答案。
1. Java基本類型的傳遞
用過Java的人都知道,Java中的基本類型包括boolean,byte,char,short,int,long,float,double 這樣幾種,如果你用這幾種類型做native方法的參數,當你通過javah -jni生成.h文件的時候,只要看一下生成的.h文件,就會一清二楚,這些類型分別對應的類型是 jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble 。這幾種類型幾乎都可以當成對應的C++類型來用,所以沒什么好說的。
2. String參數的傳遞
Java的String和C++的string是不能對等起來的,所以處理起來比較麻煩。先看一個例子,
class Prompt {
????// native method that prints a prompt and reads a line
????private native String getLine(String prompt);
????public static void main(String args[]) {
????????Prompt p = new Prompt();
????????String input = p.getLine("Type a line: ");
?????????System.out.println("User typed: " + input);
????}
????static {
????????System.loadLibrary("Prompt");
????}
}
在這個例子中,我們要實現一個native方法
String getLine(String prompt);
讀入一個String參數,返回一個String值。
通過執行javah -jni得到的頭文件是這樣的
#include <jni.h>
#ifndef _Included_Prompt
#define _Included_Prompt
#ifdef __cplusplus
extern "C" {
#endif
?
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);
?
#ifdef __cplusplus
}
#endif
#endif
jstring是JNI中對應于String的類型,但是和基本類型不同的是,jstring不能直接當作C++的string用。如果你用
cout << prompt << endl;
編譯器肯定會扔給你一個錯誤信息的。
其實要處理jstring有很多種方式,這里只講一種我認為最簡單的方式,看下面這個例子,
#include "Prompt.h"
#include <iostream>
?
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
????const char* str;
????str = env->GetStringUTFChars(prompt, false);
????if(str == NULL) {
????????return NULL; /* OutOfMemoryError already thrown */
????}
????std::cout << str << std::endl;
????env->ReleaseStringUTFChars(prompt, str);
?
????char* tmpstr = "return string succeeded";
????jstring rtstr = env->NewStringUTF(tmpstr);
????return rtstr;
}
在上面的例子中,作為參數的prompt不能直接被C++程序使用,先做了如下轉換
str = env->GetStringUTFChars(prompt, false);
將jstring類型變成一個char*類型。
返回的時候,要生成一個jstring類型的對象,也必須通過如下命令,
jstring rtstr = env->NewStringUTF(tmpstr);
這里用到的GetStringUTFChars和NewStringUTF都是JNI提供的處理String類型的函數,還有其他的函數這里就不一一列舉了。
3. 數組類型的傳遞
和String一樣,JNI為Java基本類型的數組提供了j*Array類型,比如int[]對應的就是jintArray。來看一個傳遞int數組的例子,Java程序就不寫了,
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
{
????jint *carr;
????carr = env->GetIntArrayElements(arr, false);
????if(carr == NULL) {
????????return 0; /* exception occurred */
????}
????jint sum = 0;
????for(int i=0; i<10; i++) {
????????sum += carr[i];
????}
????env->ReleaseIntArrayElements(arr, carr, 0);
????return sum;
}
這個例子中的GetIntArrayElements和ReleaseIntArrayElements函數就是JNI提供用于處理int數組的函 數。如果試圖用arr[i]的方式去訪問jintArray類型,毫無疑問會出錯。JNI還提供了另一對函數GetIntArrayRegion和 ReleaseIntArrayRegion訪問int數組,就不介紹了,對于其他基本類型的數組,方法類似。
4. 二維數組和String數組
在JNI中,二維數組和String數組都被視為object數組,因為數組和String被視為object。仍然用一個例子來說明,這次是一個二維int數組,作為返回值。
JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)
{
????jobjectArray result;
????jclass intArrCls = env->FindClass("[I");
????result = env->NewObjectArray(size, intArrCls, NULL);
?
????for (int i = 0; i < size; i++) {
????????jint tmp[256]; /* make sure it is large enough! */
????????jintArray iarr = env->NewIntArray(size);
????????for(int j = 0; j < size; j++) {
????????????tmp[j] = i + j;
????????}
????????env->SetIntArrayRegion(iarr, 0, size, tmp);
????????env->SetObjectArrayElement(result, i, iarr);
????????env->DeleteLocalRef(iarr);
????}
????return result;
}
上面代碼中的第三行,
jobjectArray result;
因為要返回值,所以需要新建一個jobjectArray對象。
jclass intArrCls = env->FindClass("[I");
是創建一個jclass的引用,因為result的元素是一維int數組的引用,所以intArrCls必須是一維int數組的引用,這一點是如何保證的 呢?注意FindClass的參數"[I",JNI就是通過它來確定引用的類型的,I表示是int類型,[標識是數組。對于其他的類型,都有相應的表示方 法,
Z boolean?
B byte?
C char?
S short?
I int?
J long?
F float?
D double
String是通過“Ljava/lang/String;”表示的,那相應的,String數組就應該是“[Ljava/lang/String;”。
還是回到代碼,
result = env->NewObjectArray(size, intArrCls, NULL);
的作用是為result分配空間。
jintArray iarr = env->NewIntArray(size);
是為一維int數組iarr分配空間。
env->SetIntArrayRegion(iarr, 0, size, tmp);
是為iarr賦值。
env->SetObjectArrayElement(result, i, iarr);
是為result的第i個元素賦值。
通過上面這些步驟,我們就創建了一個二維int數組,并賦值完畢,這樣就可以做為參數返回了。
如果了解了上面介紹的這些內容,基本上大部分的任務都可以對付了。雖然在操作數組類型,尤其是二維數組和String數組的時候,比起在單獨的語言中編程要麻煩,但既然我們享受了跨語言編程的好處,必然要付出一定的代價。
有一點要補充的是,本文所用到的函數調用方式都是針對C++的,如果要在C中使用,所有的env->都要被替換成(*env)->,而 且后面的函數中需要增加一個參數env,具體請看一下jni.h的代碼。另外還有些省略的內容,可以參考JNI的文檔:Java Native Interface 6.0 Specification,在JDK的文檔里就可以找到。如果要進行更深入的JNI編程,需要仔細閱讀這個文檔。接下來的高級篇,也會討論更深入的話 題。
?
======================================================
在本篇中,將會涉及關于JNI編程更深入的話題,包括:在native方法中訪問Java類的域和方法,將Java中自定義的類作為參數和返回值傳遞等等。了解這些內容,將會對JNI編程有更深入的理解,寫出的程序也更清晰,易用性更好。
1. 在一般的Java類中定義native方法
在前兩篇的例子中,都是將native方法放在main方法的Java類中,實際上,完全可以在任何類中定義native方法。這樣,對于外部來說,這個類和其他的Java類沒有任何區別。
2. 訪問Java類的域和方法
native方法雖然是native的,但畢竟是方法,那么就應該同其他方法一樣,能夠訪問類的私有域和方法。實際上,JNI的確可以做到這一點,我們通過幾個例子來說明,
public class ClassA {
????String str_ = "abcde";
????int number_;
????public native void nativeMethod();
????private void javaMethod() {
System.out.println("call java method succeeded");
????}
????static {
System.loadLibrary("ClassA");
????}
}
在這個例子中,我們在一個沒有main方法的Java類中定義了native方法。我們將演示如何在nativeMethod()中訪問域str_,number_和方法javaMethod(),nativeMethod()的C++實現如下,
JNIEXPORT void JNICALL Java_testclass_ClassCallDLL_nativeMethod(JNIEnv *env, jobject obj) {
????// access field
????jclass cls = env->GetObjectClass(obj);
????jfieldID fid = env->GetFieldID(cls, "str_", "Ljava/lang/String;");
????jstring jstr = (jstring)env->GetObjectField(obj, fid);
????const char *str = env->GetStringUTFChars(jstr, false);
????if(std::string(str) == "abcde")
????????std::cout << "access field succeeded" << std::endl;
?
????jint i = 2468;
????fid = env->GetFieldID(cls, "number_", "I");
????env->SetIntField(obj, fid, i);
?
????// access method
????jmethodID mid = env->GetMethodID(cls, "javaMethod", "()V");
????env->CallVoidMethod(obj, mid);
}
上面的代碼中,通過如下兩行代碼獲得str_的值,
jfieldID fid = env->GetFieldID(cls, "str_", "Ljava/lang/String;");
jstring jstr = (jstring)env->GetObjectField(obj, fid);
第一行代碼獲得str_的id,在GetFieldID函數的調用中需要指定str_的類型,第二行代碼通過str_的id獲得它的值,當然我們讀到的是一個jstring類型,不能直接顯示,需要轉化為char*類型。
接下來我們看如何給Java類的域賦值,看下面兩行代碼,
fid = env->GetFieldID(cls, "number_", "I");
env->SetIntField(obj, fid, i);
第一行代碼同前面一樣,獲得number_的id,第二行我們通過SetIntField函數將i的值賦給number_,其他類似的函數可以參考JDK的文檔。
訪問javaMethod()的過程同訪問域類似,
jmethodID mid = env->GetMethodID(cls, "javaMethod", "()V");
env->CallVoidMethod(obj, mid);
需要強調的是,在GetMethodID中,我們需要指定javaMethod方法的類型,域的類型很容易理解,方法的類型如何定義呢,在上面的例子中,我們用的是()V,V表示返回值為空,()表示參數為空。如果是更復雜的函數類型如何表示?看一個例子,
long f (int n, String s, int[] arr);
這個函數的類型符號是(ILjava/lang/String;[I)J,I表示int類型,Ljava/lang/String;表示String類型,[I表示int數組,J表示long。這些都可以在文檔中查到。
3. 在native方法中使用用戶定義的類
JNI不僅能使用Java的基礎類型,還能使用用戶定義的類,這樣靈活性就大多了。大體上使用自定義的類和使用Java的基礎類(比如 String)沒有太大的區別,關鍵的一點是,如果要使用自定義類,首先要能訪問類的構造函數,看下面這一段代碼,我們在native方法中使用了自定義 的Java類ClassB,
jclass cls = env->FindClass("Ltestclass/ClassB;");
jmethodID id = env->GetMethodID(cls, "<init>", "(D)V");
?
jdouble dd = 0.033;
jvalue args[1];
args[0].d = dd;
jobject obj = env->NewObjectA(cls, id, args);
首先要創建一個自定義類的引用,通過FindClass函數來完成,參數同前面介紹的創建String對象的引用類似,只不過類名稱變成自定義類的 名稱。然后通過GetMethodID函數獲得這個類的構造函數,注意這里方法的名稱是"<init>",它表示這是一個構造函數。
jobject obj = env->NewObjectA(cls, id, args);
生成了一個ClassB的對象,args是ClassB的構造函數的參數,它是一個jvalue*類型。
通過以上介紹的三部分內容,native方法已經看起來完全像Java自己的方法了,至少主要功能上齊備了,只是實現上稍麻煩。而了解了這 些,JNI編程的水平也更上一層樓。下面要討論的話題也是一個重要內容,至少如果沒有它,我們的程序只能停留在演示階段,不具有實用價值。
4. 異常處理
在C++和Java的編程中,異常處理都是一個重要的內容。但是在JNI中,麻煩就來了,native方法是通過C++實現的,如果在native方法中發生了異常,如何傳導到Java呢?
JNI提供了實現這種功能的機制。我們可以通過下面這段代碼拋出一個Java可以接收的異常,
jclass errCls;
env->ExceptionDescribe();
env->ExceptionClear();
errCls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(errCls, "thrown from C++ code");
如果要拋出其他類型的異常,替換掉FindClass的參數即可。這樣,在Java中就可以接收到native方法中拋出的異常。
至此,JNI編程系列的內容就完全結束了,這些內容都是本人的原創,通過查閱文檔和網上的各種文章總結出來的,相信除了JDK的文檔外,沒有比這更 全面的講述JNI編程的文章了。當然,限于篇幅,有些地方不可能講的很細。限于水平,也可能有一些錯誤。文中所用的代碼,都親自編譯執行過。。
轉載于:https://www.cnblogs.com/xsgame/p/3464436.html
總結
以上是生活随笔為你收集整理的android JNI(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Bjarne Stroustrup语录[
- 下一篇: php preg_match正则匹配中文