JNI编程(二) —— 让C++和Java相互调用(1)

自己在外面偷偷的算了下,又有将近两个月没更新过blog了。趁着今天有兴致,来更新JNI编程的第二篇文章。在第一篇里,大概介绍了JNI的特点、用途和优劣。并且做一个最简单的JNI的例子,不过说实话那个例子在实际的开发中没有太大的价值,实际开发中所需要的JNI程序要远远比那个复杂。所以这一篇就来介绍下如何通过JNI实现java和C++的相互通信,来满足实际开发的需要。

所谓”通信“,其实说白了无非也就是我们所说的方法调用,在上一篇的例子里介绍了如何在Java中调用本地的DLL,其实在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

就此,我们完成了第二个方法的测试。

鉴于篇幅的关系,第三个第四个方法的解释放到下一篇里解释。突然发现这篇写的貌似有点长了,再写下去的话臭鸡蛋和番茄就飘过来了,具体的代码也会在下一篇里上传。

相关推荐