Jni使用步骤(Windows平台下):
1.创建一个类,该类中native声明的方法则表明调用本地库的本地方法(该计算机)
2.使用javac编译该类
3.利用javah产生jni的头文件
4.用本地代码(c++)实现头文件中定义的方法
5.将本地代码编译后生成的dll文件(native library)复制到之前java项目中
6.在java代码中加载本地库文件(dll文件),运行java代码,执行定义的native方法
(下面项目请参考eclipse codeforces jni Test1和 VS ConsoleApplication2.sln)
public class Test1 {
public native void sayHello();
static{
System.loadLibrary("ConsoleApplication2"); //加载C++生成的dll文件
}
public static void main(String[] args) {
Test1 k = new Test1();
k.sayHello();
}
}
本地代码:
JNIEXPORT void JNICALL Java_com_jni_Test1_sayHello(JNIEnv *env, jobject obj)
{
printf("helloWorld\n");
return ;
}
JNIEnv指的是JNIEnv结构的指针(可以调用JNI各个方法),jobject指的是Test1对象自身,即java的this指针
当本地方法作为一个静态方法时,jobject指向所在类,当本地方法作为一个实例方法时,jobject相当于对象本身
JNIEvn类中有几个函数取得jclass:
1.jclass FindClass(const char * clsName)----传入完整类名
2.jclass GetObjectClass(jobject obj) ------传入类示例
在native方法中声明的参数类型,在JNI中都有对应的类型
Java Native(jni.h) 基本数据类型
boolean jboolean
byte jbyte
char jchar
short jshort
int jint
long jlong
float jfloat
double jdouble
在jni中对象的基类是jobject,为方便起见,还定义了jstring、jclass、jobjectArray等结构,它们都继承于jobject
JNI处理异常:JNI发生异常,不会改变代码执行轨迹,所以当返回NULL,要及时返回,或马上处理
eg. JNIEnv->newStringUTF构造java.lang.String,如果此时没有足够的内存,newStringUTF将抛出OutOfMemoryError异常,同时返回NULL
在String操作函数中有很多类似这样的:如有两个函数GetStringLength和GetStringUTFLength,前者是Unicode编码长度,后者是UTF编码长度
GlobalRef:当你需要在JNI层维护一个java对象的引用,而避免该对象呗垃圾回收时,使用newGlobalRef告诉VM不要回收此对象。当本地代码最终结束对该对象的引用时,用DeleteBlobalRef释放
LocalRef:DeleteLocalRef释放对象的LocalRef
GlobalRef与LocalRef的区别:
(1)大部分JNI函数都会创建LocalRef,如NewObject创建一个实例,并返回一个纸箱该实例的LocalRef
(2)LocalRef只在本线程的native method中有效.一旦native method返回,LocalRef将被释放。不要缓存一个LocalRef(static存放该LocalRef),并企图在下次进入该JNI方法时调用,因为这时LocalRef在这之前已经被释放了
(3)释放GlobalRef前,可以在多个本地方法调用过程和多线程中使用GlobalRef所引对象,另外GlobalRef必须通过newGlobalRef由我们主动创建,而LocalRef一般自动创建(返回值为jobject/jclass等)
(4)当你不再使用GlobalRef所指对象,及时调用DeleteGlobalRef释放对象,否则,GC价格不回收该对象
两种方式令LocalRef无效:
(1)native method返回,java VM自动释放LocalRef
(2)用deleteLocalRef主动释放
java层的field和method,不管它是public,还是package、private和protected,从JNI中都可以访问到
访问对象成员步骤:
(1)通过getFieldID得到对象成员ID(实例成员,其实静态成员类似getStaticFieldID),如
fid = env->getFieldID(env, cls, "s", "Ljava/lang/String;"); //cls是通过getObjectClass从obj对象得到cls
jstr = env->getObjectField(env, obj, fid); //通过在对象上调用该方法获得成员的值
JNI sign 签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
object Ljava/lang/String;
array [I[Ljava/lang/Object;
method (参数1签名,....)返回类型签名
type[] [ type
如: long f(int n, String s, int[] arr)
签名为: "(ILjava/lang/String;[I)J"
注意:
(1)类描述符开头的'L'与结尾的';'必须要有
(2)数组描述符,开头的'['必须有
(3)方法描述符规则:"参数描述符间没有任何分隔符号
访问对象方法的步骤:
(1)通过getMethodID在给定类中查询方法,(函数参数)查询基于方法名称和签名
(2)本地方法调用CallVoidMethod,该方法表明被调用的Java方法返回值为void,如CallIntMethod
访问对象静态方法其实和访问对象方法大致一样:
(1)通过getStaticMethodID在给定类中查询方法,(函数参数)查询基于方法名称和签名
(2)本地方法调用CallStaticVoidMethod,该方法表明被调用的Java方法返回值为void,如CallStaticIntMethod
静态方法与实例方法的不同在于,前者传入参数为jclass,后者为jobject
调用被子类覆盖的父类方法:JNI支持用CallNonvirtual
(1)getMethodID获得method ID
(2)调用CallNonvirtualVoidMethod(可以调用构造函数),CallNonvirtualIntMethod
构造函数的名称都是"
cid = env->getMethodID(env, cls, "
Push/Pop LocalFrame常被用于管理LocalRef。在进入本地方法时,调用一次PushLocalFrame,并在本地方法结束时调用PopLocalFrame,这种方法执行效率非常高
PopLocalFrame会一次性释放所有的LocalRef
本地代码处理异常的两种方式:
(1)本地代码可以立即返回(遇到NULL情况,释放相应资源,然后直接return),并在调用者中处理异常
(2)当有异常发生,调用ExceptionDescribe打印调用堆栈,然后用ExceptionClear清空异常,然后自己做重新抛出等策略
JNI知识点:
__cplusplus是cpp中的自定义宏,表示一个数字,一般cpp文件都会定义了这个宏.
__stdcall调用约定用于调用Win32 API函数
在动态链接库中__declspec(dllexport)管导出,__declspec(dllimport)管导入
extern "C",还得从cpp中对函数的重载处理开始说起。在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++和C对产生的函数名字的处理是不一样的.
C++没有加extern “C” 时名字前有其他英文,而加了extern “C”名字不会改变。
extern "C"是使C++能够调用C写作的库文件的一个手段
JNI用处:
1.你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
2.在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
3.你的Java代码中需要用到某种算法,不过算法是用C实现并封装在动态链接库文件(DLL)当中的。
JNI缺点:
1.因为JNI有一个Native这个特点,一点有项目用了JNI,也就说明这个项目基本不能跨平台了,可移植性差。
2.JNI调用是相当慢的,在实际使用的之前一定要先想明白是否有这个必要。
3.因为C++和C这样的语言非常灵活,一不小心就容易出错,比如我刚刚的代码就没有写析构字符串释放内存,对于java developer来说因为有了GC 垃圾回收机制,所以大多数人没有写析构函数这样的概念。所以JNI也会增加程序中的风险,增大程序的不稳定性。