JNI其实是Java Native Interface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其他语言的通信(主要是C&C++)。也许不少人觉得Java已经足够强大,为什么要需要JNI这种东西呢?我们知道Java是一种平台无关性的语言,平台对于上层的java代码来说是透明的,所以在多数时间我们是不需要JNI的,但是假如你遇到了如下的三种情况之一呢?
- 你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
- 在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
- 你的Java代码中需要用到某种算法,不过算法是用C实现并封装在动态链接库文件(DLL)当中的。
对于上述的三种情况,如果没有JNI的话,那就会变得异常棘手了。就算找到解决方案了,也是费时费力。其实说到底还是会增加开发和维护的成本。
说了那么多一通废话,现在进入正题。看过JDK源代码的人肯定会注意到在源码里有很多标记成native的方法。这些个方法只有方法签名但是没有方法体。其实这些naive方法就是我们说的 java native interface。他提供了一个调用(invoke)的接口,然后用C或者C++去实现。我们首先来编写这个“桥梁”.我自己的开发环境是 j2sdk1.4.2_15 + eclipse 3.2 + VC++ 6.0,先在eclipse里建立一个HelloFore的Java工程,然后编写下面的代码。
- package com.chnic.jni;
- public class SayHellotoCPP {
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
- }
- package com.chnic.jni;
- public class SayHellotoCPP {
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
- }
一般的第一个程序总是HelloWorld。今天换换口味,把world换成一个名字。我的native本地方法有一个String的参数。会传递一个name到后台去。本地方法已经完成,现在来介绍下javah这个方法,接下来就要用javah方法来生成一个相对应的.h头文件。
javah是一个专门为JNI生成头文件的一个命令。CMD打开控制台之后输入javah回车就能看到javah的一些参数。在这里就不多介绍我们要用的是 -jni这个参数,这个参数也是默认的参数,他会生成一个JNI式的.h头文件。在控制台进入到工程的根目录,也就是HelloFore这个目录,然后输入命令。
- javah -jni com.chnic.jni.SayHellotoCPP
- javah -jni com.chnic.jni.SayHellotoCPP
命令执行完之后在工程的根目录就会发现com_chnic_jni_SayHellotoCPP.h 这个头文件。在这里有必要多句嘴,在执行javah的时候,要输入完整的包名+类名。否则在以后的测试调用过程中会发生java.lang.UnsatisfiedLinkError这个异常。
到这里java部分算是基本完成了,接下来我们来编写后端的C++代码。(用C也可以,只不过cout比printf用起来更快些,所以这里俺偷下懒用C++)打开VC++首先新建一个Win32 Dynamic-Link library工程,之后选择An empty DLL project空工程。在这里我C++的工程是HelloEnd,把刚刚生成的那个头文件拷贝到这个工程的根目录里。随便用什么文本编辑器打开这个头文件,发现有一个如下的方法签名。
- /*
- * Class: com_chnic_jni_SayHellotoCPP
- * Method: sayHello
- * Signature: (Ljava/lang/String;)V
- */
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv *, jobject, jstring);
- /*
- * Class: com_chnic_jni_SayHellotoCPP
- * Method: sayHello
- * Signature: (Ljava/lang/String;)V
- */
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv *, jobject, jstring);
仔细观察一下这个方法,在注释上标注类名、方法名、签名(Signature),至于这个签名是做什么用的,我们以后再说。在这里最重要的是 Java_com_chnic_jni_SayHellotoCPP_sayHello这个方法签名。在Java端我们执行 sayHello(String name)这个方法之后,JVM就会帮我们唤醒在DLL里的Java_com_chnic_jni_SayHellotoCPP_sayHello这个方法。因此我们新建一个C++ source file来实现这个方法。
- #include <iostream.h>
- #include "com_chnic_jni_SayHellotoCPP.h"
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv* env, jobject obj, jstring name)
- {
- const char* pname = env->GetStringUTFChars(name, NULL);
- cout << "Hello, " << pname << endl;
- }
- #include <iostream.h>
- #include "com_chnic_jni_SayHellotoCPP.h"
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv* env, jobject obj, jstring name)
- {
- const char* 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放在windows path环境变量下面。有两种方法可以做到:
- 把这个DLL放到windows下面的sysytem32文件夹下面,这个是windows默认的path
- 复制你工程的Debug目录,我这里是C:/Program Files/Microsoft Visual Studio/MyProjects/HelloEnd/Debug这个目录,把这个目录配置到User variable的Path下面。重启eclipse,让eclipse在启动的时候重新读取这个path变量。
比较起来,第二种方法比较灵活,在开发的时候不用来回copy dll文件了,节省了很多工作量,所以在开发的时候推荐用第二种方法。在这里我们使用的也是第二种,eclipse重启之后打开 SayHellotoCPP这个类。其实我们上面做的那些是不是是让JVM能找到那些DLL文件,接下来我们要让我们自己的java代码“认识”这个动态链接库。加入System.loadLibrary("HelloEnd");这句到静态初始化块里。
- package com.chnic.jni;
- public class SayHellotoCPP {
- static{
- System.loadLibrary("HelloEnd");
- }
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
- }
- package com.chnic.jni;
- public class SayHellotoCPP {
- static{
- System.loadLibrary("HelloEnd");
- }
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
- }
这样我们的代码就能认识并加载这个动态链接库文件了。万事俱备,只欠测试代码了,接下来编写测试代码。
- SayHellotoCPP shp = new SayHellotoCPP();
- shp.sayHello("World");
- SayHellotoCPP shp = new SayHellotoCPP();
- shp.sayHello("World");
我们不让他直接Hello,World。我们把World传进去,执行代码。发现控制台打印出来Hello, World这句话。就此一个最简单的JNI程序已经开发完成。也许有朋友会对CPP代码里的
- const char* pname = env->GetStringUTFChars(name, NULL);
- const char* pname = env->GetStringUTFChars(name, NULL);
这句有疑问,这个GetStringUTFChars就是JNI给developer提供的API,我们以后再讲。在这里不得不多句嘴。
- 因为JNI有一个Native这个特点,一点有项目用了JNI,也就说明这个项目基本不能跨平台了。
- JNI调用是相当慢的,在实际使用的之前一定要先想明白是否有这个必要。
- 因为C++和C这样的语言非常灵活,一不小心就容易出错,比如我刚刚的代码就没有写析构字符串释放内存,对于java developer来说因为有了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中定义的别名 |
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这个头文件,各种数据类型的定义以及别名就被定义在这个文件中。
了解了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.");
- }
- }
- 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;
- }
- }
- 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方法。具体的代码如下。
- #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;
- }
- #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-><span class="hilite1">GetMethodID</span>(order_class, "setName", "(Ljava/lang/String;)V");
- env->CallVoidMethod(order, setName_method, name);
- jmethodID setAmount_method = env-><span class="hilite1">GetMethodID</span>(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-><span class="hilite1">GetMethodID</span>(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-><span class="hilite1">GetMethodID</span>(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-><span class="hilite1">GetMethodID</span>(obj_class, "<init>", "()V");
- jobject obj = env->NewObject(obj_class, construction_id);
- return obj;
- }
可以看到,在我Java中的四个本地方法在这里全部被实现,接下来针对这四个方法来解释下,一些JNI相关的API的使用方法。先从第一个方法讲起吧:
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"));
- 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的代码里。
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- 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);
- jmethodID construction_id = env-><span class="hilite1">GetMethodID</span>(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());
- Order o = 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对象。
- jclass business_class = env->GetObjectClass(obj);
- jclass business_class = env->GetObjectClass(obj);
这一句不难理解,GetObjectClass方法可以得到一个对象的类对象,这句有点像Java中的Object.class。不熟悉的朋友建议再去看一下Java反射机制。接下来的几句C++代码应该在之前的方法1和方法2中都解释过。早backend端会发一个“消息”给 foreground,之后new一个新的Order类出来。接下来的三句有必要再废话一下。
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
之前我为Order这个Javabean的amount的属性设置了一个初始值为30,其实就是为了在这里演示如何在C++中拿一个Java对象的属性,拿的方法和我们之前说过的调用Java方法的程序差不多,也要先拿到一个jfieldID,之后调用Get<type>Field方法来取得某一个对象中的某一个属性的数值,最后cout把他打印出来。我们编写测试代码来看一下最终效果。
- Business b = new Business();
- Order o2 = b.getRamdomOrder();
- System.out.println(o2.getName());
- Business b = new Business();
- Order o2 = b.getRamdomOrder();
- System.out.println(o2.getName());
运行上述的测试代码之后,控制台上打出了
Got a notification.
amount: 30
Fruit
和我们想要的结果是一样的,测试成功。
4.analyzeOrder(Order order)
这是一个静态方法,foreground会通过这个方法传一个Order的对象到backend去,然后再由CPP端进行“analyze”。在这里我们取出来传递过来的Order对象的name属性,然后打印到控制台上。因为这个方法是静态static方法,所以相对应的C++方法中的第二个参数也变成了jclass对象,也就是Business.class这个类对象。第三个参数是一个jobject对象,很明显就是我们传递过来的order对象。
前5句代码应该不难理解,就是调用getName这个方法,然后打印出来。因为JNI的API中并没有提供CallStringMethod这个方法,所以我们用CallObjectMethod这个方法来取得name这个字符串(String很明显也是一个Object),然后再转型成为 jstring。也就是下面这句代码。
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
取到了name这个字符串之后cout打印出来,之后调用Business这个类对象中的静态方法notificationByStatic来通知 foreground。调用的流程以及方法和非静态都是一样的,只不过注意JNI中调用静态方法的API所传递的一个参数是一个jclass而非 jobject(这个也不难理解,因为静态方法是属于class类对象的)
还是编写测试代码测试这个方法
- Business b = new Business();
- Order o = b.getOrder("Watermelom", 100);
- Business.analyzeOrder(o);
- Business b = new Business();
- Order o = b.getOrder("Watermelom", 100);
- Business.analyzeOrder(o);
控制台上打印出
Name in Java_com_chnic_service_Business_analyzeOrder: Watermelom
Got a notification in a static method.
第一句是C++中cout打印出来的,第二句则是Java中的静态方法打印出来的,和我们想要的结果是一致的。
呼~好不容易介绍完了4个方法,最后总结一下吧。
- JNI中所提供的API远远不止这4个方法中所使用的API。上面介绍的都是比较常用的,本人也不可能罗列出所有的API。
- 了解了JNI编程更加有利于深入了解Java中的反射机制,反之亦然。
因此如果有对JNI编程有兴趣或者有更深入的需要,可以参考一下sun的相关文档。在这里上传sun提供的JNI的API手册,还有上面例子中所用的演示代码给大家参考。
整理项目文档时, 忽然发现当年的一篇以前公司里关于JNI编程 的标准化文档。做为收藏,就贴在这里吧。
注:关于JNI, 现在好像有一个OpenSource项目jace可以帮助进行JNI的开发。另外, 推荐IBM Developerworks网站上的一个教程《用jni进行java编程》
JNI编程注意事项:
1、 JNI的函数声明:
JNI函数声明方法为JAVA_PackageName_ClassName_FuncName,其中特别要注意的是如果native方法所在的java类位于某个package中时,必须在函数声明中加上packageName,否则在运行java程序时将出现:java.lang.UnsatisfiedLinkError错误。同时调用JNI方法:FindClass时需要使用PackageName/ClassName方式,否则会找不到相应Class
2、 编译后DLL存放的位置:
Windows平台下Dll必须可以存放在系统环境变量Path指示的目录中,或者当前目录中
3、 运行javah时的注意事项:
在调用javah时如果目标类处在一个包中,则必须加上包名,比如javah –jin package.ClassName,这样才能产生可以被vm正确识别的头文件
4、 签名
在调用jni函数GetMethodID或者GetFieldID时需要获得签名,签名可以通过javap –s classname来获得
5、 使用jni接口重新封装各服务器客户端api(组装代理api)时的注意事项:
a) java中封装类实现时的注意事项:原api在java中必须用一个类进行封装,我们建议将所有这些api在java中定义为static方式,并创建一个private的类的构造函数以阻止引用该类的java代码中实例化该类的企图。通过这种方法我们可以使在一个进程中只有一个api集合在工作,当然我们也可以在该api封装类中提供一种机制创建多个api集合,并在类内部管理这些api集合的初始化和析构。这种方式将减少外部类使用该封装类时的复杂度,也给提高了系统资源的利用率。参考如下代码:
class EncapslateClass{
private EncapslateClass() {}
private static boolean bIsInit = false;
public static long Init() {
if (!bIsInit){
return API_Init();
}
return ok;
}
public static long Destroy() {
if (bIsInit){
long lRet;
lRet = API_Destroy();
Thread.sleep(1000); //很重要,java vm退出时不会等待API集合中的线程正常退出,我们必须保证在VM退出前自己的API集合全部正常退出,否则在退出时可能会出错。该逻辑也可以放在JNI代理API集合中实现。
}
return ok;
}
private static native long API_Init();
private static native long API_Destroy();
public static native long API_Func1();
public static native long API_Func2();
static{
System.loadLibrary(“xxx”);
}
protected void finalize(){
Destroy(); //永远不会执行到
}
}
上面的例子展示了我们的一种实现方式,外部类通过调用封装类的Init()和Destroy()来初始化和析构,而不是直接调用本地方法API_Init()和API_Destroy(),通过在Init()和Destroy()中加入更多的逻辑,我们甚至可以初始化和析构多个API集合,但一般情况下以上用法已经够用了。采用该方法时需要注意,我们必须在进程退出前显式调用Destroy()来进行析构,原因是该类无法实例化,因此gc不会正确处理该类的析构,必须由我们自己来进行,一般可以在我们的控制类的finalize()函数中显式调用该Destroy()。同时请参考上例中Destroy()函数中的注释
b) 返回值的处理:在我公司大量api中使用返回值(一般为long)指出该api执行是否成功,如果失败则该返回值表示出错代码,而函数正常执行情况下的输出参数采用传引用(或者指针)方式返回。比如 long FuncA(long inparam1, long inparam2, long& outparam1)函数中函数返回值指出FuncA是否执行成功,如果执行成功则通过outparam1返回结果。这种做法在C++编程时是很常见的,但在jni中希望用同样方式与JVM进行交互是行不通。主要原因是函数参数中如果是简单类型(int,short,long,char,byte等非对象类型)jni不支持传引用方式,换句话说原来我们的api通过传引用方式返回简单类型的函数值在jni中是行不通的(虽然java函数参数都是采用传引用方式)。下面有一个例子:
---C++ API:long FuncA(long inparam1, long inparam2, long& outparam1)
---Java中的封装:
public class SampleNativeCall{
public static long FuncA(long inparam1, long inparam2, long outparam1);
static{
System.loadLibrary(“abc”);
}
}
---运行javah –jni SampleNativeCall后的结果
jlong JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2, jlong outparam1)
{
…
if ((lRet = FuncA(inparam1 , inparam2, outparam1)) != OK_VALUE){
return lRet
}
return lRet;
}
上例中希望java中FuncA与C++中FuncA保持一致的接口简化代理api实现的过程,但是由于jlong指示简单的传值过程,我们无法通过outparam1将结果返回给jvm。
可以有两种方式解决以上问题:
1、 将返回的简单类型封装成类,并在类中提供Set方式来设置该简单变量(或是提供pulic成员变量直接赋值),jni代理api函数还是通过返回值方式返回错误,如下:
---C++ API:long FuncA(long inparam1, long inparam2, long& outparam1)
---Java中的封装:
public class SampleNativeCall{
public static class FuncAOutput{
private long outparam;
public long Get() { return outparam; }
public void Set(long inOutparam) {outparam = inOutparam; }
}
public static long FuncA(long inparam1, long inparam2, FuncAOutput outparam1);
static{
System.loadLibrary(“abc”);
}
}
---运行javah –jni SampleNativeCall后的结果
jlong JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2, jobject outparam1)
{
…
if ((lRet = FuncA(inparam1 , inparam2, out1)) != OK_VALUE){
return lRet
}
if (outparam1 == NULL) {…}
jclass cls = env->GetObjectClass(outparam1);
if (cls ==NULL)
{ … }
jmethodID mid = env->GetMethodID(cls, “Set”, “J”);
if (mid == NULL)
{…}
env->CallVoidMethod(outparam1, mid, out1);
…
}
---java中调用方式
public class SampleClass{
static public main(String[] argvs){
{
…
SampleNativeCall.FuncAOutput output = new SampleNativeCall.FuncAOutput(); //很重要,必须先创建对象
SampleNativeCall.FuncA(inparam1, inparam2, output);
System.out.println(“output = ” + output.Get());
…
}
}
以上方式中在返回值的处理上,以及返回值的取值可以与原api保持一致,接口参数个数也保持一致,但返回参数如果是简单类型则需封装成类(在例子中采用内部类方式,正式实现时也建议采用这种方式,简化代码)。如果原api中返回值通过一个结构返回,则可以用类似的方式用一个类封装该结构,并通过调用jni方式设置该类中成员变量的值。
2、 将结果封装成类通过函数返回值返回,函数的出错处理采用“异常”处理机制,即在jni中将错误throw出来而不是通过函数返回值返回,如下例
---C++ API:long FuncA(long inparam1, long inparam2, long& outparam1)
---Java中的封装:
public class SampleNativeCall{
public class FuncAOutput{
private long outparam;
public long Get() { return outparam; }
public void Set(long inOutparam) {outparam = inOutparam; }
}
public FuncAOutput FuncA(long inparam1, long inparam2) throws MyException;
static{
System.loadLibrary(“abc”);
}
}
---运行javah –jni SampleNativeCall后的结果
jobject JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2)
{
…
if ((lRet = FuncA(inparam1 , inparam2, out1)) != OK_VALUE){
return lRet
}
jclass cls = env->FindClass(“SampleNativeCall.FuncAOutput”);
if (cls == NULL)
{
…
throw;
…
}
jobject obj = env->AllocObject(cls);
if (obj == NULL)
{
…
throw;
…
}
jmethodID mid = env->GetMethodID(cls, “Set”, “J”);
if (mid == NULL)
{
…
throw;
…
}
env->CallVoidMethod(outparam1, mid, out1);
…
}
---java中调用方式
public class SampleClass{
static public main(String[] argvs){
{
…
//很重要,必须先创建对象
try{
SampleNativeCall.FuncAOutput output = SampleNativeCall.FuncA(inparam1, inparam2);
}
catch ( xxx ){
…
return;
}
System.out.println(“output = ” + output.Get());
…
}
}
我们推荐采用第二种方式,此种方式在输入输出参数的安排上更为清晰合理,而且采用的异常处理机制更符合java的思路。在此种方式里jni通过AllocObject分配的对象同样接受JVM中gc的管理,在没有引用时将被正确释放(已经经过验正)
c) 原api中大量预定义宏和常量处理方式:通过一个类来封装这些东西,这些宏和常量全部设置为static final方式。
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/jackiejuju/archive/2005/04/15/348522.aspx
JNI一直以来都很少去关注,但却是我心中的一个结,最近这几天刚好手头有点时间,因此抽空看了一下这方面的东西,整理了一份文档,JNI技术的出现主要是基于三个方面的应用需求:
1. 解决性能问题
Java具有平台无关性,这使人们在开发企业级应用的时候总是把它作为主要候选方案之一,但是性能方面的因素又大大削弱了它的竞争力。为此,提高Java的性能就显得十分重要。Sun公司及Java的支持者们为提高Java的运行速度已经做出了许多努力,其中大多数集中在程序设计的方法和模式选择方面。由于算法和设计模式的优化是通用的,对Java有效的优化算法和设计模式,对其他编译语言也基本同样适用,因此不能从根本上改变Java程序与编译型语言在执行效率方面的差异。由此,于是人们开始引入JIT(Just In Time,及时编译)的概念。它的基本原理是:首先通过Java编译器把Java源代码编译成平台无关的二进制字节码。然后在Java程序真正执行之前,系统通过JIT编译器把Java的字节码编译为本地化机器码。最后,系统执行本地化机器码,节省了对字节码进行解释的时间。这样做的优点是大大提高了Java程序的性能,缩短了加载程序的时间;同时,由于编译的结果并不在程序运行间保存,因此也节约了存储空间。缺点是由于JIT编译器对所有的代码都想优化,因此同样也占用了很多时间。
动态优化技术是提高Java性能的另一个尝试。该技术试图通过把Java源程序直接编译成机器码,以充分利用Java动态编译和静态编译技术来提高Java的性能。该方法把输入的Java源码或字节码转换为经过高度优化的可执行代码和动态库 (Windows中的. dll文件或Unix中的. so文件)。该技术能大大提高程序的性能,但却破坏了Java的可移植性。
JNI(Java Native Interface, Java本地化方法)技术由此闪亮登场。因为采用JNI技术只是针对一些严重影响Java性能的代码段,该部分可能只占源程序的极少部分,所以几乎可以不考虑该部分代码在主流平台之间移植的工作量。同时,也不必过分担心类型匹配问题,我们完全可以控制代码不出现这种错误。此外,也不必担心安全控制问题,因为Java安全模型已扩展为允许非系统类加载和调用本地方法。根据Java规范,从JDK 1. 2开始,FindClass将设法找到与当前的本地方法关联的类加载器。如果平台相关代码属于一个系统类,则无需涉及任何类加载器; 否则,将调用适当的类加载器来加载和链接已命名的类。换句话说,如果在Java程序中直接调用C/C++语言产生的机器码,该部分代码的安全性就由Java虚拟机控制。
2. 解决本机平台接口调用问题
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
3. 嵌入式开发应用
“一次编程,到处使用”的Java软件概念原本就是针对网上嵌入式小设备提出的,几经周折,目前SUN公司已推出了J2ME(Java 2 P1atform Micro Edition)针对信息家电的Java版本,其技术日趋成熟,开始投入使用。SUN公司Java虚拟机(JVM)技术的有序开放,使得Java软件真正实现跨平台运行,即Java应用小程序能够在带有JVM的任何硬软件系统上执行。加上Java语言本身所具有的安全性、可靠性和可移植性等特点,对实现瘦身上网的信息家电等网络设备十分有利,同时对嵌入式设备特别是上网设备软件编程技术产生了很大的影响。也正是由于JNI解决了本机平台接口调用问题,于是JNI在嵌入式开发领域也是如火如荼。
不失直观性,我们首先写一个JNI小例子:
- public class HelloJni {
- public native void displayHelloJni();
- static {
- System.loadLibrary("helloJni");
- }
- public static void main(String[] args) {
- //System.out.println(System.getProperty("java.library.path"));
- new HelloJni().displayHelloJni();
- }
- }
- public class HelloJni {
- public native void displayHelloJni();
- static {
- System.loadLibrary("helloJni");
- }
- public static void main(String[] args) {
- //System.out.println(System.getProperty("java.library.path"));
- new HelloJni().displayHelloJni();
- }
- }
在class文件生成的相应目录执行命令如下:
----------------------------------------------------
E:/projects/jni/target/classes>javah HelloJni
----------------------------------------------------
得到C++文件HelloJni.h
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class HelloJni */
- #ifndef _Included_HelloJni
- #define _Included_HelloJni
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: HelloJni
- * Method: displayHelloJni
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_HelloJni_displayHelloJni
- (JNIEnv *, jobject);
- #ifdef __cplusplus
- }
- #endif
- #endif
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class HelloJni */
- #ifndef _Included_HelloJni
- #define _Included_HelloJni
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: HelloJni
- * Method: displayHelloJni
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_HelloJni_displayHelloJni
- (JNIEnv *, jobject);
- #ifdef __cplusplus
- }
- #endif
- #endif
JNI函数名称分为三部分:首先是Java关键字,供Java虚拟机识别;然后是调用者类名称(全限定的类名,其中用下划线代替名称分隔符);最后是对应的方法名称,各段名称之间用下划线分割。
JNI函数的参数也由三部分组成:首先是JNIEnv *,是一个指向JNI运行环境的指针;第二个参数随本地方法是静态还是非静态而有所不同一一非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用;其余的参数对应通常Java方法的参数,参数类型需要根据一定规则进行映射。
编写C++文件HelloJni.h的实现类,我是比较常用VC6.0来生成dll文件(helloJni.dll)的
- #include <jni.h>
- #include "HelloJni.h"
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_HelloJni_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Hello Dynamic Link Library has been calling!/n");
- printf("Java_HelloJni_displayHelloJni method has been executed!/n");
- return;
- }
- #include <jni.h>
- #include "HelloJni.h"
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_HelloJni_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Hello Dynamic Link Library has been calling!/n");
- printf("Java_HelloJni_displayHelloJni method has been executed!/n");
- return;
- }
其实此时,我们的工程目前还暂时不能生成我们想要的 helloJni.dll 文件,问题就出在了“#include <jni.h>”。由于VC6.0里没有我们需要的“jni.h”文件,因此就需要手动加入到VC6.0的环境中去。在JAVA_HOME路径下我们可以找到include文件夹,其中就可以找到我们需要的“jni.h”文件。为了避免以后麻烦起见,将所有的C++文件全部拿出来,放在“%CPP_HOME%/VC98/Include”路径下。然后将工程进行打包就可以得到我们需要的“helloJni.dll”文件了。
将helloJni.dll文件放置于工程classes目录,执行命令如下:
-----------------------------------------------
E:/projects/jni/target/classes>java HelloJni
-----------------------------------------------
运行结果如下:
-----------------------------------------------------------------
Hello Dynamic Link Library has been calling!
Java_HelloJni_displayHelloJni method has been executed!
-----------------------------------------------------------------
但是要想在eclipse中运行helloJni.dll文件,就需要将文件拷贝到工程的根目录,或者将其放在诸如C:/WINDOWS/system32;C:/WINDOWS;等目录下。因为,eclipse在运行helloJni.dll文件时首先会去在当前根目录找,如果找不到则在path上去找,因此你还可以为了方便管理生成的dll文件,将所有工程中的dll文件都放到一个特定的目录,然后将该目录加入到你的本地path环境变量中去,这样每次只需要将生成的dll文件放入path目录下就可以访问了。注,如果需要加环境变量最好在加好以后重新启动一下eclipse,确保eclipse能够加载到最新的path环境。
接下来,对小例子进行重构:
1. 新增一个基础类
- package org.danlley.jni.test;
- public class BaseClass {
- public BaseClass(String arg) {
- loadLibrary(arg);
- }
- private static void loadLibrary(String arg) {
- System.loadLibrary(arg);
- }
- }
- package org.danlley.jni.test;
- public class BaseClass {
- public BaseClass(String arg) {
- loadLibrary(arg);
- }
- private static void loadLibrary(String arg) {
- System.loadLibrary(arg);
- }
- }
2. 定义新类继承基础类
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- }
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- }
3. 编写调用类
- package org.danlley.jni.test;
- public class RunMain {
- public static void main(String[] args) {
- new HelloJniTest("helloJniTest").displayHelloJni();
- }
- }
- package org.danlley.jni.test;
- public class RunMain {
- public static void main(String[] args) {
- new HelloJniTest("helloJniTest").displayHelloJni();
- }
- }
此次,将dll文件定义为:helloJniTest.dll。
执行结果:
------------------------------------------------------------------------------------
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
------------------------------------------------------------------------------------
例子相当简单,没有传入参数,也没有返回值,那么是不是可以让本地方法返回一些参数,同时又可以传入数据进行处理,并把处理结果返回给方法的调用者呢,先拿基本类型开刀。接下来对 HelloJniTest 继续进行改造:新增两个本地方法,如下:
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- public native int getDynamicIntDataNoParam();
- public native int getDynamicIntData(int i);
- }
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- public native int getDynamicIntDataNoParam();
- public native int getDynamicIntData(int i);
- }
重新生成org_danlley_jni_test_HelloJniTest.h文件,并改写其实现类org_danlley_jni_test_HelloJniTest.cpp如下:
- // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
- //
- //////////////////////////////////////////////////////////////////////
- #include "org_danlley_jni_test_HelloJniTest.h"
- #include <jni.h>
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!/n");
- return;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj)
- {
- return 65535;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
- {
- i*=i;
- return i;
- }
- // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
- //
- //////////////////////////////////////////////////////////////////////
- #include "org_danlley_jni_test_HelloJniTest.h"
- #include <jni.h>
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!/n");
- return;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj)
- {
- return 65535;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
- {
- i*=i;
- return i;
- }
修改 RunMain 类:
- package org.danlley.jni.test;
- public class RunMain {
- public static void main(String[] args) {
- HelloJniTest tester=new HelloJniTest("helloJniTest");
- tester.displayHelloJni();
- int i=tester.getDynamicIntDataNoParam();
- System.out.println("tester.getDynamicIntDataNoParam()="+i);
- int j=tester.getDynamicIntData(100);
- System.out.println("tester.getDynamicIntData(100)="+j);
- }
- }
- package org.danlley.jni.test;
- public class RunMain {
- public static void main(String[] args) {
- HelloJniTest tester=new HelloJniTest("helloJniTest");
- tester.displayHelloJni();
- int i=tester.getDynamicIntDataNoParam();
- System.out.println("tester.getDynamicIntDataNoParam()="+i);
- int j=tester.getDynamicIntData(100);
- System.out.println("tester.getDynamicIntData(100)="+j);
- }
- }
运行RunMain:
-----------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
-----------------------------------------------------------------------
OK,一切正常。
还是不过瘾,简单对象可以处理了,如果是一个java对象,还可以处理吗,答案是当然可以,接下来我们来继续对 helloJniTest 类进行改造。新增一个方法如下:
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- public native int getDynamicIntDataNoParam();
- public native int getDynamicIntData(int i);
- public native String getDynamicStringData(String arg);
- }
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- public native int getDynamicIntDataNoParam();
- public native int getDynamicIntData(int i);
- public native String getDynamicStringData(String arg);
- }
重新生成org_danlley_jni_test_HelloJniTest.h文件:
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class org_danlley_jni_test_HelloJniTest */
- #ifndef _Included_org_danlley_jni_test_HelloJniTest
- #define _Included_org_danlley_jni_test_HelloJniTest
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: displayHelloJni
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntDataNoParam
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntData
- * Signature: (I)I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData
- (JNIEnv *, jobject, jint);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicStringData
- * Signature: (Ljava/lang/String;)Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *, jobject, jstring);
- #ifdef __cplusplus
- }
- #endif
- #endif
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class org_danlley_jni_test_HelloJniTest */
- #ifndef _Included_org_danlley_jni_test_HelloJniTest
- #define _Included_org_danlley_jni_test_HelloJniTest
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: displayHelloJni
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntDataNoParam
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntData
- * Signature: (I)I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData
- (JNIEnv *, jobject, jint);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicStringData
- * Signature: (Ljava/lang/String;)Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *, jobject, jstring);
- #ifdef __cplusplus
- }
- #endif
- #endif
改写org_danlley_jni_test_HelloJniTest.cpp文件:
- // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
- //
- //////////////////////////////////////////////////////////////////////
- #include "org_danlley_jni_test_HelloJniTest.h"
- #include <jni.h>
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!/n");
- return;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj)
- {
- return 65535;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
- {
- i*=i;
- return i;
- }
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *env, jobject obj, jstring arg){
- //Get the native string from javaString
- const char *nativeString = env->GetStringUTFChars(arg, 0);
- printf("%s", nativeString);
- //DON'T FORGET THIS LINE!!!
- env->ReleaseStringUTFChars(arg, nativeString);
- return arg;
- }
- // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
- //
- //////////////////////////////////////////////////////////////////////
- #include "org_danlley_jni_test_HelloJniTest.h"
- #include <jni.h>
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!/n");
- return;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj)
- {
- return 65535;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
- {
- i*=i;
- return i;
- }
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *env, jobject obj, jstring arg){
- //Get the native string from javaString
- const char *nativeString = env->GetStringUTFChars(arg, 0);
- printf("%s", nativeString);
- //DON'T FORGET THIS LINE!!!
- env->ReleaseStringUTFChars(arg, nativeString);
- return arg;
- }
重新对C++工程打包成dll文件,运行结果:
---------------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
tester.getDynamicStringData=My first String test
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
My first String test
---------------------------------------------------------------------------
我们不仅把Java的一个String对象成功的传给了dll,而且还将处理后的结果返回了出来。
但是总觉得还是不够,那我们就再来个比较复杂的对象把,我们这次将一个整形数组通过java传给dll,看看是不是也可以处理,继续还是对 helloJniTest 类进行改造,新增一个方法:
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- public native int getDynamicIntDataNoParam();
- public native int getDynamicIntData(int i);
- public native String getDynamicStringData(String arg);
- public native int[] getDynamicArrayData(int[] args);
- }
- package org.danlley.jni.test;
- public class HelloJniTest extends BaseClass {
- public HelloJniTest(String arg){
- super(arg);
- }
- public native void displayHelloJni();
- public native int getDynamicIntDataNoParam();
- public native int getDynamicIntData(int i);
- public native String getDynamicStringData(String arg);
- public native int[] getDynamicArrayData(int[] args);
- }
重新生成org_danlley_jni_test_HelloJniTest.h文件
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class org_danlley_jni_test_HelloJniTest */
- #ifndef _Included_org_danlley_jni_test_HelloJniTest
- #define _Included_org_danlley_jni_test_HelloJniTest
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: displayHelloJni
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntDataNoParam
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntData
- * Signature: (I)I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData
- (JNIEnv *, jobject, jint);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicStringData
- * Signature: (Ljava/lang/String;)Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *, jobject, jstring);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicArrayData
- * Signature: ([I)[I
- */
- JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData
- (JNIEnv *, jobject, jintArray);
- #ifdef __cplusplus
- }
- #endif
- #endif
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class org_danlley_jni_test_HelloJniTest */
- #ifndef _Included_org_danlley_jni_test_HelloJniTest
- #define _Included_org_danlley_jni_test_HelloJniTest
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: displayHelloJni
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntDataNoParam
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam
- (JNIEnv *, jobject);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicIntData
- * Signature: (I)I
- */
- JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData
- (JNIEnv *, jobject, jint);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicStringData
- * Signature: (Ljava/lang/String;)Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *, jobject, jstring);
- /*
- * Class: org_danlley_jni_test_HelloJniTest
- * Method: getDynamicArrayData
- * Signature: ([I)[I
- */
- JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData
- (JNIEnv *, jobject, jintArray);
- #ifdef __cplusplus
- }
- #endif
- #endif
改写org_danlley_jni_test_HelloJniTest.cpp文件:
- // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
- //
- //////////////////////////////////////////////////////////////////////
- #include "org_danlley_jni_test_HelloJniTest.h"
- #include <jni.h>
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!/n");
- return;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj)
- {
- return 65535;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
- {
- i*=i;
- return i;
- }
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *env, jobject obj, jstring arg){
- //Get the native string from javaString
- const char *nativeString = env->GetStringUTFChars(arg, 0);
- printf("%s", nativeString);
- //DON'T FORGET THIS LINE!!!
- env->ReleaseStringUTFChars(arg, nativeString);
- return arg;
- }
- JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData
- (JNIEnv *env, jobject obj, jintArray args){
- jint buf[10];
- jint i;
- env->GetIntArrayRegion(args, 0, 10, buf);
- jint j=0;
- for (i = 0; i < 10; i++) {
- j=buf[i];
- j*=j;
- buf[i]=j;
- }
- env->SetIntArrayRegion(args, 0, 10, buf);
- return args;
- }
- // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
- //
- //////////////////////////////////////////////////////////////////////
- #include "org_danlley_jni_test_HelloJniTest.h"
- #include <jni.h>
- #include <stdio.h>
- JNIEXPORT void JNICALL
- Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj)
- {
- printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!/n");
- return;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj)
- {
- return 65535;
- }
- JNIEXPORT jint JNICALL
- Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
- {
- i*=i;
- return i;
- }
- JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
- (JNIEnv *env, jobject obj, jstring arg){
- //Get the native string from javaString
- const char *nativeString = env->GetStringUTFChars(arg, 0);
- printf("%s", nativeString);
- //DON'T FORGET THIS LINE!!!
- env->ReleaseStringUTFChars(arg, nativeString);
- return arg;
- }
- JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData
- (JNIEnv *env, jobject obj, jintArray args){
- jint buf[10];
- jint i;
- env->GetIntArrayRegion(args, 0, 10, buf);
- jint j=0;
- for (i = 0; i < 10; i++) {
- j=buf[i];
- j*=j;
- buf[i]=j;
- }
- env->SetIntArrayRegion(args, 0, 10, buf);
- return args;
- }
改写RunMain:
- package org.danlley.jni.test;
- public class RunMain {
- public static void main(String[] args) {
- HelloJniTest tester = new HelloJniTest("helloJniTest");
- tester.displayHelloJni();
- int i = tester.getDynamicIntDataNoParam();
- System.out.println("tester.getDynamicIntDataNoParam()=" + i);
- int j = tester.getDynamicIntData(100);
- System.out.println("tester.getDynamicIntData(100)=" + j);
- String str = tester.getDynamicStringData("My first String test");
- System.out.println("tester.getDynamicStringData=" + str);
- int[] args_int = new int[10];
- for (int ii = 0; ii < 10; ii++) {
- args_int[ii] = ii;
- }
- int[] args_arr = tester.getDynamicArrayData(args_int);
- for (int ii = 0; ii < 10; ii++) {
- System.out.println(args_arr[ii]);
- }
- }
- }
- package org.danlley.jni.test;
- public class RunMain {
- public static void main(String[] args) {
- HelloJniTest tester = new HelloJniTest("helloJniTest");
- tester.displayHelloJni();
- int i = tester.getDynamicIntDataNoParam();
- System.out.println("tester.getDynamicIntDataNoParam()=" + i);
- int j = tester.getDynamicIntData(100);
- System.out.println("tester.getDynamicIntData(100)=" + j);
- String str = tester.getDynamicStringData("My first String test");
- System.out.println("tester.getDynamicStringData=" + str);
- int[] args_int = new int[10];
- for (int ii = 0; ii < 10; ii++) {
- args_int[ii] = ii;
- }
- int[] args_arr = tester.getDynamicArrayData(args_int);
- for (int ii = 0; ii < 10; ii++) {
- System.out.println(args_arr[ii]);
- }
- }
- }
运行结果:
--------------------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
tester.getDynamicStringData=My first String test
0
1
4
9
16
25
36
49
64
81
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
My first String test
--------------------------------------------------------------------------------