一个简单解释:
http://www.cnblogs.com/mandroid/archive/2011/06/15/2081093.html
又一个:
http://developer.51cto.com/art/200509/2815.htm
jni手册:
http://java.sun.com/docs/books/jni/
jni是和java程序在一个进程中,当然, jni可以调用native code的api, 从而使native daemon和java在不同进程。
通常java和native进程还有其他的通讯方式, 比如:
1. tcp/ip或者其他ipc技术
2. jdbc
3. java idl
先生成java文件,并声明native函数,
然后 调用 javah -jni xxx生成jni的header file
JNIEnv:
The second argument differs depending on whether the native method is a
static or an instance method. The second argument to an instance native method is
a reference to the object on which the method is invoked, similar to the this
pointer in C++. The second argument to a static native method is a reference to
the class in which the method is defined. Our example, Java_Prompt_getLine,
implements an instance native method. Thus the jobject parameter is a reference
to the object itself.
There are two kinds of types in the Java programming language: primitive
types such as int, float, and char, and reference types such as classes, instances,
and arrays. In the Java programming language, strings are instances of the
java.lang.String class.
java premitive类型和jni类型map:
Java Language Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
The JNI passes objects to native methods as opaque references.
opaque references是c指针,指向jvm的内部数据结构, 没有c 的类型对应, 需要使用JNIEnv提供的函数来转换, 比如:
java的java.lang.String类型对应jni的jstring, 需要调用JNIEnv的GetStringUTFChars来得到真正的string。
所有的reference都有jobject类型, 其他类型是jobject的子类型:
The jvalue type is a union of the reference types and primitive types. It is defined
as follows:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
Method and field IDs are regular C pointer types:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID *jfieldID; /* field ID */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method ID */
jstring转换的sample:
JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s", str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
/* We assume here that the user does not type more than
* 127 characters */
scanf("%s", buf);
return (*env)->NewStringUTF(env, buf);
}
The JNI uses C strings to represent class names, field and method names, and field
and method descriptors. These strings are in the UTF-8 format.
0-127表示ascii码, 128-255之间表示ASCII扩展(ibm字符集)或者ISO 8859-1Latin 1, 而UNICODE编码通过增加一个高字节对ISO Latin-1字符集进行扩展, UNICODE支持欧洲、非洲、中东、亚洲(包括统一标准的东亚象形汉字和韩国象形文字)。
如果UNICODE字符由2个字节表示,则编码成UTF-8很可能需要3个字节,而如果UNICODE字符由4个字节表示,则编码成UTF-8可能需要6个字节。用4个或6个字节去编码一个UNICODE字符可能太多了,但很少会遇到那样的UNICODE字符。
下面是unicode对utf-8的转换码表:
UNICODE | UTF-8 |
0000 0000 - 0000 007F | 0XXX XXXX |
0000 0080 - 0000 07FF | 110X XXXX 10XX XXXX |
0000 0800 - 0000 FFFF | 1110 XXXX 10XX XXXX 10XX XXXX |
0001 0000 - 001F FFFF | 1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX |
0020 0000 - 03FF FFFF | 1111 10XX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
0400 0000 - 7FFF FFFF | 1111 110X 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
一个unicode的码值对应右边utf-8的一组编码, 最上面的是utf-8的首字节, 对应着uncode的高位。
每个字节由一个换码序列开始。第一个字节由唯一的换码序列,由n位连续的1加一位0组成, 首字节连续的1的个数表示字符编码所需的字节数
由上分析可以看到,UNICODE到UTF-8的转换就是先确定编码所需要的字节数,然后用UNICODE编码位从低位到高位依次填入上面表示为x的位上,不足的高位以0补充。
字节FF和FE在UTF-8编码中永远不会出现
8位字符的UTF-8编码会被email网关过滤,因为internet信息最初设计为7位ASCII码。因此产生了UTF-7编码。 UTF-8 在它的表示中使用值100xxxxx的几率超过50%, 而现存的实现如ISO 2022, 4873, 6429, 和8859系统,会把它错认为是C1 控制码。因此产生了UTF-7.5编码。
java的utf-8和标准的utf-8格式有点不同:
First, the null byte (byte)0 is encoded using the two-byte format rather than the
one-byte format. This means that JNI UTF-8 strings never have embedded nulls(因为有utf-8的识别码).
Second, only the one-byte, two-byte, and three-byte formats are used. The JNI
does not recognize the longer UTF-8 formats.
ReleaseStringUTFChars:用于释放utf-8z字符的buffer
NewStringUTF: 构建新的java.lang.String instance in the native method
GetStringChars and ReleaseStringChars obtain string characters represented
in the Unicode format.
UTF-8 strings are always terminated with the ‘\0’ character, whereas Unicode
strings are not.
GetStringLength:得到unicode string的长度
GetStringUTFLength or ANSI C function strlen: 得到utf-8的长度
GetStringChars和GetStringUTFChars有第三个参数jboolean *isCopy:
JNI_FALSE: 返回的string指向original java.lang.String instance.
JNI_TRUE:返回的string指向original java.lang.String 的copy的instance.
When the location pointed
to by isCopy is set to JNI_FALSE, native code must not modify the contents of the
returned string
It is in general not possible to predict whether the virtual machine will copy
the characters in a given java.lang.String instance.
Once a direct pointer to a java.lang.String instance is passed back to
the native code, the garbage collector can no longer relocate the
java.lang.String instance.
The ReleaseStringChars
call is necessary whether GetStringChars has set *isCopy to JNI_TRUE or
JNI_FALSE. ReleaseStringChars either frees the copy or unpins the instance,
depending upon whether GetStringChars has returned a copy or not.
Java 2 SDK release 1.2 introduces
a new pair of functions, Get/ReleaseStringCritical(disable GC)为了得到direct pointer, 但有一些限制条件:
When garbage collection(因为GC产生的这个copied string) is disabled, any other
threads that trigger garbage collection will be blocked as well.
Native code between a Get/ReleaseStringCritical pair must not issue blocking calls or
allocate new objects in the Java virtual machine. Otherwise, the virtual machine
may deadlock.
这个函数的嵌套调用是允许的。
The JNI does not support GetStringUTFCritical and ReleaseStringUTFCritical
functions.
GetStringRegion and GetStringUTFRegion: 将string输出到指定的buffer,例如:
char outbuf[128];
int len = (*env)->GetStringLength(env, prompt);
(*env)->GetStringUTFRegion(env, prompt, 0, len, outbuf);
printf("%s", outbuf);
The JNI treats primitive arrays and object arrays differently。
int[][] arr2是object arrays。
访问primitive arrays的方法跟访问string的方法类似, 需要使用JNIEnv的函数, 例如一个访问int型array的例子:
jint buf[10];
jint i, sum = 0;
(*env)->GetIntArrayRegion(env, arr, 0, 10, buf);
for (i = 0; i < 10; i++) {
sum += buf[i];
}
return sum;
Get/Release<Type>ArrayElements functions 返回直接的direct pointer to the elements of primitive arrays.
primitive array方法总结:
The JNI provides a separate pair of functions to access objects arrays.
GetObjectArrayElement returns the element at a given index, whereas
SetObjectArrayElement updates the element at a given index.
you cannot get all the object elements or copy
multiple object elements at once.
一个生成两维数组的例子(行列尺寸都是size):
JNIEXPORT jobjectArray JNICALL
Java_ObjectArrayTest_initInt2DArray(JNIEnv *env,
jclass cls,
int size)
{
jobjectArray result;
int i;
jclass intArrCls = (*env)->FindClass(env, "[I");
if (intArrCls == NULL) {
return NULL; /* exception thrown */
}
result = (*env)->NewObjectArray(env, size, intArrCls,
NULL);
if (result == NULL) {
return NULL; /* out of memory error thrown */
}
for (i = 0; i < size; i++) {
jint tmp[256]; /* make sure it is large enough! */
int j;
jintArray iarr = (*env)->NewIntArray(env, size);
if (iarr == NULL) {
return NULL; /* out of memory error thrown */
}
for (j = 0; j < size; j++) {
tmp[j] = i + j;
}
(*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);
(*env)->SetObjectArrayElement(env, result, i, iarr);
(*env)->DeleteLocalRef(env, iarr);
}
return result;
}
the NewObjectArray function allocates an array whose element type is
denoted by the intArrCls class reference:
jclass intArrCls = (*env)->FindClass(env, "[I");
The DeleteLocalRef call at the end of the loop ensures that the virtual
machine does not run out of the memory used to hold JNI references such as iarr.
一般情况下, jvm会释放reference, 有几种情况需要显示地释放:
1. 占用大量reference table
2. You want to write a utility function that is called from unknown contexts.(unkown contexts是什么意思?指这个jni函数不知道被谁调用还是指jni函数里面调用了如后方法?cid = (*env)->GetMethodID(env, stringClass,
"<init>", "([C)V"); 其实这里指的一种情况是在jni中生成java对象)
3. 函数不返回(释放是在jni函数返回以后或者后面不用(case 4 例外))
4. Your native method accesses a large object, thereby creating a local reference
to the object. The native method then performs additional computation before
returning to the caller. The local reference to the large object will prevent the
object from being garbage collected until the native method returns, even if
the object is no longer used in the remainder of the native method.
显示释放也是出发GC。
两种fields:
instance fields: 为对象
static fields: 所有类共享
访问instance field的步骤:
1. jclass cls = (*env)->GetObjectClass(env, obj); //先得到instance的class对象
2. fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;"); //得到成员的field id
3. jstr = (*env)->GetObjectField(env, obj, fid); //得到成员, 这里是string的例子
4. (*env)->SetObjectField(env, obj, fid, jstr); //更改成员值
"Ljava/lang/String;"是JNI field descriptors, 对于reference type的, 需在前面加个L, "."号变成了"/"
“[”代表array类型, "[I"表示int[]类型
“double[][][]”用"[[[D"表示
Field Descriptor Java Language Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
Field Descriptor Java Language Type
"Ljava/lang/String;" String
"[I" int[]
"[Ljava/lang/Object;" Object[]
访问static field的步骤:
1. jclass cls = (*env)->GetObjectClass(env, obj); //先得到class ref
2.fid = (*env)->GetStaticFieldID(env, cls, "si", "I"); //得到成员的field id
3.si = (*env)->GetStaticIntField(env, cls, fid); //得到成员, 这里是int的例子
4. (*env)->SetStaticIntField(env, cls, fid, 200); //更改成员值
两种methods:
instance method: 为对象
static method: 所有类共享
访问instance field的步骤:
1. jclass cls = (*env)->GetObjectClass(env, obj); //得到instance obj
2. jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V"); //得到method
3. (*env)->CallVoidMethod(env, obj, mid); //调用
也可用接口来访问, 如下:
jclass runnableIntf =
(*env)->FindClass(env, "java/lang/Runnable");
A method descriptor combines the argument types and the
return type of a method.
"(I)V": 参数int型。 返回类型是void
"()D": 没有参数, 返回double型
Method Descriptor Java Language Type
"()Ljava/lang/String;" String f();
"(ILjava/lang/Class;)J" long f(int i, Class c);
"([B)V" String(byte[] bytes);
c风格的“int f(void)” 变成"()I"; // 不能传递void *了, java没有指针, init f(void, void)也应该没有,(java没有void类型)
native private String getLine(String);
has the following descriptor:
"(Ljava/lang/String;)Ljava/lang/String;"
array如下:
public static void main(String[] args);
is as follows:
"([Ljava/lang/String;)V"
访问instance field的步骤:
1. jclass cls = (*env)->GetObjectClass(env, obj); //得到instance obj
2. jmethodID mid = (*env)->GetStaticMethodID(env, cls, "callback", "()V");//得到method
3. (*env)->CallStaticVoidMethod(env, cls, mid); //调用
用CallNonvirtual<Type>Method方法访问父类实现的函数
在jni中构造java对象
1. stringClass = (*env)->FindClass(env, "java/lang/String");
2. cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V"); // 得到构造函数id, 注意"<init>", 为构造函数用的
3. result = (*env)->NewObject(env, stringClass, cid, elemArr); //生成新对象, 构造函数将运行
result = (*env)->AllocObject(env, stringClass); //生成一个未初始化的object
(*env)->CallNonvirtualVoidMethod(env, result, stringClass, cid, elemArr); //调用构造函数
用static变量保存cached feild或者method(method可用id, 而用 (*env)->FindClass(env, "java/lang/String");的方法得到的是local reference,local reference将被释放, 用id的方式比如static jmethodID cid = NULL;)(调用jni时)。
static jfieldID fid_s = NULL;
if (fid_s == NULL) {
fid_s = (*env)->GetFieldID(env, cls, "s",
"Ljava/lang/String;");
if (fid_s == NULL) {
return; /* exception already thrown */
}
}
One thread may overwrite
the static variable fid_s computed by another thread. Luckily, although this
race condition leads to duplicated work in multiple threads, it is otherwise harmless.
The field IDs computed by multiple threads for the same field in the same
class will necessarily be the same.
这种方法是线程安全的!!!!
可以用初始化函数的方式, 通过全局的变量或者方法cache feild和method, 比如:
jmethodID MID_InstanceMethodCall_callback;
JNIEXPORT void JNICALL
Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls)
{
MID_InstanceMethodCall_callback =
(*env)->GetMethodID(env, cls, "callback", "()V");
}
一些宏:
JNIEXPORT and JNICALL are macros used to specify the calling and linkage convention
of both JNI functions and native method implementations. The
JNIEXPORT jint JNICALL
Java_pkg_Cls_f(JNIEnv *env, jobject this);
jboolean:
#define JNI_FALSE 0
#define JNI_TRUE 1
#define JNI_OK 0
#define JNI_ERR (-1)
#define JNI_COMMIT 1
#define JNI_ABORT 2
#define JNI_VERSION_1_1 0x00010001 /* JNI version 1.1 */
#define JNI_VERSION_1_2 0x00010002 /* JNI version 1.2 */
#define JNI_EDETACHED (-2) /* thread detached from the VM */
#define JNI_EVERSION (-3) /* JNI version error */
一个使用global reference的例子:
static jclass stringClass = NULL;
...
if (stringClass == NULL) {
jclass localRefCls =
(*env)->FindClass(env, "java/lang/String");
if (localRefCls == NULL) {
return NULL; /* exception thrown */
}
/* Create a global reference */
stringClass = (*env)->NewGlobalRef(env, localRefCls);
/* The local reference is no longer useful */
(*env)->DeleteLocalRef(env, localRefCls);
/* Is the global reference created successfully? */
if (stringClass == NULL) {
return NULL; /* out of memory exception thrown */
}
}
a weak global reference allows to still be unloaded, 使用They are created using
NewGlobalWeakRef and freed using DeleteGlobalWeakRef.
对于系统提供的class, 用global或者weak global没有不同。
使用下面的方法判断是否同样的obj:
(*env)->IsSameObject(env, obj1, obj2)
The JNI specification dictates that the virtual machine automatically ensures
that each native method can create at least 16 local references.
用下面的方法确认有无空间可放local ref:
if ((*env)->EnsureLocalCapacity(env, len)) < 0) {
... /* out of memory */
}
可以用Push/PopLocalFrame管理local ref帧:
#define N_REFS ... /* the maximum number of local references
used in each iteration */
for (i = 0; i < len; i++) {
if ((*env)->PushLocalFrame(env, N_REFS) < 0) {
... /* out of memory */
}
jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* process jstr */
(*env)->PopLocalFrame(env, NULL);
}
创建local ref:
NewLocalRef,
The NewLocalRef function is useful when you write utility functions that are
expected to return a local reference.
怎么用(应该是启动虚拟机的时候用):
Java 2 SDK release 1.2 supports a command-line option -verbose:jni.
When this option is enabled, the virtual machine implementation reports excessive
local reference creation beyond the reserved capacity.
DeleteGlobalRef: 释放global ref
DeleteWeakGlobalRef: 释放weak global ref
global和weak global的不同: GC不会释放global
ExceptionOccurred: 检查异常发生
ExceptionDescribe: 打印异常
ExceptionClear: 清除异常
ThrowNew: 抛出异常
ExceptionCheck: 检查是否出现异常
使用下列函数在c语言中创建jvm,并运行java程序:
1. JNI_CreateJavaVM
2. mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
3. (*env)->CallStaticVoidMethod(env, cls, mid, args);
4. (*jvm)->DestroyJavaVM(jvm);
这种方式甚至可在native code中使用多线程来调用虚拟机对应的java代码。
需要这些库: cc -I<jni.h dir> -L<libjava.so dir> -lthread -ljava invoke.c
JNIEnv不能在对线程中作为参数传递, 应该用jvm attach((*jvm)->AttachCurrentThread)的方法获取
local reference不能在线程中作为参数传递, 可转换成global
MonitorEnter/MonitorExit就像互斥锁, 用于线程阻塞访问,MonitorEnter and MonitorExit work on jclass, jstring, and jarray, 主要用于java侧
types, which are special kinds of jobject references.
if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
... /* error handling */
}
... /* synchronized block */
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
}
还有一些其他同步方法:这些主要在(*env)->CallVoidMethod等的时候作为参数:
static jmethodID MID_Object_wait;
static jmethodID MID_Object_notify;
static jmethodID MID_Object_notifyAll;
native thread model: 操作系统或者其他native线程库提供(lock/mutex等)
user thread model:jvm提供(MonitorEnter等)
一般建议在jni中用user thread model,因为可移植性。
把native chars转换成jstring的方法:
jstring JNU_NewStringNative(JNIEnv *env, const char *str)
{
jstring result;
jbyteArray bytes = 0;
int len;
if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
return NULL; /* out of memory error */
}
len = strlen(str);
bytes = (*env)->NewByteArray(env, len);
if (bytes != NULL) {
(*env)->SetByteArrayRegion(env, bytes, 0, len,
(jbyte *)str);
result = (*env)->NewObject(env, Class_java_lang_String,
MID_String_init, bytes);
(*env)->DeleteLocalRef(env, bytes);
return result;
} /* else fall through */
return NULL;
}
把jstring转换成native chars的方法:
char *JNU_GetStringNativeChars(JNIEnv *env, jstring jstr)
{
jbyteArray bytes = 0;
jthrowable exc;
char *result = 0;
if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
return 0; /* out of memory error */
}
bytes = (*env)->CallObjectMethod(env, jstr,
MID_String_getBytes);
exc = (*env)->ExceptionOccurred(env);
if (!exc) {
jint len = (*env)->GetArrayLength(env, bytes);
result = (char *)malloc(len + 1);
if (result == 0) {
JNU_ThrowByName(env, "java/lang/OutOfMemoryError",
0);
(*env)->DeleteLocalRef(env, bytes);
return 0;
}
(*env)->GetByteArrayRegion(env, bytes, 0, len,
(jbyte *)result);
result[len] = 0; /* NULL-terminate */
} else {
(*env)->DeleteLocalRef(env, exc);
}
(*env)->DeleteLocalRef(env, bytes);
return result;
}
可用RegisterNatives方法注册jni native方法:
JNINativeMethod nm;
nm.name = "g";
/* method descriptor assigned to signature field */
nm.signature = "()V";
nm.fnPtr = g_impl;
(*env)->RegisterNatives(env, cls, &nm, 1);
这样做的好处:
• It is sometimes more convenient and more efficient to register a large number
of native method implementations eagerly, as opposed to letting the virtual
machine link these entries lazily.
• You may call RegisterNatives multiple times on a method, allowing the
native method implementation to be updated at runtime.
• RegisterNatives is particularly useful when a native application embeds a
virtual machine implementation and needs to link with a native method implementation
defined in the native application. The virtual machine would not be
able to find this native method implementation automatically because it only
searches in native libraries, not the application itself.
C/C++的不同:
in C:
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
In C++, however, you need to insert an explicit conversion:
jstring jstr = (jstring)env->GetObjectArrayElement(arr, i);
构造jni可用one-to-one map或者share stub的方式:
one-to-one means一个java调用对应一个native方法
share stub means: 通过dispatch实现
不能传递NULL or (jobject)0xFFFFFFFF给jni作为参数, 否则引起yndefine或者crash