1.Introduction
JNI 是 Java Native Interface 的缩写,用以解决 Java 对本地方法的操作问题,而本地方法是以库文件的形式存放的(.dll for windows,.so for Unix)。
2.JNICoding steps
1) 编写带有native声明的方法的java类
2) 使用javac命令编译所编写的java类
3) 使用javah java类名生成扩展名为h的头文件
4) 使用C/C++实现本地方法
5) 将C/C++编写的文件生成动态连接库
3. Examples
1) 编写java程序
classHelloWorld
{
/* 对将要调用的方法做本地声明,关键字为native。并且只需要声明,而不需要具体实现。*/
public native void sayHello(String aa);
/* 在类中声明所调用的库名称(这个库就是在下面 step 4 中对本地方法编译链接得到的动态库),
* 在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。
*/
static
{
System.loadLibrary("hello");
}
public static void main(String[] args)
{
obj = newHelloWorld();
obj.sayHello("Hello^-^");
}
}
2) 编译
javac HelloWorld.java,生成HelloWorld.class;
3) 生成头文件
javah HelloWorld,生成扩展名为h的头文件。
[Note]:
如果你的java class是在某个package,比如 xxx 中,这里必须在 xxx 目录的上一层使用“javah xxx.HelloWorld”,则在xxx目录的上一层会生成一个名为xxx_HelloWorld.h 的头文件。
其中,头文件的内容:
/* DO NOT EDIT THIS FILE - it is machinegenerated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_sayHello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
[Note]:
a. JNIEXPORT 和 JNICALL 都是 JNI 的关键字,表示此函数是要被 JNI 调用的。
b. jstring 是以 JNI 为中介使Java的 String 类型与本地的 char* 沟通的一种类型。
c. 函数的名称是 JAVA_ 再加上 java 程序的 package 路径再加函数名组成的。
d. 参数中,我们也只需要关心在 Java 程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
4) 编写本地方法
实现和由 javah 命令生成的头文件里面声明的方法名相同的方法,然后编译连接成库文件,再把库文件拷贝到JAVA程序的路径下面即可。
HelloWorldImp.c
1 #include <jni.h>
2 #include "HelloWorld.h"
3 #include <stdio.h>
4 JNIEXPORT jstring JNICALLJava_HelloWorld_sayHello
(JNIEnv * env, jobject obj, jstring s)
{
char * str;
str=(char*)(*env)->GetStringUTFChars(env,s, NULL);
printf("%s\n",str);
(*env)->ReleaseStringUTFChars(env, s,str);
}
[Note]:
a. 代码2中的第1行,需要将jni.h(该文件可以在%JAVA_HOME%/include文件夹下面找到)文件引入,因为在程序中的JNIEnv、jobject等类型都是在该头文件中定义的;
b. 在第2行需要将 HelloWorld.h 头文件引入。
5) 生成动态库
这里以在Windows中为例,需要生成dll文件。在保存HelloWorldImpl.c文件夹下面,使用VC的编译器cl成。
cl -I%java_home%\include -I%java_home%\include\win32-LD HelloWorldImp.c -Fehello.dll
[Note]:
生成的dll文件名在选项-Fe后面配置,这里是hello,因为在HelloWorld.java文件中我们loadLibary的时候使用的名字是hello。当然这里修改之后那里也需要修改。另外需要将-I%java_home%\include -I%java_home%\include\win32参数加上,因为在第四步里面编写本地方法的时候引入了jni.h文件。
6) 运行程序
java HelloWorld 就ok。
4. JNI 语法
以 JNI 调用 本地 C 语言为例,在JNI中,既可使用 c 函数 (直接使用),又可使用 JAVA 函数。
在JNI里使用java类的某些方法,分为以下几个步骤:
1. 查找该类:
jclass xxx = (*env)->FindClass(env, "Lclass_name;");
2. 查找该类的初始化方法:
jmethodID xxx = (*env)->GetMethodID(env, jclass,"<init>", "(M)N");
3. 查找需要调用的该类的方法:
jmethodID xxx = (*env)->GetMethodID(env, jclass, "(M)N" );
4. 初始化该类的实例:
jobject xxx = (*env)->NewObject(env, jclass, jmethodID );
5. 调用实例的某方法:
(*env)->CallObjectMethod(env, jclass, jmethodID,[parameter1, parameter2,...] );
6. 释放实例:
(*env)->DeleteLocalRef(env, xxx);
[Note]:
"(M)N"是该函数的输入和输出参数的类型签名(Type Signature)。 这里,JNI中对于以下类型定义了类型签名:
TypeSignature Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class; fully-qualified-class
[type type[]
e.g. 某函数输入double型,输出boolean型,则“(M)N”是"(D)Z",
如果输入是int型,输出是java的Object型,则“(M)N”是 "(I)Ljava/lang/Object;"
这里需注意,对于fully-qualified-class类型前面要加"L",后面加";"
e.g.
ArrayList.add : "(Ljava/lang/Object;)Z"
ArrayList.get: "(I)Ljava/lang/Object;"
ArrayList_size: "()I"
对于 String 类型的实例化方法比较特殊, 1~4 步可以用调用 JNI 提供的实例化方法NewStringUTF/NewString 取代。下面是访问String的一些方法:
◆ GetStringUTFChars 将jstring转换成为UTF-8格式的char*
◆ GetStringChars 将jstring转换成为Unicode格式的char*
◆ ReleaseStringUTFChars 释放指向UTF-8格式的char*的指针
◆ ReleaseStringChars 释放指向Unicode格式的char*的指针
◆ NewStringUTF 用 char* 创建一个UTF-8格式的 jstring 对象
◆ NewString 用char*创建一个Unicode 格式的 jstring对象
◆ GetStringUTFLengt 获取UTF-8格式的char*的长度
◆ GetStringLength 获取Unicode格式的char*的长度
e.g.
jstring aaa =(*env)->NewStringUTF(env, "bbb");
char* bbb = (*env)->GetStringUTFChars(env, aaa, NULL);
e.g: 在JNI里使用java类的实例调用该类的一些方法HashMap.init(), put(), 以及ArrayList.size(),...etc.
JNIEnv *env;
jclass clsHASHMAP;
jmethodID ArrayList_add, ArrayList_get, ArrayList_size, HashMap_put,HashMap_init;
jstring key, value;
jobject objHashMap;
/* get class. */
clsHASHMAP = (*env)->FindClass(env,"Ljava/util/HashMap;");
/* get init method for HashMap */
HashMap_init = (*env)->GetMethodID(env,clsHASHMAP,"<init>", "()V");
/* Call HashMap.init() to get a hashmap object */
objHashMap = (*env)->NewObject(env, clsHASHMAP, HashMap_init);
HashMap_put = (*env)->GetMethodID(env, *clsHASHMAP,"put","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
key = (*env)->NewStringUTF(env, "xxx");
Value = (*env)->NewStringUTF(env, "xxx");
/* put key/value to hashmap. */
(*env)->CallObjectMethod(env, objHashMap, HashMap_put, key, value);
/* 内存回收 */
(*env)->DeleteLocalRef(env, key);
(*env)->DeleteLocalRef(env, value);
(*env)->DeleteLocalRef(env, objHashMap);
e.g. /* get init method forInteger */
jclass clsINT;
jmethodID Integer_init;
jobject objINT;
clsINT = (*env)->FindClass(env,"Ljava/lang/Integer;");
Integer_init = (*env)->GetMethodID(env, clsINT, "<init>","(I)V")
objINT(*env)->NewObject(env,clsINT, Integer_init);
[Note]:
Solaris 上使用 JNI 调用JAVA某方法时,当返回值为 int 型时,返回值就会是个随意的假值。这可能是JNI的一个Bug。也就是说,在我们上面的例子中, ArrayList.size()方法尽量不要使用,不然在 Solaris 平台上会有问题。