JNI就是Java Native Interface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其它语言的通信(主要是C&C++)。也许不少人认为Java已经足够强大,为什么要须要JNI这样的东西呢?我们知道Java是一种平台无关性的语言,平台对于上层的java代码来说是透明的,所以在多数时间我们是不须要JNI的,可是假如你遇到了例如以下的三种情况之中的一个呢?

  1. 你的Java代码,须要得到一个文件的属性。

    可是你找遍了JDK帮助文档也找不到相关的API。

  2. 在本地另一个别的系统。只是他不是Java语言实现的。这个时候你的老板要求你把两套系统整合到一起。
  3. 你的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的Javaproject,然后编写以下的代码。

Java代码 
  1. package com.chnic.jni;
  2. public class SayHellotoCPP {
  3. public SayHellotoCPP(){
  4. }
  5. public native void sayHello(String name);
  6. }
[java] view
plain
copy

  1. package com.chnic.jni;
  2. public class SayHellotoCPP {
  3. public SayHellotoCPP(){
  4. }
  5. public native void sayHello(String name);
  6. }

一般的第一个程序总是HelloWorld。今天换换口味。把world换成一个名字。我的native本地方法有一个String的參数。会传递一个name到后台去。本地方法已经完毕。如今来介绍下javah这种方法,接下来就要用javah方法来生成一个相相应的.h头文件。

javah是一个专门为JNI生成头文件的一个命令。

CMD打开控制台之后输入javah回车就能看到javah的一些參数。在这里就不多介绍我们要用的是 -jni这个參数。这个參数也是默认的參数,他会生成一个JNI式的.h头文件。在控制台进入到project的根文件夹,也就是HelloFore这个文件夹。然后输入命令。

Java代码 
  1. javah -jni com.chnic.jni.SayHellotoCPP
[java] view
plain
copy

  1. javah -jni com.chnic.jni.SayHellotoCPP

命令运行完之后在project的根文件夹就会发现com_chnic_jni_SayHellotoCPP.h 这个头文件。在这里有必要多句嘴。在运行javah的时候,要输入完整的包名+类名。

否则在以后的測试调用过程中会发生java.lang.UnsatisfiedLinkError这个异常。

到这里java部分算是基本完毕了,接下来我们来编写后端的C++代码。(用C也能够,仅仅只是cout比printf用起来更快些,所以这里俺偷下懒用C++)打开VC++首先新建一个Win32 Dynamic-Link libraryproject,之后选择An empty DLL project空project。在这里我C++的project是HelloEnd,把刚刚生成的那个头文件复制到这个project的根文件夹里。

随便用什么文本编辑器打开这个头文件,发现有一个例如以下的方法签名。

Cpp代码 
  1. /*
  2. * Class:     com_chnic_jni_SayHellotoCPP
  3. * Method:    sayHello
  4. * Signature: (Ljava/lang/String;)V
  5. */
  6. JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
  7. (JNIEnv *, jobject, jstring);
  1. /*
  2. * Class:     com_chnic_jni_SayHellotoCPP
  3. * Method:    sayHello
  4. * Signature: (Ljava/lang/String;)V
  5. */
  6. JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
  7. (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来实现这种方法。

Cpp代码 
  1. #include <iostream.h>
  2. #include "com_chnic_jni_SayHellotoCPP.h"
  3. JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
  4. (JNIEnv* env, jobject obj, jstring name)
  5. {
  6. const char* pname = env->GetStringUTFChars(name, NULL);
  7. cout << "Hello, " << pname << endl;
  8. }
  1. #include <iostream.h>
  2. #include "com_chnic_jni_SayHellotoCPP.h"
  3. JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
  4. (JNIEnv* env, jobject obj, jstring name)
  5. {
  6. const char* pname = env->GetStringUTFChars(name, NULL);
  7. cout << "Hello, " << pname << endl;
  8. }

由于我们生成的那个头文件是在C++project的根文件夹不是在环境文件夹。所以我们要把尖括号改成单引號。至于VC++的环境文件夹能够在 Tools->Options->Directories里设置。F7编译project发现缺少jni.h这个头文件。

这个头文件能够在%JAVA_HOME%/include文件夹下找到。把这个文件复制到C++project文件夹。继续编译发现还是找不到。

原来是由于在我们刚刚生成的那个头文件中,jni.h这个文件是被 #include <jni.h>引用进来的。因此我们把尖括号改成双引號#include "jni.h"。继续编译发现少了jni_md.h文件,接着在%JAVA_HOME%/include/win32以下找到那个头文件,放入到project根文件夹。F7编译成功。在Debug文件夹里会发现生成了HelloEnd.dll这个文件。

这个时候后端的C++代码也已经完毕。接下来的任务就是怎么把他们连接在一起了。要让前端的java程序“认识并找到”这个动态链接库,就必须把这个DLL放在windows path环境变量以下。有两种方法能够做到:

  1. 把这个DLL放到windows以下的sysytem32文件夹以下,这个是windows默认的path
  2. 复制你project的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");这句到静态初始化块里。

Java代码 
  1. package com.chnic.jni;
  2. public class SayHellotoCPP {
  3. static{
  4. System.loadLibrary("HelloEnd");
  5. }
  6. public SayHellotoCPP(){
  7. }
  8. public native void sayHello(String name);
  9. }
[java] view
plain
copy

  1. package com.chnic.jni;
  2. public class SayHellotoCPP {
  3. static{
  4. System.loadLibrary("HelloEnd");
  5. }
  6. public SayHellotoCPP(){
  7. }
  8. public native void sayHello(String name);
  9. }

这样我们的代码就能认识并载入这个动态链接库文件了。

万事俱备。仅仅欠測试代码了。接下来编写測试代码。

Java代码 
  1. SayHellotoCPP shp = new SayHellotoCPP();
  2. shp.sayHello("World");
[java] view
plain
copy

  1. SayHellotoCPP shp = new SayHellotoCPP();
  2. shp.sayHello("World");

我们不让他直接Hello,World。

我们把World传进去,运行代码。发现控制台打印出来Hello, World这句话。

就此一个最简单的JNI程序已经开发完毕。或许有朋友会对CPP代码里的

Cpp代码 
  1. const char* pname = env->GetStringUTFChars(name, NULL);
  1. const char* pname = env->GetStringUTFChars(name, NULL);

这句有疑问,这个GetStringUTFChars就是JNI给developer提供的API,我们以后再讲。

在这里不得不多句嘴。

  1. 由于JNI有一个Native这个特点,一点有项目用了JNI。也就说明这个项目基本不能跨平台了。
  2. JNI调用是相当慢的,在实际使用的之前一定要先想明确是否有这个必要。

  3. 由于C++和C这种语言很灵活,一不小心就easy出错,比方我刚刚的代码就没有写析构字符串释放内存,对于java developer来说由于有了GC 垃圾回收机制,所以大多数人没有写析构函数这种概念。所以JNI也会添加程序中的风险,增大程序的不稳定性。

事实上在Java代码中,除了对本地方法标注nativekeyword和加上要载入动态链接库之外,JNI基本上是对上层coder透明的,上层coder调用那些本地方法的时候并不知道这种方法的方法体到底是在哪里,这个道理就像我们用JDK所提供的API一样。

所以在Java中使用JNI还是非常easy的。相比之下在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类。

Java代码 
  1. package com.chnic.service;
  2. import com.chnic.bean.Order;
  3. public class Business {
  4. static{
  5. System.loadLibrary("FruitFactory");
  6. }
  7. public Business(){
  8. }
  9. public native double getPrice(String name);
  10. public native Order getOrder(String name, int amount);
  11. public native Order getRamdomOrder();
  12. public native void analyzeOrder(Order order);
  13. public void notification(){
  14. System.out.println("Got a notification.");
  15. }
  16. public static void notificationByStatic(){
  17. System.out.println("Got a notification in a static method.");
  18. }
  19. }
[java] view
plain
copy

  1. package com.chnic.service;
  2. import com.chnic.bean.Order;
  3. public class Business {
  4. static{
  5. System.loadLibrary("FruitFactory");
  6. }
  7. public Business(){
  8. }
  9. public native double getPrice(String name);
  10. public native Order getOrder(String name, int amount);
  11. public native Order getRamdomOrder();
  12. public native void analyzeOrder(Order order);
  13. public void notification(){
  14. System.out.println("Got a notification.");
  15. }
  16. public static void notificationByStatic(){
  17. System.out.println("Got a notification in a static method.");
  18. }
  19. }

这个类里面包括4个本地方法。一个静态初始化块载入将要生成的dll文件。

剩下的方法都是非常普通的java方法。等会在backend中回调这些方法。这个类须要一个名为Order的JavaBean。

Java代码 
  1. package com.chnic.bean;
  2. public class Order {
  3. private String name = "Fruit";
  4. private double price;
  5. private int amount = 30;
  6. public Order(){
  7. }
  8. public int getAmount() {
  9. return amount;
  10. }
  11. public void setAmount(int amount) {
  12. this.amount = amount;
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public double getPrice() {
  21. return price;
  22. }
  23. public void setPrice(double price) {
  24. this.price = price;
  25. }
  26. }
[java] view
plain
copy

  1. package com.chnic.bean;
  2. public class Order {
  3. private String name = "Fruit";
  4. private double price;
  5. private int amount = 30;
  6. public Order(){
  7. }
  8. public int getAmount() {
  9. return amount;
  10. }
  11. public void setAmount(int amount) {
  12. this.amount = amount;
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public double getPrice() {
  21. return price;
  22. }
  23. public void setPrice(double price) {
  24. this.price = price;
  25. }
  26. }

JavaBean中。我们为两个私有属性赋值。方便后面的样例演示。

到此为止除了測试代码之外的Java端的代码就所有高调了,接下来进行生成.h 头文件、建立C++project的工作,在这里就一笔带过。不熟悉的朋友请回头看第一篇。

在project里我们新建一个名为Foctory的C++ source file 文件,去实现那些native方法。详细的代码例如以下。

Cpp代码 
  1. #include <iostream.h>
  2. #include <string.h>
  3. #include "com_chnic_service_Business.h"
  4. jobject getInstance(JNIEnv* env, jclass obj_class);
  5. JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
  6. jobject obj,
  7. jstring name)
  8. {
  9. const char* pname = env->GetStringUTFChars(name, NULL);
  10. cout << "Before release: "  << pname << endl;
  11. if (strcmp(pname, "Apple") == 0)
  12. {
  13. env->ReleaseStringUTFChars(name, pname);
  14. cout << "After release: " << pname << endl;
  15. return 1.2;
  16. }
  17. else
  18. {
  19. env->ReleaseStringUTFChars(name, pname);
  20. cout << "After release: " << pname << endl;
  21. return 2.1;
  22. }
  23. }
  24. JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
  25. jobject obj,
  26. jstring name,
  27. jint amount)
  28. {
  29. jclass order_class = env->FindClass("com/chnic/bean/Order");
  30. jobject order = getInstance(env, order_class);
  31. jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V");
  32. env->CallVoidMethod(order, setName_method, name);
  33. jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V");
  34. env->CallVoidMethod(order, setAmount_method, amount);
  35. return order;
  36. }
  37. JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
  38. jobject obj)
  39. {
  40. jclass business_class = env->GetObjectClass(obj);
  41. jobject business_obj = getInstance(env, business_class);
  42. jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V");
  43. env->CallVoidMethod(obj, notification_method);
  44. jclass order_class = env->FindClass("com/chnic/bean/Order");
  45. jobject order = getInstance(env, order_class);
  46. jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
  47. jint amount = env->GetIntField(order, amount_field);
  48. cout << "amount: " << amount << endl;
  49. return order;
  50. }
  51. JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
  52. jclass cls,
  53. jobject obj)
  54. {
  55. jclass order_class = env->GetObjectClass(obj);
  56. jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;");
  57. jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
  58. const char* pname = env->GetStringUTFChars(name_str, NULL);
  59. cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
  60. jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
  61. env->CallStaticVoidMethod(cls, notification_method_static);
  62. }
  63. jobject getInstance(JNIEnv* env, jclass obj_class)
  64. {
  65. jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
  66. jobject obj = env->NewObject(obj_class, construction_id);
  67. return obj;
  68. }
  1. #include <iostream.h>
  2. #include <string.h>
  3. #include "com_chnic_service_Business.h"
  4. jobject getInstance(JNIEnv* env, jclass obj_class);
  5. JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
  6. jobject obj,
  7. jstring name)
  8. {
  9. const char* pname = env->GetStringUTFChars(name, NULL);
  10. cout << "Before release: "  << pname << endl;
  11. if (strcmp(pname, "Apple") == 0)
  12. {
  13. env->ReleaseStringUTFChars(name, pname);
  14. cout << "After release: " << pname << endl;
  15. return 1.2;
  16. }
  17. else
  18. {
  19. env->ReleaseStringUTFChars(name, pname);
  20. cout << "After release: " << pname << endl;
  21. return 2.1;
  22. }
  23. }
  24. JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
  25. jobject obj,
  26. jstring name,
  27. jint amount)
  28. {
  29. jclass order_class = env->FindClass("com/chnic/bean/Order");
  30. jobject order = getInstance(env, order_class);
  31. jmethodID setName_method = env-><span class="hilite1">GetMethodID</span>(order_class, "setName", "(Ljava/lang/String;)V");
  32. env->CallVoidMethod(order, setName_method, name);
  33. jmethodID setAmount_method = env-><span class="hilite1">GetMethodID</span>(order_class, "setAmount", "(I)V");
  34. env->CallVoidMethod(order, setAmount_method, amount);
  35. return order;
  36. }
  37. JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
  38. jobject obj)
  39. {
  40. jclass business_class = env->GetObjectClass(obj);
  41. jobject business_obj = getInstance(env, business_class);
  42. jmethodID notification_method = env-><span class="hilite1">GetMethodID</span>(business_class, "notification", "()V");
  43. env->CallVoidMethod(obj, notification_method);
  44. jclass order_class = env->FindClass("com/chnic/bean/Order");
  45. jobject order = getInstance(env, order_class);
  46. jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
  47. jint amount = env->GetIntField(order, amount_field);
  48. cout << "amount: " << amount << endl;
  49. return order;
  50. }
  51. JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
  52. jclass cls,
  53. jobject obj)
  54. {
  55. jclass order_class = env->GetObjectClass(obj);
  56. jmethodID getName_method = env-><span class="hilite1">GetMethodID</span>(order_class, "getName", "()Ljava/lang/String;");
  57. jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
  58. const char* pname = env->GetStringUTFChars(name_str, NULL);
  59. cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
  60. jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
  61. env->CallStaticVoidMethod(cls, notification_method_static);
  62. }
  63. jobject getInstance(JNIEnv* env, jclass obj_class)
  64. {
  65. jmethodID construction_id = env-><span class="hilite1">GetMethodID</span>(obj_class, "<init>", "()V");
  66. jobject obj = env->NewObject(obj_class, construction_id);
  67. return obj;
  68. }

能够看到。在我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里是必须的了,否则easy造成内存泄露。我们在这里在release之前和之后分别打出这个字符串来看一下效果。

粗略的解释完一些API之后。我们编写測试代码。

Java代码 
  1. Business b = new Business();
  2. System.out.println(b.getPrice("Apple"));
[java] view
plain
copy

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

执行这段測试代码。控制台上打出

Before release: Apple

After release: ?&#18;?

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] view
plain
copy

  1. jclass order_class = env->FindClass("com/chnic/bean/Order");

是不是认为这句代码似曾相识?没错,这句代码非常像我们java里写的Class.forName(className)反射的代码。事实上在这里 FindClass的作用和上面的forName是类似的。

仅仅只是在forName中要用完整的类名。可是在这里必须用"/"来取代“.”。这种方法会返回一个jclass的对象,事实上也就是我们在Java中说的类对象。

Java代码 
  1. jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
  2. jobject obj = env->NewObject(obj_class, construction_id);
[java] view
plain
copy

  1. jmethodID construction_id = env-><span class="hilite1">GetMethodID</span>(obj_class, "<init>", "()V");
  2. 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 反射又有了自己新的理解?)

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

Java代码 
  1. Order o = b.getOrder("Watermelom", 100);
  2. System.out.println("java: " + o.getName());
  3. System.out.println("java: " + o.getAmount());
[java] view
plain
copy

  1. Order o = b.getOrder("Watermelom", 100);
  2. System.out.println("java: " + o.getName());
  3. 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. jclass business_class = env->GetObjectClass(obj);
  1. jclass business_class = env->GetObjectClass(obj);

这一句不难理解。GetObjectClass方法能够得到一个对象的类对象。这句有点像Java中的Object.class。不熟悉的朋友建议再去看一下Java反射机制。

接下来的几句C++代码应该在之前的方法1和方法2中都解释过。早backend端会发一个“消息”给 foreground,之后new一个新的Order类出来。接下来的三句有必要再废话一下。

Cpp代码 
  1. jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
  2. jint amount = env->GetIntField(order, amount_field);
  3. cout << "amount: " << amount << endl;
  1. jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
  2. jint amount = env->GetIntField(order, amount_field);
  3. cout << "amount: " << amount << endl;

之前我为Order这个Javabean的amount的属性设置了一个初始值为30,事实上就是为了在这里演示怎样在C++中拿一个Java对象的属性。拿的方法和我们之前说过的调用Java方法的程序几乎相同。也要先拿到一个jfieldID,之后调用Get<type>Field方法来取得某一个对象中的某一个属性的数值。最后cout把他打印出来。我们编写測试代码来看一下终于效果。

Java代码 
  1. Business b = new Business();
  2. Order o2 = b.getRamdomOrder();
  3. System.out.println(o2.getName());
[java] view
plain
copy

  1. Business b = new Business();
  2. Order o2 = b.getRamdomOrder();
  3. 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。也就是以下这句代码。

Cpp代码 
  1. jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
  1. jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));

取到了name这个字符串之后cout打印出来。之后调用Business这个类对象中的静态方法notificationByStatic来通知 foreground。调用的流程以及方法和非静态都是一样的,仅仅只是注意JNI中调用静态方法的API所传递的一个參数是一个jclass而非 jobject(这个也不难理解。由于静态方法是属于class类对象的)

还是编写測试代码測试这种方法

Java代码 
  1. Business b = new Business();
  2. Order o = b.getOrder("Watermelom", 100);
  3. Business.analyzeOrder(o);
[java] view
plain
copy

  1. Business b = new Business();
  2. Order o = b.getOrder("Watermelom", 100);
  3. Business.analyzeOrder(o);

控制台上打印出

Name in Java_com_chnic_service_Business_analyzeOrder: Watermelom

Got a notification in a static method.

第一句是C++中cout打印出来的,第二句则是Java中的静态方法打印出来的。和我们想要的结果是一致的。

呼~好不easy介绍完了4个方法,最后总结一下吧。

  1. JNI中所提供的API远远不止这4个方法中所使用的API。

    上面介绍的都是比較经常使用的,本人也不可能罗列出全部的API。

  2. 了解了JNI编程更加有利于深入了解Java中的反射机制,反之亦然。

Java JNI的具体介绍的更多相关文章

  1. java JNI介绍

    java JNI介绍 目录 java JNI介绍 1. Java调用C++代码 2.C++代码调用java代码 JNI是Java Native Interface的全称. oracle文档中是这样描述 ...

  2. 三、Android NDK编程预备之Java jni入门创建C/C++共享库

    转自: http://www.eoeandroid.com/thread-264971-1-1.html 应网友回复,答应在两天前要出一篇创建C/C++共享库的,但由于清明节假期,跟朋友出去游玩,丢手 ...

  3. 二、Android NDK编程预备之Java jni入门Hello World

    转自:  http://www.eoeandroid.com/forum.php?mod=viewthread&tid=264543&fromuid=588695 昨天已经简要介绍了J ...

  4. 一、Android NDK编程预备之Java jni简介

    转自:  http://www.eoeandroid.com/thread-264384-1-1.html 游戏开发 视频教程 博客 淘帖     论坛›eoe·Android应用开发区›Androi ...

  5. JAVA JNI

    jni非常好的一篇文章 http://m.blog.csdn.net/article/details?id=22827307 JAVA JNI介绍 http://blog.csdn.net/cyg08 ...

  6. Java 16 新功能介绍

    点赞再看,动力无限.Hello world : ) 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 程序猿阿朗博客 已经收录,有很多知识点和系列文章. Ja ...

  7. Java 17 新功能介绍(LTS)

    点赞再看,动力无限.Hello world : ) 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. Jav ...

  8. Java 19 新功能介绍

    点赞再看,动力无限. 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. Java 19 在2022 年 9 ...

  9. java + jni + mingw实例开发(基于命令行窗口模式)

    java+ jni + mingw 参考网址: http://wenku.baidu.com/link?url=9aQ88d2ieO7IgKLlNhJi5d3mb3xwzbezLPzSIX3ixz4_ ...

随机推荐

  1. 请教如何改善C#中socket通信机客户端程序的健壮性

    我是做Socket的新手,最近做了一个Socket客户端程序,连接Server的时候,如果server存在,并且允许连接的话,程序无错,正常执行:但是如果Server不存在,或者拒绝连接,程序就会卡住 ...

  2. 使用ECharts实现数据图表分析

    一.ECharts介绍 实现对统计数据的图形分析之前用过JFreeChar,但它是用纯java实现编码繁琐且效果不佳,后来又使用过Fusioncharts 报表工具,它是基于Flash的图表组件.以X ...

  3. java泛型中extends 和 super的区别

    一般对泛型中extends 和 super 的区别是这样介绍的: 关键字说明 ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类 < ...

  4. xpath语法速查

    xpath的具体学习可以通过w3c查看(链接:http://www.w3school.com.cn/xpath/index.asp) 这里只是将平时用到的几个表格贴出来,以后查询: 这里的xpath我 ...

  5. 算法笔记_210:第六届蓝桥杯软件类决赛真题(Java语言C组)

    目录 1 机器人数目 2 生成回文数 3 空心菱形 4 奇怪的数列 5 密文搜索 6 居民集会 前言:以下代码仅供参考,若有错误欢迎指正哦~ 1 机器人数目 标题:机器人数目 少年宫新近邮购了小机器人 ...

  6. 开源 免费 java CMS - FreeCMS1.9 移动APP管理 网站配置

    项目地址:http://www.freeteam.cn/ 网站配置 管理员能够在这里设置当前管理网站是否同意移动app訪问,是否默认移动APP网站.首页的布局,首页数据最多载入页数. 从左側管理菜单点 ...

  7. POJ 1163 The Triangle DP题解

    寻找路径,动态规划法题解. 本题和Leetcode的triangle题目几乎相同一样的,本题要求的是找到最大路径和. 逆向思维.从底往上查找起就能够了. 由于从上往下能够扩展到非常多路径.而从下往上个 ...

  8. ExceptionLess 搭建到本地服务器

    Exceptionless 是一个开源的实时的日志收集框架,它可以应用在基于 ASP.NET,ASP.NET Core,Web Api,Web Forms,WPF,Console,MVC 等技术栈的应 ...

  9. Linux 禁止用户或 IP通过 SSH 登录

             一切都是为了安全,做到来着可知! 限制用户 SSH 登录 1.只允许指定用户进行登录(白名单): 在 /etc/ssh/sshd_config 配置文件中设置 AllowUsers ...

  10. 【vue.js】windows下安装vue.js

    windows下搭建vue开发环境 Vue.js是一套构建用户界面的 “渐进式框架”.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计.Vue 的核心库只关注视图层,并且非常容易学习,非常 ...