JNI学习笔记.doc

上传人:文库蛋蛋多 文档编号:2387128 上传时间:2023-02-17 格式:DOC 页数:25 大小:391KB
返回 下载 相关 举报
JNI学习笔记.doc_第1页
第1页 / 共25页
JNI学习笔记.doc_第2页
第2页 / 共25页
JNI学习笔记.doc_第3页
第3页 / 共25页
JNI学习笔记.doc_第4页
第4页 / 共25页
JNI学习笔记.doc_第5页
第5页 / 共25页
点击查看更多>>
资源描述

《JNI学习笔记.doc》由会员分享,可在线阅读,更多相关《JNI学习笔记.doc(25页珍藏版)》请在三一办公上搜索。

1、JNI学习笔记开篇java是跨平台的语言,但是在有些时候仍需调用本地代码(通常由C、C+开发)SUN公司提供的JNI是Java平台的一个功能强大的接口,这个JNI接口提供了Java与操作系统本地代码相互调用的功能。最简单的Java调用C、C+代码的步骤HelloWorld首先在java类中声明一个native方法package .higinet.jni;public class HelloWorld public void sayHello();/ 本地方法声明。public st native atic void main(String args) 使用javah命令生成包含native方法

2、定义的C/C+头文件Jni .higinet.jni.HelloWorld:这条命令会生成一个.h的头文件:cn_com_higinet_jni_HelloWorld.h/* DO NOT EDIT THIS FILE - it is machine generated */#include /* Header for class cn_com_higinet_jni_HelloWorld */#ifndef _Included_cn_com_higinet_jni_HelloWorld#define _Included_cn_com_higinet_jni_HelloWorld#ifdef

3、_cplusplusextern C #endif/* * Class: cn_com_higinet_jni_HelloWorld * Method: sayHello * Signature: ()V */JNIEXPORT void JNICALL Java_cn_com_higinet_jni_HelloWorld_sayHello (JNIEnv *, jobject);#ifdef _cplusplus#endif#endif按照生成的C/C+头文件来写C/C+源文件【1】打开Visual Studio 2005,新建一个项目:选择Visual C+ Win32Win32控制台应用

4、程序输入项目名JavaNative确定然后选择dll空项目完成。【2】将之前生成好的.h头文件拷到JavaNative的C+项目目录之下并将之引入到解决方案的头文件目录下,然后在源文件中新建C+源文件,写具体的实现#include cn_com_higinet_jni_HelloWorld.h;#include using namespace std;JNIEXPORT void JNICALL Java_cn_com_higinet_jni_HelloWorld_sayHello (JNIEnv * evn , jobject obj)coutHello World!endl;【3】编译之,

5、出现如下错误: fatal error C1083: 无法打开包括文件:“jni.h”: No such file or directory这是因为在cn_com_higinet_jni_HelloWorld.h这个头文件中用到了jni.h这个头文件,而这个头文件在JAVAHOME/ include文件夹下面,所以将之拷贝到C+项目的目录下,但是在cn_com_higinet_jni_HelloWorld.h这里面是#include,这种方式是在系统目录下找的,因为也需要将之改为#include”jni.h”,这样就在项目目录下找了。 fatal error C1083: 无法打开包括文件:“

6、jni_md.h”: No such file or directory这是因为在系统目录下也没有jni_md.h这个头文件中,可以将JAVAHOME/include/win32下找到拷贝到项目目录下。这样编译通过将C/C+源文件译成动态连接库(DLL)点击工具栏上的生成,生成JavaNative,这样在debug目录下就会生成一个JavaNative.dll文件将DLL文件加入到PATH环境变量下。将之前生成的dll文件的目录加入的path环境变量之下java类中加载DLL,然后调用声明的native方法package .higinet.jni;public class HelloWorld

7、 public native void sayHello();/ 本地方法声明。public static void main(String args) System.loadLibrary(JavaNative);/ Exception in thread main java.lang.UnsatisfiedLinkError: no/ JavaNative in java.library.path/ Eclipse启动的时候会读取一次环境变量,之后就不再读取了,因此应该重启Eclipsenew HelloWorld().sayHello();最终打印出了: Hello World!使用JN

8、I的两个弊端使用了JNI,那么java Application将不能跨平台了,如果要移植到别的平台上,那么native代码就要重新进行编译java是强类型的语言,而C/C+不是因此写JNI代码时更为小心总之,必须在构建java程序的时候,尽量少用本地代码。本地代码访问Java代码在被调用的C/C+函数中也可以反过来访问java程序中的类javah工具生成的C/C+函数声明中,可以看到有两参数:JNIEXPORT void JNICALL Java_cn_com_higinet_jni_HelloWorld_sayHello (JNIEnv *, jobject);JNIEnv类型实际上代表了j

9、ava环境,通过这个JNIEnv *指针就可以对java端的代码进行操作,例如:创建java类的对象,调用java对象的方法,获取java对象的属性等等,JNIEnv的指针会被JNI传入到本地方法的实现函数中来对java端的代码进行操作JNIEnv类中有很多函数:NewObject/NewString/NewArrayGet/SetFieldGet/SetStaticFieldCallMethod/CallStaticMethod对于第二个参数obj,如果是非静态的方法,当调用这样的方法时,与这个方法相关联的对象的引用将传入到C/C+方法中的obj上,对于静态方法,obj指的代表那个类的cla

10、ss对象Java类型在C/C+中的映射关系Jclass的取得 为了能够在C/C+中使用java类,jni.h头文件中专门定义了jclass类型来表示java中的Class类。typedef _jclass *jclass; JNIEnv类中有如下几个简单的函数可以取得jclass:jclass FindClass(const char *name) jclass GetObjectClass(jobject obj) jclass GetSuperclass(jclass sub) FindClass会在classpath系统环境变量下寻找类传入完整类名,注意包与包之间是用”/”而不再是”.”

11、来分隔,如:jclass cls_string = envfindClass(“java/lang/String”);访问java类中的属性和方法 在C/C+本地代码访问java端的代码,一个常见的问题就是获取类的属性和调用类的方法,为了在C/C+中表示属性和方法,JNI在jni.h头文件中定义了jfieldID,jmethodID类型分别代表java端的属性和方法。 我们在访问或是设置java属性的时候,首先就要在本地代码获取代表该java类属性的jfieldID ,然后才能在本地进行java属性操作,同样的,我们需要呼叫java端的方法时,也是需要取得该方法jmethodID才能进行jav

12、a方法调用 使用JNIEnv的n GetFieldID/GetMethodIDn GetStaticFieldID/GetStaticMethodID来取得相应的jfieldID和jmethodID GetFieldID(jclass clazz,const char*name,const char*sign); GetStaticFieldID(jclass clazz,const char*name,const char*sign); GetMethod ID(jclass clazz,const char*name,const char*sign); GetStaticMethodID(

13、jclass clazz,const char*name,const char*sign); GetMethodID也能取得构造函数的methodID,创建一个java对象时可以调用指定的构造方法,如:envGetMethodID(data_class,”,”()V”); 类似java的Reflect需要指定类跟属性/方法名来取得相应的jfieldID跟jmethodID,而sign又是什么呢?Sign是什么?例如TestNative类中有两个重载的方法:public class TestNativepublic void function(int i)System.out.println(“

14、 Integer:“+i);public void function(double j)System.out.println(“double:”+j);/要取得其中的一个方法,我们首先要取得这个方法的类Jclass clazz_TestNative = envfindclass(“TestNative”);/类的完整类名/取得jmethodID之后才能调用Jmethod id_fun = envGetMethodID(class_TestNative,”function”,”?”);但是这样取得的是哪一个呢?因为有重载,所以确定不了,这就需要sign来指定类型了。即:sign如果指定为”(I)

15、V”;则取回void function(int)的jmethodID如果指定为”(D)V”则取回void function(double)的jmethodIDSign签名用来表示要取得的属性/方法的类型使用签名取得属性/方法ID的例子import java.util.Date;public class Hello public int property;public int function(int foo, Date date, int arr) System.out.println(funtion);return 0;public native void test();/test本地方法的

16、实现JNIEXPORT void JNICALL Java_Hello_test (JNIEnv * evn , jobject obj)/因为test不是静态函数,所以传进来的就是调用这个类的函数的对象/否则就传入一个jclass对象表示native方法所在的类jclass hello_clazz = env-GetObjectClass(obj);jfieldID fieldID_prop = env-GetFieldID(hello_clazz,property,I);jmethodID methodID_func = env-GetMethodID(hello_clazz,functi

17、on,(ILjava/util/Date;I)I );env-CallIntMethod(obj,methodID_func,0L,NULL,NULL);/invoke!解说:取得的property是int类型的,所以在签名中传入I取得function的ID时由于这样的签名是难以记忆的,所以:使用javap命令行来签名JDK也提供了一个工具javap来查看一个类的声明,其中就可以设置输出的每个方法/属性的签名比如使用:javap -s -public .higinet.jni.HelloWorld命令后:取得java属性/设计java属性值取得了相应属性的jfield之后就可以用Set Fie

18、ld,Get Field,SetStaticField跟GetStaticField等函数对java属性进行操作了。这些函数在jni.h中都有定义,如:jint GetIntField(jobject obj, jfieldID fieldID) return functions-GetIntField(this,obj,fieldID);怎样获取数组属性?可以使用GetObjectField来取得数组类型的属性。下面写一个例子,在java文件中定义一个变量,然后在C中修改它,打印出来。public class HelloWorld /以下定义了一个int类型的变量number,我们要在c中修

19、改它,然后打印。public int number = 10 ;在C中:#include cn_com_higinet_jni_HelloWorld.h;#include using namespace std;JNIEXPORT void JNICALL Java_cn_com_higinet_jni_HelloWorld_sayHello (JNIEnv * env , jobject obj)jclass helloworld_clazz = env-GetObjectClass(obj);jfieldID number_fieldID = env-GetFieldID(hellowor

20、ld_clazz,number,I);jint number = env-GetIntField(obj,number_fieldID);cout number SetIntField(obj,number_fieldID,100L);/注意,java中的int对应本地的Long类型,所以要在后面加上L然后在java中打出来:hw.sayHello();System.out.println(hw.number);Java方法的调用JNIEnv提供了众多的CallMethod跟CallStaticMethod还有CallNonVirtualMethod函数,需要通过GetMethodID取得相应

21、的方法的jmethodID来传入到上述函数中。调用实例方法的三种形式:1、常用方式:CallMethod(jobject obj,jmethodID id,)2、当调用这个的时候有一个指向参数表的va_list变量时使用CallMethodV(jobject obj,jmethodID id,va_list lst );3、当调用这个函数时候有一个指向jvalue或jvalue数组的指针时使用CallMethodA(jobject obj,jmethodID id,jvalue* v);如:在java中的一个方法:public boolean function(int I,double d,c

22、har c)则在C中如下调用:env-CallBooleanMethod(obj,id_function,100L,3.44,La);解释:因为java中的int对应本地的long所以100后面加一个L,因为java中的char是Unicode,是宽字符,所以要La这样写。调用静态方法的三种形式:CallStaticMethod(jclass clazz,jmethodID id,);CalStaticMethodV(jclass clazz,jmethodID id,va_list lst);CallStaticMethodA(jclass clazz, jmethodID id,jvalu

23、e * vars);现在写一个例子,用来展示在C中调用java函数:首先在java中写一个这样的函数:/返回两者的最在值。public double max(double num1,double num2)return num1 num2?num1:num2;然后在C中写:jclass helloworld_clazz = env-GetObjectClass(obj);jmethodID id_max = env-GetMethodID(helloworld_clazz,max,(DD)D);jdouble d = env-CallDoubleMethod(obj,id_max,20.2,3

24、0.2);coutdendl;然后在java中运行即可打印最大的值。CallNonvirtual Method有如下java代码:public class Fatherpublic void function()System.out.println(“Father”);public class Child extends Fatherpublic void function()System.out.println(“Child”);则Father p = new Child();p.function();肯定会调用子类的方法而无法调用父类的方法。但是在C+中是可以的。如下C+代码:class

25、Fatherpublic:void function()cout”father”endl;class Child:public Fatherpublic :void function()cout”father”endl;Father * p = new Child();pfunction();上面的代码会调用父类的方法。如果改为如下的:class Fatherpublic:virtual void function()cout”father”endl;class Child:public Fatherpublic :void function()cout”father”endl;Father

26、* p = new Child();pfunction();则会调用子类的方法。在JNI中定义的CallNonvirtualMethod就能够实现子类对象调用父类方法的功能,如果想要调用一个对象的父类的方法而不是子类的这个方法的话,就可以使用CallNonvirtualMethod.要使用它,首先要取得父类及要调用的父类的jmethodID,然后传入到这个函数就能通过子类对象呼叫被覆盖(override)的父类的方法。在java文件中:public class Father public void function()System.out.println(father.);public cla

27、ss Child extends Father Overridepublic void function() System.out.println(child.);public class HelloWorld public native void sayHello();/ 本地方法声明。/试图用子类的实例去执行父类被覆盖的方法Father p = new Child();那么在本地方法中:jclass helloworld_clazz = env-GetObjectClass(obj);jfieldID id_p = env-GetFieldID(helloworld_clazz,p,Lcn

28、/com/higinet/jni/Father;);jobject p = env-GetObjectField(obj,id_p);jclass clazz_Father = env-FindClass(cn/com/higinet/jni/Father);jmethodID id_Father_function = env-GetMethodID(clazz_Father,function,()V);env-CallVoidMethod(p,id_Father_function);这样将调用java中子类Child的函数function,那么如何调用父类的function方法呢?如果改为:

29、env-CallNonvirtualVoidMethod(p,clazz_Father,id_Father_function);将会调用父类中的方法。 在C/C+本地代码中创建 Java对象 在C/C+本地代码中访问Java的String字符串对象 在C/C+本地代码中创建Java的String字符串对象Java对象的创建NewObject 使用函数NewObject可以用来创建Java对象 GetMethodID能够取得构造方法的jmethodID,如果传入的要取得方法的名称设定为”就能取得构造方法 构造方法的返回值类型为voidjobject NewObject(jclass clazz,

30、 jmethodID methodID, .)例子:Jclass clazz_date = env-FindClass(“java/util/Date”);jmethodID mid_date = GetMethodID(clazz_date,”,”()V”);jobject now = env-NewObject(clazz_date,mid_date);例子代码如下:1、 写如下java代码:package .higinet.jni;import java.util.Date;/* * 在本地实现java类的实例化 */public class JavaNative2 public sta

31、tic native void outputDate();public static void main(String args) 2、之后由javah命令生成头文件:javah .higinet.jni.JavaNative23、生成头文件后新建C+项目:JavaNative,将头文件导入进来并进行源码编写#include cn_com_higinet_jni_JavaNative2.h#include using namespace std;JNIEXPORT void JNICALL Java_cn_com_higinet_jni_JavaNative2_outputDate(JNIEn

32、v *env, jclass jclazz)jclass clazz_date = env-FindClass(java/util/Date);jmethodID mid_date = env-GetMethodID(clazz_date,()V);jobject now = env-NewObject(clazz_date,mid_date);jmethodID time_date = env-GetMethodID(clazz_date,getTime,()J);/得到getTime()方法jlong t = env-CallLongMethod(now,time_date);cout t

33、 FindClass(“java/lang/String”);jmethodID methodID_str = env-GetMethodID(class_str,”,”C)V”);/预先创建一个没有初始化的字符串jobject string = env-AllocObject(clazz_str);/创建一个4个元素的字符串数组,然后以清 原 卓 也 赋值jcharArray arg = env-NewCharArray(4);env-SetCharArrayRegion(arg,0,4,L”清原卓也”);/呼叫构造方法env-CallNonvirtualVoidMethod(string,

34、clazz_str,methodID_str ,str);jc lass clazz_this = env-GetObjectClass(obj);/这里假设这个对象的类中没有定义static String STATIC_STRjfieldID fieldID_str = env-GetStaticFieldID(clazz_this,”STATIC_STR”,”L/java/lang/String”);env-SetStaticObjectField(clazz_str,fieldID_str,String);Java字符串C/C+的字符串在java中,使用的字符串String对象是Unic

35、ode(UTF-16)码,即每个字符不论是中文还是英文还是符号,一个字符总是占两个字节Java通过JNI接口可以将Java的字符串转换到C/C+中的宽字体串(wchar_t*),或是传回到一个UTF-8的字符串(char*)到C/C+ ,反过来C/C+可以通过一个宽字符串或是一个UTF-8编码的字符串创建一个Java端的String对象。相关函数介绍:GetStringCharsGetStringUTFChars这两个函数用来取得某个jstring对象相关的Java字符串,分别可以取得UTF-16编码的宽字符串(jchar*)跟UTF8编码的字符串(char*)const jchar* Get

36、StringChars(jstring str,jbolean * copied)const char* GetStringUTFChars(jstring str,jboolean * copied)第一个参数传入一个指向Java中的String对象的jstring变量第二个参数传入的是一个jboolean的指针这两个函数分别都会有两个不同的动作1、 开辟新内存,然后把Java中的String拷贝到这个内存中,然后返回一个指向这个内存的指针2、 直接返回指向Java中String的内存的指针,这个时候千万不要改变这个内存的内容,这将破坏String在Java中始终不变这个原则第二个参数是用来

37、标识是否对Java的String对象进行了拷贝的如果传入的这个jboolean指针不是NULL,则他会给该指针所指向的内存传入JNI_TURE或JNI_FALSE标识是否进行了拷贝,转入NULL表示不关心是否拷贝字符串,它就不会给jboolean*指向的内存赋值使用这两个函数取得的字符串,在不使用的时候,要使用ReleaseStringChars/ReleaseStringUTFChars来释放拷贝的内存,或是释放对Java的String对象的引用ReleaseStringChars(jstring jstr,const jchar* str)ReleaseStrngUTFChars(jstr

38、ing jstr,const char*str)第一个参数指向一个jstring变量,即是要释放的本地字符串的来源第二个参数就是要释放的本地字符串GetStringCritical为了增加直接返回指向Java字符串的指针的可能性(而不是拷贝),JDK1.2有了新的函数GetStringCritical/ReleaseStringCritical定义为:const jchar* GetStringCritical(jstring str,jboolean* copied)void ReleaseStringCritical(jstring jstr,const jchar*str)在GetStr

39、ingCritical/ReleaseStringCritical之间是一个关键区域,在这个关键区域中不能呼叫JNI的其他函数和会造成当前线程中断或是会让当前线程等待的任何本地代码,否则会造成关键区代码执行期间垃圾回收器停止运作,任何触发垃圾回收器的线程也会暂停,其他的触发垃圾回收器的线程不能前进直到当前线程结束而激活垃圾回收器在关键区域中千万不要出现中断操作,或是在JVM中分配任何新的对象,否则会造成JVM死锁虽说这个函数会增加直接传回指向Java字符串的指针的可能性,不过还是会根据情况传回拷贝过的字符串不支持GetStringUTFCritical,没有这个函数,由于Java字符串是UTF

40、16要转成UTF8编码始终要进行一次拷贝,所以没有这样的函数。GetStringRegionGetStringUTFRegionJava1.2出来的函数,这个函数的动作,是把Java字符串的内容直接拷贝到C/C+的字符串数组中,在呼叫这个函数之前必须有一个C/C+分配出来的字符串,然后传入到这个函数中进行字符串的拷贝由于C/C+中分配的内存开销相对小,而且Java中的String内容拷贝的开销可以忽略,更好的一点是此函数不分配内存,不会招聘OutOfMemoryError异常拷贝Java字符串并以UTF-8传入bufferGetStringUTFRegion(jstring str,jsize

41、 start,jsize len,char * buffer);拷贝Java字符串并以UTF16编码传入bufferGetStringRegion(jstring str,jsize start,jsize len,jchar* buffer);其他的字符串函数:jstring NewString(const jchar* str,jsize len)jstring NewStringUTF(const char* str)jsize GetStringLength(jstrng str)jsize GetStringUTFLength(jstring str)实例:1、 在java中申明一个

42、字符串变量message和输出函数如下:package .higinet.jni;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;/* * java与c/c+字符串的转换 * author xuyanhua */public class JNString public String message ;public native void outputMsg();public static void main(String args) throws IOExcept

43、ion 2、 写如下代码在C程序中:include cn_com_higinet_jni_JNString.h#include windows.h#include using namespace std;JNIEXPORT void JNICALL Java_cn_com_higinet_jni_JNString_outputMsg (JNIEnv *env, jobject obj)jfieldID fid_msg = env-GetFieldID(env-GetObjectClass(obj),message,Ljava/lang/String;);/得到jfieldIDjstring j_msg = (jstring)env-GetObjectField(obj,fid_msg);/const jchar* jstr = env-GetStringChars(j_msg,NULL);MessageBoxW(NULL,(const wchar_t*)jstr,LTitle,MB_OK);env-ReleaseStringChars(j_msg,jstr);上面代码的含义是将取得的字符串转换为C中的宽字符

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 建筑/施工/环境 > 项目建议


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号