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,就比前者要复杂的多了。java

 

如今来介绍下JNI里的数据类型。在C++里,编译器会很据所处的平台来为一些基本的数据类型来分配长度,所以也就形成了平台不一致性,而这个问题在Java中则不存在,由于有JVM的缘故,因此Java中的基本数据类型在全部平台下获得的都是相同的长度,好比int的宽度永远都是32位。基于这方面的缘由,java和c++的基本数据类型就须要实现一些mapping,保持一致性。下面的表能够归纳:ios

 

    Java类型                 本地类型                  JNI中定义的别名    
int long jint
long _int64 jlong
byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double double jdouble
Object _jobject* jobject

 

上面的表格是我在网上搜的,放上来给你们对比一下。对于每一种映射的数据类型,JNI的设计者其实已经帮咱们取好了相应的别名以方便记忆。若是想了解一些更加细致的信息,能够去看一些jni.h这个头文件,各类数据类型的定义以及别名就被定义在这个文件中。c++

 

了解了JNI中的数据类型,下面就来看此次的例子。此次咱们用Java来实现一个前端的market(如下就用Foreground代替)用CPP来实现一个后端factory(如下用backend代替)。咱们首先仍是来编写包含本地方法的java类。编程

package com.chnic.service;  
  
import com.chnic.bean.Order;  
  
public class Business {  
    static{  
        System.loadLibrary("FruitFactory");  
    }  
      
    public Business(){  
          
    }  
      
    public native double getPrice(String name);  
    public native Order getOrder(String name, int amount);  
    public native Order getRamdomOrder();  
    public native void analyzeOrder(Order order);  
      
    public void notification(){  
        System.out.println("Got a notification.");  
    }  
      
    public static void notificationByStatic(){  
        System.out.println("Got a notification in a static method.");  
    }  
}  

 

 

这个类里面包含4个本地方法,一个静态初始化块加载将要生成的dll文件。剩下的方法都是很普通的java方法,等会在backend中回调这些方法。这个类须要一个名为Order的JavaBean。后端

package com.chnic.bean;  
  
public class Order {  
      
    private String name = "Fruit";  
    private double price;  
    private int amount = 30;  
      
    public Order(){  
          
    }  
  
    public int getAmount() {  
        return amount;  
    }  
   
    public void setAmount(int amount) {  
        this.amount = amount;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public double getPrice() {  
        return price;  
    }  
  
    public void setPrice(double price) {  
        this.price = price;  
    }  
}  

 

 

JavaBean中,咱们为两个私有属性赋值,方便后面的例子演示。到此为止除了测试代码以外的Java端的代码就所有高调了,接下来进行生成.h头文件、创建C++工程的工做,在这里就一笔带过,不熟悉的朋友请回头看第一篇。在工程里咱们新建一个名为Foctory的C++ source file 文件,去实现那些native方法。具体的代码以下。app

#include <iostream.h>  
#include <string.h>  
#include "com_chnic_service_Business.h"  
  
jobject getInstance(JNIEnv* env, jclass obj_class);  
  
JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,   
                                                                   jobject obj,   
                                                                   jstring name)  
{  
    const char* pname = env->GetStringUTFChars(name, NULL);  
    cout << "Before release: "  << pname << endl;  
  
    if (strcmp(pname, "Apple") == 0)  
    {  
        env->ReleaseStringUTFChars(name, pname);  
        cout << "After release: " << pname << endl;  
        return 1.2;  
    }   
    else  
    {  
        env->ReleaseStringUTFChars(name, pname);  
        cout << "After release: " << pname << endl;  
        return 2.1;  
    }     
}  
  
  
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,   
                                                                   jobject obj,   
                                                                   jstring name,   
                                                                   jint amount)  
{  
    jclass order_class = env->FindClass("com/chnic/bean/Order");  
    jobject order = getInstance(env, order_class);  
      
    jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V");  
    env->CallVoidMethod(order, setName_method, name);  
  
    jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V");  
    env->CallVoidMethod(order, setAmount_method, amount);  
  
    return order;  
}  
  
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,   
                                                                         jobject obj)  
{  
    jclass business_class = env->GetObjectClass(obj);  
    jobject business_obj = getInstance(env, business_class);  
  
    jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V");  
    env->CallVoidMethod(obj, notification_method);  
  
    jclass order_class = env->FindClass("com/chnic/bean/Order");  
    jobject order = getInstance(env, order_class);  
    jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");  
    jint amount = env->GetIntField(order, amount_field);  
    cout << "amount: " << amount << endl;  
    return order;  
}  
  
  
JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,   
                                                                     jclass cls,   
                                                                     jobject obj)  
{  
    jclass order_class = env->GetObjectClass(obj);  
    jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;");  
    jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));  
    const char* pname = env->GetStringUTFChars(name_str, NULL);  
  
    cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;  
    jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");  
    env->CallStaticVoidMethod(cls, notification_method_static);  
  
}  
  
jobject getInstance(JNIEnv* env, jclass obj_class)  
{  
    jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");  
    jobject obj = env->NewObject(obj_class, construction_id);  
    return obj;  
}  

 

 

能够看到,在我Java中的四个本地方法在这里所有被实现,接下来针对这四个方法来解释下,一些JNI相关的API的使用方法。先从第一个方法讲起吧:dom

 

1.getPrice(String name)函数

 

这个方法是从foreground传递一个类型为string的参数到backend,而后backend判断返回相应的价格。在cpp的代码中,咱们用GetStringUTFChars这个方法来把传来的jstring变成一个UTF-8编码的char型字符串。由于jstring的实际类型是jobject,因此没法直接比较。工具

GetStringUTFChars方法包含两个参数,第一参数是你要处理的jstring对象,第二个参数是否须要在内存中生成一个副本对象。将jstring转换成为了一个const char*了以后,咱们用string.h中带strcmp函数来比较这两个字符串,若是传来的字符串是“Apple”的话咱们返回1.2。反之返回2.1。在这里还要多说一下ReleaseStringUTFChars这个函数,这个函数从字面上不难理解,就是释放内存用的。有点像cpp里的析构函数,只不过Sun帮咱们已经封装好了。因为在JVM中有GC这个东东,因此多数java coder并无写析构的习惯,不过在JNI里是必须的了,不然容易形成内存泄露。咱们在这里在release以前和以后分别打出这个字符串来看一下效果。

 

粗略的解释完一些API以后,咱们编写测试代码。

 

Business b = new Business();          
System.out.println(b.getPrice("Apple"));  

运行这段测试代码,控制台上打出

 

Before release: Apple
After release: ��
1.2

 

在release以前打印出来的是咱们“须要”的Apple,release以后就成了乱码了。因为传递的是Apple,因此获得1.2。测试成功。

 

2. getOrder(String name, int amount)

 

在foreground中能够经过这个方法让backend返回一个你“指定”的Order。所谓“指定”,其实也就是指方法里的两个参数:name和amout,在cpp的代码在中,会根据传递的两个参数来构造一个Order。回到cpp的代码里。

 

Java代码   收藏代码
  1. jclass order_class = env->FindClass("com/chnic/bean/Order");  

 

是否是以为这句代码似曾相识?没错,这句代码很像咱们java里写的Class.forName(className)反射的代码。其实在这里FindClass的做用和上面的forName是相似的。只不过在forName中要用完整的类名,可是在这里必须用"/"来代替“.”。这个方法会返回一个jclass的对象,其实也就是咱们在Java中说的类对象。

jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");  
jobject obj = env->NewObject(obj_class, construction_id);  

 

 

拿到"类对象"了以后,按照Java RTTI的逻辑咱们接下来就要唤醒那个类对象的构造函数了。在JNI中,包括构造函数在内的全部方法都被当作Method。每一个method都有一个特定的ID,咱们经过GetMethodID这个方法就能够拿到咱们想要的某一个java 方法的ID。GetMethodID须要传三个参数,第一个是很显然jclass,第二个参数是java方法名,也就是你想取的method ID的那个方法的方法名(有些绕口 ),第三个参数是方法签名。

 

 在这里有必要单独来说一讲这个方法签名,为何要用这个东东呢?咱们知道,在Java里方法是能够被重载的,好比我一个类里有public void a(int arg)和public void a(String arg)这两个方法,在这里用方法名来区分方法显然就是行不通的了。方法签名包括两部分:参数类型和返回值类型;具体的格式:(参数1类型签名 参数2类型签名)返回值类型签名。下面是java类型和年名类型的对照的一个表

 

    Java类型      对应的签名
boolean Z
byte B
char C
shrot S
int I
long L
float F
double D
void V
Object L用/分割包的完整类名;  Ljava/lang/String;
Array [签名       [I       [Ljava/lang/String;

 

其实除了本身对照手写以外,JDK也提供了一个很好用的生成签名的工具javap,cmd进入控制台到你要生成签名的那个类的目录下。在这里用Order类打比方,敲入: javap -s -private Order。 全部方法签名都会被输出,关于javap的一些参数能够在控制台下面输入 javap -help查看。(作coder的 毕竟仍是要认几个单词的)

 

啰嗦了一大堆,仍是回到咱们刚刚的getMethodID这个方法上。由于是调用构造函数,JNI规定调用构造函数的时候传递的方法名应该为<init> ,经过javap查看 咱们要的那个无参的构造函数的方法签是()V。获得方法签名,最后咱们调用NewObject方法来生成一个新的对象。

 

拿到了对象,以后咱们开始为对象jobject填充数值,仍是首先拿到setXXX方法的Method ID,以后调用Call<Type>Method来调用java方法。这里的<Type>所指的是方法的返回类型,咱们刚刚调用的是set方法的返回值是void,所以这里的方法也就是CallVoidMethod,这个方法的参数除了前两个要传入jobject和jmethodID以外还要传入要调用的那个方法的参数,并且要顺序必须一致,这点和Java的反射如出一辙,在这里就很少解释。(看到这一步是否是对java 反射又有了本身新的理解?)

 

终于介绍完了第二个方法,下来就是测试代码测试。

Order o = b.getOrder("Watermelom", 100);  
System.out.println("java: " + o.getName());  
System.out.println("java: " + o.getAmount());  

 

 

控制台打出

 

java: Watermelom
java: 100

 

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

 

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

相关文章
相关标签/搜索