JNI编程 —— 让C++和Java相互调用
JNI编程——让C++和Java相互调用
JNI其实是JavaNativeInterface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其他语言的通信(主要是C&C++)。也许不少人觉得Java已经足够强大,为什么要需要JNI这种东西呢?我们知道Java是一种平台无关性的语言,平台对于上层的java代码来说是透明的,所以在多数时间我们是不需要JNI的,但是假如你遇到了如下的三种情况之一呢?
1.你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
2.在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
3.你的Java代码中需要用到某种算法,不过算法是用C实现并封装在动态链接库文件(DLL)当中的。
对于上述的三种情况,如果没有JNI的话,那就会变得异常棘手了。就算找到解决方案了,也是费时费力。其实说到底还是会增加开发和维护的成本。
说了那么多一通废话,现在进入正题。看过JDK源代码的人肯定会注意到在源码里有很多标记成native的方法。这些个方法只有方法签名但是没有方法体。其实这些naive方法就是我们说的javanativeinterface。他提供了一个调用(invoke)的接口,然后用C或者C++去实现。我们首先来编写这个“桥梁”.我自己的开发环境是j2sdk1.4.2_15+eclipse3.2+VC++6.0,先在eclipse里建立一个HelloFore的Java工程,然后编写下面的代码。
Java代码
1.packagecom.chnic.jni;
2.
3.publicclassSayHellotoCPP{
4.
5.publicSayHellotoCPP(){
6.}
7.publicnativevoidsayHello(Stringname);
8.}
packagecom.chnic.jni;
publicclassSayHellotoCPP{
publicSayHellotoCPP(){
}
publicnativevoidsayHello(Stringname);
}
一般的第一个程序总是HelloWorld。今天换换口味,把world换成一个名字。我的native本地方法有一个String的参数。会传递一个name到后台去。本地方法已经完成,现在来介绍下javah这个方法,接下来就要用javah方法来生成一个相对应的.h头文件。
javah是一个专门为JNI生成头文件的一个命令。CMD打开控制台之后输入javah回车就能看到javah的一些参数。在这里就不多介绍我们要用的是-jni这个参数,这个参数也是默认的参数,他会生成一个JNI式的.h头文件。在控制台进入到工程的根目录,也就是HelloFore这个目录,然后输入命令。
Java代码
1.javah-jnicom.chnic.jni.SayHellotoCPP
javah-jnicom.chnic.jni.SayHellotoCPP
命令执行完之后在工程的根目录就会发现com_chnic_jni_SayHellotoCPP.h这个头文件。在这里有必要多句嘴,在执行javah的时候,要输入完整的包名+类名。否则在以后的测试调用过程中会发生java.lang.UnsatisfiedLinkError这个异常。
到这里java部分算是基本完成了,接下来我们来编写后端的C++代码。(用C也可以,只不过cout比printf用起来更快些,所以这里俺偷下懒用C++)打开VC++首先新建一个Win32Dynamic-Linklibrary工程,之后选择AnemptyDLLproject空工程。在这里我C++的工程是HelloEnd,把刚刚生成的那个头文件拷贝到这个工程的根目录里。随便用什么文本编辑器打开这个头文件,发现有一个如下的方法签名。
Cpp代码
1./*
2.*Class:com_chnic_jni_SayHellotoCPP
3.*Method:sayHello
4.*Signature:(Ljava/lang/String;)V
5.*/
6.JNIEXPORTvoidJNICALLJava_com_chnic_jni_SayHellotoCPP_sayHello
7.(JNIEnv*,jobject,jstring);
/*
*Class:com_chnic_jni_SayHellotoCPP
*Method:sayHello
*Signature:(Ljava/lang/String;)V
*/
JNIEXPORTvoidJNICALLJava_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv*,jobject,jstring);
仔细观察一下这个方法,在注释上标注类名、方法名、签名(Signature),至于这个签名是做什么用的,我们以后再说。在这里最重要的是Java_com_chnic_jni_SayHellotoCPP_sayHello这个方法签名。在Java端我们执行sayHello(Stringname)这个方法之后,JVM就会帮我们唤醒在DLL里的Java_com_chnic_jni_SayHellotoCPP_sayHello这个方法。因此我们新建一个C++sourcefile来实现这个方法。
Cpp代码
1.#include<iostream.h>
2.#include"com_chnic_jni_SayHellotoCPP.h"
3.
4.
5.JNIEXPORTvoidJNICALLJava_com_chnic_jni_SayHellotoCPP_sayHello
6.(JNIEnv*env,jobjectobj,jstringname)
7.{
8.constchar*pname=env->GetStringUTFChars(name,NULL);
9.cout<<"Hello,"<<pname<<endl;
10.}
#include<iostream.h>
#include"com_chnic_jni_SayHellotoCPP.h"
JNIEXPORTvoidJNICALLJava_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv*env,jobjectobj,jstringname)
{
constchar*pname=env->GetStringUTFChars(name,NULL);
cout<<"Hello,"<<pname<<endl;
}
因为我们生成的那个头文件是在C++工程的根目录不是在环境目录,所以我们要把尖括号改成单引号,至于VC++的环境目录可以在Tools->Options->Directories里设置。F7编译工程发现缺少jni.h这个头文件。这个头文件可以在%JAVA_HOME%\include目录下找到。把这个文件拷贝到C++工程目录,继续编译发现还是找不到。原来是因为在我们刚刚生成的那个头文件里,jni.h这个文件是被#include<jni.h>引用进来的,因此我们把尖括号改成双引号#include"jni.h",继续编译发现少了jni_md.h文件,接着在%JAVA_HOME%\include\win32下面找到那个头文件,放入到工程根目录,F7编译成功。在Debug目录里会发现生成了HelloEnd.dll这个文件。
这个时候后端的C++代码也已经完成,接下来的任务就是怎么把他们连接在一起了,要让前端的java程序“认识并找到”这个动态链接库,就必须把这个DLL放在windowspath环境变量下面。有两种方法可以做到:
1.把这个DLL放到windows下面的sysytem32文件夹下面,这个是windows默认的path
2.复制你工程的Debug目录,我这里是C:\ProgramFiles\MicrosoftVisualStudio\MyProjects\HelloEnd\Debug这个目录,把这个目录配置到Uservariable的Path下面。重启eclipse,让eclipse在启动的时候重新读取这个path变量。
比较起来,第二种方法比较灵活,在开发的时候不用来回copydll文件了,节省了很多工作量,所以在开发的时候推荐用第二种方法。在这里我们使用的也是第二种,eclipse重启之后打开SayHellotoCPP这个类。其实我们上面做的那些是不是是让JVM能找到那些DLL文件,接下来我们要让我们自己的java代码“认识”这个动态链接库。加入System.loadLibrary("HelloEnd");这句到静态初始化块里。
Java代码
1.packagecom.chnic.jni;
2.
3.publicclassSayHellotoCPP{
4.
5.static{
6.System.loadLibrary("HelloEnd");
7.}
8.publicSayHellotoCPP(){
9.}
10.publicnativevoidsayHello(Stringname);
11.
12.}
packagecom.chnic.jni;
publicclassSayHellotoCPP{
static{
System.loadLibrary("HelloEnd");
}
publicSayHellotoCPP(){
}
publicnativevoidsayHello(Stringname);
}
这样我们的代码就能认识并加载这个动态链接库文件了。万事俱备,只欠测试代码了,接下来编写测试代码。
Java代码
1.SayHellotoCPPshp=newSayHellotoCPP();
2.shp.sayHello("World");
SayHellotoCPPshp=newSayHellotoCPP();
shp.sayHello("World");
我们不让他直接Hello,World。我们把World传进去,执行代码。发现控制台打印出来Hello,World这句话。就此一个最简单的JNI程序已经开发完成。也许有朋友会对CPP代码里的
Cpp代码
1.constchar*pname=env->GetStringUTFChars(name,NULL);
constchar*pname=env->GetStringUTFChars(name,NULL);
这句有疑问,这个GetStringUTFChars就是JNI给developer提供的API,我们以后再讲。在这里不得不多句嘴。
1.因为JNI有一个Native这个特点,一点有项目用了JNI,也就说明这个项目基本不能跨平台了。
2.JNI调用是相当慢的,在实际使用的之前一定要先想明白是否有这个必要。
3.因为C++和C这样的语言非常灵活,一不小心就容易出错,比如我刚刚的代码就没有写析构字符串释放内存,对于javadeveloper来说因为有了GC垃圾回收机制,所以大多数人没有写析构函数这样的概念。所以JNI也会增加程序中的风险,增大程序的不稳定性。
其实在Java代码中,除了对本地方法标注native关键字和加上要加载动态链接库之外,JNI基本上是对上层coder透明的,上层coder调用那些本地方法的时候并不知道这个方法的方法体究竟是在哪里,这个道理就像我们用JDK所提供的API一样。所以在Java中使用JNI还是很简单的,相比之下在C++中调用java,就比前者要复杂的多了。
现在来介绍下JNI里的数据类型。在C++里,编译器会很据所处的平台来为一些基本的数据类型来分配长度,因此也就造成了平台不一致性,而这个问题在Java中则不存在,因为有JVM的缘故,所以Java中的基本数据类型在所有平台下得到的都是相同的长度,比如int的宽度永远都是32位。基于这方面的原因,java和c++的基本数据类型就需要实现一些mapping,保持一致性。下面的表可以概括:
Java类型本地类型JNI中定义的别名
intlongjint
long_int64jlong
bytesignedcharjbyte
booleanunsignedcharjboolean
charunsignedshortjchar
shortshortjshort
floatfloatjfloat
doubledoublejdouble
Object_jobject*jobject
上面的表格是我在网上搜的,放上来给大家对比一下。对于每一种映射的数据类型,JNI的设计者其实已经帮我们取好了相应的别名以方便记忆。如果想了解一些更加细致的信息,可以去看一些jni.h这个头文件,各种数据类型的定义以及别名就被定义在这个文件中。
了解了JNI中的数据类型,下面就来看这次的例子。这次我们用Java来实现一个前端的market(以下就用Foreground代替)用CPP来实现一个后端factory(以下用backend代替)。我们首先还是来编写包含本地方法的java类。
Java代码
1.packagecom.chnic.service;
2.
3.importcom.chnic.bean.Order;
4.
5.publicclassBusiness{
6.static{
7.System.loadLibrary("FruitFactory");
8.}
9.
10.publicBusiness(){
11.
12.}
13.
14.publicnativedoublegetPrice(Stringname);
15.publicnativeOrdergetOrder(Stringname,intamount);
16.publicnativeOrdergetRamdomOrder();
17.publicnativevoidanalyzeOrder(Orderorder);
18.
19.publicvoidnotification(){
20.System.out.println("Gotanotification.");
21.}
22.
23.publicstaticvoidnotificationByStatic(){
24.System.out.println("Gotanotificationinastaticmethod.");
25.}
26.}
packagecom.chnic.service;
importcom.chnic.bean.Order;
publicclassBusiness{
static{
System.loadLibrary("FruitFactory");
}
publicBusiness(){
}
publicnativedoublegetPrice(Stringname);
publicnativeOrdergetOrder(Stringname,intamount);
publicnativeOrdergetRamdomOrder();
publicnativevoidanalyzeOrder(Orderorder);
publicvoidnotification(){
System.out.println("Gotanotification.");
}
publicstaticvoidnotificationByStatic(){
System.out.println("Gotanotificationinastaticmethod.");
}
}
这个类里面包含4个本地方法,一个静态初始化块加载将要生成的dll文件。剩下的方法都是很普通的java方法,等会在backend中回调这些方法。这个类需要一个名为Order的JavaBean。
Java代码
1.packagecom.chnic.bean;
2.
3.publicclassOrder{
4.
5.privateStringname="Fruit";
6.privatedoubleprice;
7.privateintamount=30;
8.
9.publicOrder(){
10.
11.}
12.
13.publicintgetAmount(){
14.returnamount;
15.}
16.
17.publicvoidsetAmount(intamount){
18.this.amount=amount;
19.}
20.
21.publicStringgetName(){
22.returnname;
23.}
24.
25.publicvoidsetName(Stringname){
26.this.name=name;
27.}
28.
29.publicdoublegetPrice(){
30.returnprice;
31.}
32.
33.publicvoidsetPrice(doubleprice){
34.this.price=price;
35.}
36.}
packagecom.chnic.bean;
publicclassOrder{
privateStringname="Fruit";
privatedoubleprice;
privateintamount=30;
publicOrder(){
}
publicintgetAmount(){
returnamount;
}
publicvoidsetAmount(intamount){
this.amount=amount;
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
publicdoublegetPrice(){
returnprice;
}
publicvoidsetPrice(doubleprice){
this.price=price;
}
}
JavaBean中,我们为两个私有属性赋值,方便后面的例子演示。到此为止除了测试代码之外的Java端的代码就全部高调了,接下来进行生成.h头文件、建立C++工程的工作,在这里就一笔带过,不熟悉的朋友请回头看第一篇。在工程里我们新建一个名为Foctory的C++sourcefile文件,去实现那些native方法。具体的代码如下。
Cpp代码
1.#include<iostream.h>
2.#include<string.h>
3.#include"com_chnic_service_Business.h"
4.
5.jobjectgetInstance(JNIEnv*env,jclassobj_class);
6.
7.JNIEXPORTjdoubleJNICALLJava_com_chnic_service_Business_getPrice(JNIEnv*env,
8.jobjectobj,
9.jstringname)
10.{
11.constchar*pname=env->GetStringUTFChars(name,NULL);
12.cout<<"Beforerelease:"<<pname<<endl;
13.
14.if(strcmp(pname,"Apple")==0)
15.{
16.env->ReleaseStringUTFChars(name,pname);
17.cout<<"Afterrelease:"<<pname<<endl;
18.return1.2;
19.}
20.else
21.{
22.env->ReleaseStringUTFChars(name,pname);
23.cout<<"Afterrelease:"<<pname<<endl;
24.return2.1;
25.}
26.}
27.
28.
29.JNIEXPORTjobjectJNICALLJava_com_chnic_service_Business_getOrder(JNIEnv*env,
30.jobjectobj,
31.jstringname,
32.jintamount)
33.{
34.jclassorder_class=env->FindClass("com/chnic/bean/Order");
35.jobjectorder=getInstance(env,order_class);
36.
37.jmethodIDsetName_method=env->GetMethodID(order_class,"setName","(Ljava/lang/String;)V");
38.env->CallVoidMethod(order,setName_method,name);
39.
40.jmethodIDsetAmount_method=env->GetMethodID(order_class,"setAmount","(I)V");
41.env->CallVoidMethod(order,setAmount_method,amount);
42.
43.returnorder;
44.}
45.
46.JNIEXPORTjobjectJNICALLJava_com_chnic_service_Business_getRamdomOrder(JNIEnv*env,
47.jobjectobj)
48.{
49.jclassbusiness_class=env->GetObjectClass(obj);
50.jobjectbusiness_obj=getInstance(env,business_class);
51.
52.jmethodIDnotification_method=env->GetMethodID(business_class,"notification","()V");
53.env->CallVoidMethod(obj,notification_method);
54.
55.jclassorder_class=env->FindClass("com/chnic/bean/Order");
56.jobjectorder=getInstance(env,order_class);
57.jfieldIDamount_field=env->GetFieldID(order_class,"amount","I");
58.jintamount=env->GetIntField(order,amount_field);
59.cout<<"amount:"<<amount<<endl;
60.returnorder;
61.}
62.
63.
64.JNIEXPORTvoidJNICALLJava_com_chnic_service_Business_analyzeOrder(JNIEnv*env,
65.jclasscls,
66.jobjectobj)
67.{
68.jclassorder_class=env->GetObjectClass(obj);
69.jmethodIDgetName_method=env->GetMethodID(order_class,"getName","()Ljava/lang/String;");
70.jstringname_str=static_cast<jstring>(env->CallObjectMethod(obj,getName_method));
71.constchar*pname=env->GetStringUTFChars(name_str,NULL);
72.
73.cout<<"NameinJava_com_chnic_service_Business_analyzeOrder:"<<pname<<endl;
74.jmethodIDnotification_method_static=env->GetStaticMethodID(cls,"notificationByStatic","()V");
75.env->CallStaticVoidMethod(cls,notification_method_static);
76.
77.}
78.
79.jobjectgetInstance(JNIEnv*env,jclassobj_class)
80.{
81.jmethodIDconstruction_id=env->GetMethodID(obj_class,"<init>","()V");
82.jobjectobj=env->NewObject(obj_class,construction_id);
83.returnobj;
84.}
#include<iostream.h>
#include<string.h>
#include"com_chnic_service_Business.h"
jobjectgetInstance(JNIEnv*env,jclassobj_class);
JNIEXPORTjdoubleJNICALLJava_com_chnic_service_Business_getPrice(JNIEnv*env,
jobjectobj,
jstringname)
{
constchar*pname=env->GetStringUTFChars(name,NULL);
cout<<"Beforerelease:"<<pname<<endl;
if(strcmp(pname,"Apple")==0)
{
env->ReleaseStringUTFChars(name,pname);
cout<<"Afterrelease:"<<pname<<endl;
return1.2;
}
else
{
env->ReleaseStringUTFChars(name,pname);
cout<<"Afterrelease:"<<pname<<endl;
return2.1;
}
}
JNIEXPORTjobjectJNICALLJava_com_chnic_service_Business_getOrder(JNIEnv*env,
jobjectobj,
jstringname,
jintamount)
{
jclassorder_class=env->FindClass("com/chnic/bean/Order");
jobjectorder=getInstance(env,order_class);
jmethodIDsetName_method=env->GetMethodID(order_class,"setName","(Ljava/lang/String;)V");
env->CallVoidMethod(order,setName_method,name);
jmethodIDsetAmount_method=env->GetMethodID(order_class,"setAmount","(I)V");
env->CallVoidMethod(order,setAmount_method,amount);
returnorder;
}
JNIEXPORTjobjectJNICALLJava_com_chnic_service_Business_getRamdomOrder(JNIEnv*env,
jobjectobj)
{
jclassbusiness_class=env->GetObjectClass(obj);
jobjectbusiness_obj=getInstance(env,business_class);
jmethodIDnotification_method=env->GetMethodID(business_class,"notification","()V");
env->CallVoidMethod(obj,notification_method);
jclassorder_class=env->FindClass("com/chnic/bean/Order");
jobjectorder=getInstance(env,order_class);
jfieldIDamount_field=env->GetFieldID(order_class,"amount","I");
jintamount=env->GetIntField(order,amount_field);
cout<<"amount:"<<amount<<endl;
returnorder;
}
JNIEXPORTvoidJNICALLJava_com_chnic_service_Business_analyzeOrder(JNIEnv*env,
jclasscls,
jobjectobj)
{
jclassorder_class=env->GetObjectClass(obj);
jmethodIDgetName_method=env->GetMethodID(order_class,"getName","()Ljava/lang/String;");
jstringname_str=static_cast<jstring>(env->CallObjectMethod(obj,getName_method));
constchar*pname=env->GetStringUTFChars(name_str,NULL);
cout<<"NameinJava_com_chnic_service_Business_analyzeOrder:"<<pname<<endl;
jmethodIDnotification_method_static=env->GetStaticMethodID(cls,"notificationByStatic","()V");
env->CallStaticVoidMethod(cls,notification_method_static);
}
jobjectgetInstance(JNIEnv*env,jclassobj_class)
{
jmethodIDconstruction_id=env->GetMethodID(obj_class,"<init>","()V");
jobjectobj=env->NewObject(obj_class,construction_id);
returnobj;
}
可以看到,在我Java中的四个本地方法在这里全部被实现,接下来针对这四个方法来解释下,一些JNI相关的API的使用方法。先从第一个方法讲起吧:
1.getPrice(Stringname)
这个方法是从foreground传递一个类型为string的参数到backend,然后backend判断返回相应的价格。在cpp的代码中,我们用GetStringUTFChars这个方法来把传来的jstring变成一个UTF-8编码的char型字符串。因为jstring的实际类型是jobject,所以无法直接比较。
GetStringUTFChars方法包含两个参数,第一参数是你要处理的jstring对象,第二个参数是否需要在内存中生成一个副本对象。将jstring转换成为了一个constchar*了之后,我们用string.h中带strcmp函数来比较这两个字符串,如果传来的字符串是“Apple”的话我们返回1.2。反之返回2.1。在这里还要多说一下ReleaseStringUTFChars这个函数,这个函数从字面上不难理解,就是释放内存用的。有点像cpp里的析构函数,只不过Sun帮我们已经封装好了。由于在JVM中有GC这个东东,所以多数javacoder并没有写析构的习惯,不过在JNI里是必须的了,否则容易造成内存泄露。我们在这里在release之前和之后分别打出这个字符串来看一下效果。
粗略的解释完一些API之后,我们编写测试代码。
Java代码
1.Businessb=newBusiness();
2.System.out.println(b.getPrice("Apple"));
Businessb=newBusiness();
System.out.println(b.getPrice("Apple"));
运行这段测试代码,控制台上打出
Beforerelease:Apple
Afterrelease:��
1.2
在release之前打印出来的是我们“需要”的Apple,release之后就成了乱码了。由于传递的是Apple,所以得到1.2。测试成功。
2.getOrder(Stringname,intamount)
在foreground中可以通过这个方法让backend返回一个你“指定”的Order。所谓“指定”,其实也就是指方法里的两个参数:name和amout,在cpp的代码在中,会根据传递的两个参数来构造一个Order。回到cpp的代码里。
Java代码
1.jclassorder_class=env->FindClass("com/chnic/bean/Order");
jclassorder_class=env->FindClass("com/chnic/bean/Order");
是不是觉得这句代码似曾相识?没错,这句代码很像我们java里写的Class.forName(className)反射的代码。其实在这里FindClass的作用和上面的forName是类似的。只不过在forName中要用完整的类名,但是在这里必须用"/"来代替“.”。这个方法会返回一个jclass的对象,其实也就是我们在Java中说的类对象。
Java代码
1.jmethodIDconstruction_id=env->GetMethodID(obj_class,"<init>","()V");
2.jobjectobj=env->NewObject(obj_class,construction_id);
jmethodIDconstruction_id=env->GetMethodID(obj_class,"<init>","()V");
jobjectobj=env->NewObject(obj_class,construction_id);
拿到"类对象"了之后,按照JavaRTTI的逻辑我们接下来就要唤醒那个类对象的构造函数了。在JNI中,包括构造函数在内的所有方法都被看成Method。每个method都有一个特定的ID,我们通过GetMethodID这个方法就可以拿到我们想要的某一个java方法的ID。GetMethodID需要传三个参数,第一个是很显然jclass,第二个参数是java方法名,也就是你想取的methodID的那个方法的方法名(有些绕口),第三个参数是方法签名。
在这里有必要单独来讲一讲这个方法签名,为什么要用这个东东呢?我们知道,在Java里方法是可以被重载的,比如我一个类里有publicvoida(intarg)和publicvoida(Stringarg)这两个方法,在这里用方法名来区分方法显然就是行不通的了。方法签名包括两部分:参数类型和返回值类型;具体的格式:(参数1类型签名参数2类型签名)返回值类型签名。下面是java类型和年名类型的对照的一个表
Java类型对应的签名
booleanZ
byteB
charC
shrotS
intI
longL
floatF
doubleD
voidV
ObjectL用/分割包的完整类名;Ljava/lang/String;
Array[签名[I[Ljava/lang/String;
其实除了自己对照手写之外,JDK也提供了一个很好用的生成签名的工具javap,cmd进入控制台到你要生成签名的那个类的目录下。在这里用Order类打比方,敲入:javap-s-privateOrder。所有方法签名都会被输出,关于javap的一些参数可以在控制台下面输入javap-help查看。(做coder的毕竟还是要认几个单词的)
啰嗦了一大堆,还是回到我们刚刚的getMethodID这个方法上。因为是调用构造函数,JNI规定调用构造函数的时候传递的方法名应该为<init>,通过javap查看我们要的那个无参的构造函数的方法签是()V。得到方法签名,最后我们调用NewObject方法来生成一个新的对象。
拿到了对象,之后我们开始为对象jobject填充数值,还是首先拿到setXXX方法的MethodID,之后调用Call<Type>Method来调用java方法。这里的<Type>所指的是方法的返回类型,我们刚刚调用的是set方法的返回值是void,因此这里的方法也就是CallVoidMethod,这个方法的参数除了前两个要传入jobject和jmethodID之外还要传入要调用的那个方法的参数,而且要顺序必须一致,这点和Java的反射一模一样,在这里就不多解释。(看到这一步是不是对java反射又有了自己新的理解?)
终于介绍完了第二个方法,下来就是测试代码测试。
Java代码
1.Ordero=b.getOrder("Watermelom",100);
2.System.out.println("java:"+o.getName());
3.System.out.println("java:"+o.getAmount());
Ordero=b.getOrder("Watermelom",100);
System.out.println("java:"+o.getName());
System.out.println("java:"+o.getAmount());
控制台打出
java:Watermelom
java:100
就此,我们完成了第二个方法的测试。
3.getRamdomOrder()
这个方法会从backend得到一个随机的Order对象(抱歉这里“Random”拼错了),然后再调用java中相应的通知方法来通知foreground。getRamdomOrder方法没有参数,但是所对应的C++方法里却有两个参数,一定有人会不解。其实细心的朋友一定会发现,JNI里所有对应Java方法的C++方法都会比Java方法多两个参数,第一个参数是我们很熟悉的JNIEnv*指针,第二个参数有时是jobject有时是个jclass。针对这第二个参数在这里有必要多废话两句。
其实第二个参数传递的是包含了native本地方法的对象或者类对象,我们知道非静态的方法是属于某一个对象的,而静态方法是属于类对象的,所以静态方法可以被所有对象共享。有这个对象/类对象,我们就可以很方便的操作包含了native方法的对象的一些函数了。(这句话有点绕口,没看明白的建议多读两遍)。
废话完了言归正传,因为getRamdomOrder不是静态的,所以C++相对应的参数中传递来的是一个jobject对象。
Cpp代码
1.jclassbusiness_class=env->GetObjectClass(obj);
jclassbusiness_class=env->GetObjectClass(obj);
这一句不难理解,GetObjectClass方法可以得到一个对象的类对象,这句有点像Java中的Object.class。不熟悉的朋友建议再去看一下Java反射机制。接下来的几句C++代码应该在之前的方法1和方法2中都解释过。早backend端会发一个“消息”给foreground,之后new一个新的Order类出来。接下来的三句有必要再废话一下。
Cpp代码
1.jfieldIDamount_field=env->GetFieldID(order_class,"amount","I");
2.jintamount=env->GetIntField(order,amount_field);
3.cout<<"amount:"<<amount<<endl;
jfieldIDamount_field=env->GetFieldID(order_class,"amount","I");
jintamount=env->GetIntField(order,amount_field);
cout<<"amount:"<<amount<<endl;
之前我为Order这个Javabean的amount的属性设置了一个初始值为30,其实就是为了在这里演示如何在C++中拿一个Java对象的属性,拿的方法和我们之前说过的调用Java方法的程序差不多,也要先拿到一个jfieldID,之后调用Get<type>Field方法来取得某一个对象中的某一个属性的数值,最后cout把他打印出来。我们编写测试代码来看一下最终效果。
Java代码
1.Businessb=newBusiness();
2.Ordero2=b.getRamdomOrder();
3.System.out.println(o2.getName());
Businessb=newBusiness();
Ordero2=b.getRamdomOrder();
System.out.println(o2.getName());
运行上述的测试代码之后,控制台上打出了
Gotanotification.
amount:30
Fruit
和我们想要的结果是一样的,测试成功。
4.analyzeOrder(Orderorder)
这是一个静态方法,foreground会通过这个方法传一个Order的对象到backend去,然后再由CPP端进行“analyze”。在这里我们取出来传递过来的Order对象的name属性,然后打印到控制台上。因为这个方法是静态static方法,所以相对应的C++方法中的第二个参数也变成了jclass对象,也就是Business.class这个类对象。第三个参数是一个jobject对象,很明显就是我们传递过来的order对象。
前5句代码应该不难理解,就是调用getName这个方法,然后打印出来。因为JNI的API中并没有提供CallStringMethod这个方法,所以我们用CallObjectMethod这个方法来取得name这个字符串(String很明显也是一个Object),然后再转型成为jstring。也就是下面这句代码。
Cpp代码
1.jstringname_str=static_cast<jstring>(env->CallObjectMethod(obj,getName_method));
jstringname_str=static_cast<jstring>(env->CallObjectMethod(obj,getName_method));
取到了name这个字符串之后cout打印出来,之后调用Business这个类对象中的静态方法notificationByStatic来通知foreground。调用的流程以及方法和非静态都是一样的,只不过注意JNI中调用静态方法的API所传递的一个参数是一个jclass而非jobject(这个也不难理解,因为静态方法是属于class类对象的)
还是编写测试代码测试这个方法
Java代码
1.Businessb=newBusiness();
2.Ordero=b.getOrder("Watermelom",100);
3.Business.analyzeOrder(o);
Businessb=newBusiness();
Ordero=b.getOrder("Watermelom",100);
Business.analyzeOrder(o);
控制台上打印出
NameinJava_com_chnic_service_Business_analyzeOrder:Watermelom
Gotanotificationinastaticmethod.
第一句是C++中cout打印出来的,第二句则是Java中的静态方法打印出来的,和我们想要的结果是一致的。
呼~好不容易介绍完了4个方法,最后总结一下吧。
1.JNI中所提供的API远远不止这4个方法中所使用的API。上面介绍的都是比较常用的,本人也不可能罗列出所有的API。
2.了解了JNI编程更加有利于深入了解Java中的反射机制,反之亦然。