JNI视频教程 笔记(一)

来源:互联网 发布:知乎图标如何变 编辑:程序博客网 时间:2024/06/06 08:12
第1课
使用JNI的步骤:
1)首先在Java类中声明一个native的方法。
2)使用Javah命令生成包含native方法声明的C/C++头文件。
3)按照生成的C/C++头文件来写C/C++源文件
4)将C/C++源文件编译成动态链接库(DLL)。
5)将DLL路径加入到Path环境变量中。

使用JNI的两个弊端
1)使用了JNI那么这个Java Application将不能跨平台了。
2)Java是强类型语言,而C/C++不是。所以在写JNI时必须更小心。

第2课

JNIEXPORT void JNICALL java_com_MainClass_outputDate(JNIEnv *env,jobject obj)

1)JNIEnv类型实际上代表了java环境。通过这个JNIEnv*指针,就可以对Java端的代码进行操作。例如,创建Java类的对象,调用Java对象的方法,获取java对象的属性等等。
2)JNIEnv类中有很多函数可以用:
NewObject/NewString/New<TYPE>Array
Get/Set<TYPE>Field
Call<TYPE>Method/CallStatic<TYPE>Method     等很多的函数

3)第二个参数obj.要看这个方法在java中是否被定义成static类型,如果不是那这个obj就是调用这个方法的那个对象;如果是static类型,那么这个obj就是那个class类.
使用java类
为了能够在C/C++中使用Java类。JNI.h头文件中专门定义了jclass类型来表示Java中的Class类.
JNIEnv类中有几个简单的函数可以取得jclass
jclass FindClass(const char* clsName);
jclass GetObjectClass(jobject obj);
jclass GetSuperClass(jclass obj);

FindClass 会在classpath系统环境变量下寻找类,传入完整类名,注意包与包之间是用'/'而不是用'.'来分隔,如
jclass cls_string=env->FindClass("java/lang/String");
使用java对象
在C/C++本地代码中访问Java端的代码,一个常见的应用就是获取类的属性和调用 类的方法.为了在C/C++中表示属性和方法,JNI在Jni.h头文件中定义了jfieldID,jmethodID类型来分别代表java端 的属性和方法.
我们在访问或是设置java属性时,首先就要先在本地代码取得代表该java属性的jfieldID,然后才能在本地代码进行java属性操作.同样的,我们需要呼叫java端的方法时,也是需要取得代表该方法的jmethodID才能进行java方法调用.
使用JNIEnv的 GetFieldID/GetMethodID 
GetStaticFieldID/GetStaticMethodID 来取得相应的jfieldID和jmethodID.
GetFieldID(jclass clazz, const char* name, const char* sign);
GetStaticFieldID(jclass clazz, const char* name, const char* sign);
GetMethodID (jclass clazz, const char* name, const char* sign);
GetStaticMethodID (jclass clazz, const char* name, const char* sign);

GetMethodID也能够取得构造函数jmethodID,创建一个java对象进可以调用指定的构造方法,如:env->GetMethodID(data_Clazz,"<init>","()V");
而sign又是什么呢?
例如TestNative类中有两个重载方法:
package cn.itcast;public class TestNative{public void function(int i){System.out.println("Interger:"+i);}public void function(double d){System.out.println("Interger:"+d);}}
然后在C/C++代码中需要调用其中一个function方法的话.
首先要取得调用 的方法所在的类:
jclass clazz_TestNative = env->FindClass("cn/itcast/TestNative");
取得jmethodID之后才能进行调用:
jmethodID jd_func = env->GetMethodID(clazz_TestNative,"function","??");
但到底调用的是哪个方法呢?这就是sign的作用了,它用于指定要取得的属性/方法的类型.
这里的sign如果为"(I)V" 则取回的是void function(int i)的jmethodID;
        如果 是"(D)V",则取回的是void function(double)的jmethodID.

例2:使用签名取得属性/方法ID的例子
public class Hello{public int property;public int function(int foo,Date date,int[] arr){System.out.println("function");return 0;}public native void test();}//test本地方法的实现:JNIEXPORT void Java_Hello_test(JNIEnv* env, jobject obj){//因为test不是static类型,所以这里传进来的就是调用这个函数的对象,否则就传入一个jclass对象表示native方法所在的类.jclass hello_clazz=env->GetObjectClass(obj);jfieldID fieldID_prop=env->GetFileID(hello_clazz,"property","I");jmethodID methodID_func=env->GetMethodID(hello_clazz,"function","(ILjava/util/Date;[I)I");env->CallIntMethod(obj,methodID_func,OL,NULL,NULL);}

第3课
在了解了获取jfieldID和jmethodID 之后,我们就来了解如何取得java属性和设置java属性值。
1. 取得了相应的jfieldID之后就可以用 Set<TYPE>Field,Get<TYPE>Field, SetStatic<TYPE>Field,和 GetStatic<TYPE>Field等函数来对java属性进行操作了。
看下它们的定义:
 jint GetIntFieldId(jobject obj,jfieldID fieldID);  //获取obj对象的fieldID属性。
 jint SetIntFieldId(jobject obj,jfieldID fieldID,jint val ); 
 jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID);

2. 怎样获取数组属性呢?
 可以使用GetObjectField来取得数组类型的属性,
修改java的属性:
下面用代码来演示:在本地代码中修改一个int型变量的值
TestNative.java
package cn.itcast;public class TestNative{public native void sayHello();public int number=10;    //接下来在本地代码中修改这个值public static void main(String[] args) {System.loadLibrary("nativeCode");TestNative tst=new TestNative;tst.sayHello();System.out.println(tst.number); /*在这里输出的是123*/}}
source.cpp
#include "cn_itcast_TestNative.h"#include <iostream>using namespace std;JNIEXPORT void JNICALL Java_cn_itcast_testNative_sayHello(JNIEnv *env,jobject obj){jclass clazz_TestNative=env->GetObjectClass(obj); /*获得一个jclass对象*/jfiedID id_number=env->GetFieldID(clazz_TestNative,"number","I"); /*得到name为number变量的ID,它的类型是int ,所以签名为I*/jint numbler2=env->GetIntField(obj,id_number);cout<<numbler2<<endl;  /*在这里输出的是10*/env->SetIntField(obj,id_number,123L);/*jint对应到c/c++是长整数类型32位,所以加个L*/}
3. java方法的调用
1)JNIEnv 提供了从多的Call<TYPE>Moethod跟CallStatic<TYPE>Method,还有CallNonvirtual<TYPE>Method函数,需要通过GetMethodID取得相应方法的jmethodID来传入一鸡皮疙瘩述函数的参数中。
2)调用实例 方法的三种形式:
 Call<TYPE>Moethod(jobject obj, jmethodIDid,...);  //最常用的方式
  例:java:
  boolean fun1(int i,double d,char c){.....}
调用:  env->CallBooleanMethod(obj,id_fun1,100L,3.44,L'3');

100L:因为java中的int,对应到C/C++中是32位的,长整形,所以在加个L表示。
L'3':因为java中的字符是两个字节的,unicode16,所以C/C++中要使用宽字符。
 Call<TYPE>MoethodV(jobject obj, jmethodID id, va_list lst);  //当调用这个函数的时候有一个指向参数表的va_list变量时使用的
 Call<TYPE>MoethodA(jobject obj, jmethodID id, jvalue* v);  //当调用 这个函数的一个指向jvalue或jvalue数组的指针时用 的。
例:java:boolean fun1(int i,double d,char c){.....}调用:jvalue *args=new jvalue[3];args[0].i=100L;args[1].d=3.44;args[2].c=L'3';env->CallBooleanMethodA(obj,id_fun1,args);delete [] args;
代码示例:
TestNative.java
package cn.itcast;public class TestNative{public native void sayHello();public int number=10;    //接下来在本地代码中修改这个值double max(double num1,double num2){return num1>num2? num1:num2;}public static void main(String[] args) {System.loadLibrary("nativeCode");TestNative tst=new TestNative;tst.sayHello();System.out.println(tst.number); /*在这里输出的是123*/}}
source.cpp
#include "cn_itcast_TestNative.h"#include <iostream>using namespace std;JNIEXPORT void JNICALL Java_cn_itcast_testNative_sayHello(JNIEnv *env,jobject obj){jclass clazz_TestNative=env->GetObjectClass(obj); /*获得一个jclass对象*/jmethodID id_max=env->GetMethID(clazz_TestNative,"max","(DD)D");jdouble maxValue=env->CallDoubleMethod(obj,id_max,3.14,3.15);cout<<maxValue<<endl;}
这样就在C/C++中调用了java中的max方法。
使用命令行生成签名:
 在DOS中进入工程目录,>javap -s -private cn.itcast.TestNative
 要先编译这个TestNative.java才会出现max的签名。

4. CallNonvirtual<TYPE>Method
public class Father{public void fun(){System.out.println("Father:func");}}public class Child extends Father{public void fun(){System.out.println("Child:func");} }
想想这段JAVA代码调用的是哪个类的方法?
Father p=new Child();
p.fun();
调用的是Child的fun()方法。
再看下面这个C++代码:
class Father{public:(virtual) void fun(){cout<<"Father:func"<<endl;}}; class Child :public Father{public: void fun(){cout<<"Child:func"<<endl;}};
Father* p=new Child();
p->fun();
1)没有virtual时,不是虚拟函数,调用的是Father的fun()方法。
2)加上virtual时,是虚拟函数,调用的是Child的fun()方法。
1) 在JNI中定义的CallNonvirtual<TYPE>Method就能够实现子类对象调用父类方法的功能
如果想要调用一个对象的父类的方法,而不是子类的方法的话,就可以用CallNonvirtual<TYPE>Method。
2)要使用它,首先要取得父类及要调用的父类方法的jmethodID.然后传入到这个函数就能通过子类对象呼叫被覆写的父类的方法了。
示例:
Father.java,Child.java
//***********************Father.java************************************//package cn.itcast;public class Father{public void fun(){System.out.println("Father:func");}}//***********************Child.java************************************//public class Child extends Father{@overridepublic void fun(){System.out.println("Child:func");}}
TestNative.java
package cn.itcast;public class TestNative{public native void sayHello();public Father p=new Chiled();public static void main(String[] args) {System.loadLibrary("nativeCode");TestNative tst=new TestNative;tst.sayHello();}}
source.cpp
#include "cn_itcast_TestNative.h"#include <iostream>using namespace std;JNIEXPORT void JNICALL Java_cn_itcast_testNative_sayHello(JNIEnv *env,jobject obj){jfieldID id_p=env->GetFieldID(clazz_TestNative,"p","Lcn/itcast/Father;");jobject p=env->GetObjectField(obj,id_p);jclass clazz_Father=env->FindClass("cn/itcast/Father");/*传入完整类名*/jmethodID id_Father_fun=env->GetMethID(clazz_Father,"fun","()V"); /*没有参数,所以是(),返回void,所以是V*/env->CallvoidMethod(p,id_Father_fun); /*在这里打印了:Child:func,已经成功调用了该方法*/env->CallNonvirtualVoidMethod(p,clazz_Father,id_Father_fun);/*在这里打印了:Child:func,已经成功调用了父类中的方法*/}

第4课 NewObject   String
主要内容:
1. 在C/C++本地代码中创建Java的对象
2. 在C/C++本地代码中访问Java的String对象
3. 在C/C++本地代码中创建Java的String对象

1. Java对象的创建--NewObject
1) 使用函数NewObject可以创建Java对象。
jobject NewObject(jclass clazz, jmethodID methodID,...)
第一个参数,jclass就是要创建的java对象的类型。
第二个参数,指明调用哪一个构造函数。
2)GetMethodID能够取得构造方法的jmethodID 。如果传入的要取得的方法名称设定为"<init>"就能够得到构造方法。
3)构造方法没有返回值,所以其返回值类型的签名始终为void.
jclass clazz_date = env->FindClass("java/util/Data");jmethodID mid_date=env->GetMethodID(clazz_date,"<init>","()V");jobject now=env->NewObject(clazz_date,mid_date);
代码例子:
MainClass.java
public class MainClass{    public static native void outputDate();    public static void main(String[] args){        System.loadlibrary("nativeCreateDataObj");        outputDate();    }}
main.cpp
#include "xx.h"JNIEXPORT void JNICALL java_com_MainClass_outputDate(JNIEnv *env,jclass jclaxx){    jclass clazz_date = env->FindClass("java/util/Data");    jmethodID mid_date=env->GetMethodID(clazz_date,"<init>","()V");/*<init>表明调用的是构造函数*/    jobject now=env->NewObject(clazz_date,mid_date);    jmethod mid_date_getTime=env->GetMethodID(clazz_date,"getTime","()L");    jlong time=env->CallLongMethod(now,mid_date_getTime);    cout<<time<<endl;}
2.  Java字符串 <- ->  C/C++字符串
1)在Java中,使用的字符串String对象是Unicode(UTF-16)码,即每个字符不论是中文还是英文,一个字符总是占用两个字节。
2)Java通过JNI接口可以将Java的字符串转换到C/C++中的宽字符串(wchar_t *),或者传回一个UTF-8字符串(char*)到C/C++。反过来,C/C++可以通过一个宽字符串,或是一个UTF-8编码的字符串来创建一个java端的String对象。
GetStringChars
GetStringUTFChars
这两个函数用来取得与某个jstring对象相关的Java字符串,分别可以取得UTF-16编码的宽字符串(jchar *)跟UTF8编码的字符串(char *)。
const jchar* GetStringChars(jstring str,jboolean* copied)
const char* GetStringUTFChars(jstring str,jboolean* copied)

第一个参数:传入一个指向java中的string对象的jstring变量。
第二个参数:传入的是一个jboolean的指针。
这两个函数分别都会有两个不同的动作:
1.开辟新内存,然后把java中的String拷贝到这个内存中,然后返回指向这个内存的址的指针。
2. 直接返回指向Java的String的内存的指针,这个时候千万不要改变这个内存的内容,这将破坏String的Java中始终是常量这个原则。
第二个参数是用来标示是否对Java的String对象进行了拷贝的。
如果传入的这个jboolean指针不是NULL,则它会给该指针所指向的内存传入JNI_TRUE或JNI_FALSE标示是否进行了拷贝。
传入NULL表示不关心 是否拷贝字符串,它就不会给jboolean* 指向的内存赋值。
使用这两个函数取得的字符串,在不使用的时候,要使用ReleaseStringChars/ReleaseStringUTFChars来释放拷贝的内存,或是释放对Java的String对象的引用。
ReleaseStringChars(jstring jstr, const jchar * str);
ReleaseStringUTFChars(jstring jstr,const char * str);

第一个参数指定一个jstring变量,即是要释放的本地字符串的来源。
第二个对数就是本地字符串。

GetStringCtritical
为了增加直接会加指向Java字符串的指针的可能性(而不是拷贝),JDK1.2出了新的函数
const jchar* GetStringCtritical(jstring str,jboolean* copied)
void ReleaseStringCritical(jstring jstr, const jchar* str);

在GetStringCtritical/ReleaseStringCritical之间是一个关键区,在这个关键区之中绝对不能呼叫JNI的其它函数和会造成当前线程中断或会让当前线程等待的任何本地代码。否则将造成关键区代码执行期间垃圾回收器停止动作,任何触发垃圾回收器的线程也会暂停,其它的触发垃圾回收器的线程不能前进直到当前线程结束而激活垃圾回收器。

在关键区中千万不要出现中断操作,或是在JVM中分配任何新对象,否则会造成JVM死锁。

GetStringRegion
GetStringUTFRegion
这个函数的动作,是把Java字符串的内容直接拷贝到C/C++的字符数组中。
GetStringUTFRegion(jstring str,jsize start ,jsize len, char* buffer);  //拷贝Java字符串并以UTF-8编码传入buffer
GetStringRegion(jstring str, jsize start, jsize len,jchar* buffer); //拷贝Java字符串并以UTF-16编码传入buffer



1 0