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. RHCSA和RHCE

    RHCSA: 红帽考试流程: RHCE(上下午) 上午:RHCSA(红帽认证系统管理员)下午:RHCE (红帽认证系统工程师) 上午考试通过,下午未通过(RHCSA)上午考试未通过,下午考试通过(没有 ...

  2. U盘去保护方法

    一.基本信息 U盘大小是16G的,估计用了2G的空间存储,没有任何开关设置,格式化或写入时提示被写保护: U盘放到任何一台电脑上都是只能读不能写,说明与电脑无关,用了各种U盘修复程序都无效: 二.一般 ...

  3. php 内置函数JSON处理

    一:生成json数据 <?php $arr = array ('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5); echo json_enco ...

  4. UVA 10026 Shoemaker's Problem 鞋匠的难题 贪心+排序

    题意:鞋匠一口气接到了不少生意,但是做鞋需要时间,鞋匠只能一双一双地做,根据协议每笔生意如果拖延了要罚钱. 给出每笔生意需要的天数和每天的罚钱数,求出最小罚钱的排列顺序. 只要按罚款/天数去从大到小排 ...

  5. Nginx负载均衡简易方法

    做个简单的测试,一个Nginx, 通过FastCGI协议和另外两台服务器上的基于CppCMS开发的web server通信.配置方法很简单: 首先,必须在nginx.conf文件开头,server 配 ...

  6. uni - 介绍

    uni-app 是一个使用 Vue.js 开发跨平台应用的前端框架,开发者编写一套代码,可编译到iOS.Android.H5.小程序等多个平台 优点:基于vue.mpvue.微信小程序 微信小程序AP ...

  7. BeautifulSoup的成员结构

    >>> dir(soup)['ASCII_SPACES', 'DEFAULT_BUILDER_FEATURES', 'HTML_FORMATTERS', 'ROOT_TAG_NAME ...

  8. 36个Android开发常用代码片段

    //36个Android开发常用代码片段 //拨打电话 public static void call(Context context, String phoneNumber) { context.s ...

  9. 剑指offer(2) - 二维数组中的查找

    题目: 在一个二维数组中.每一行都依照从左到右递增的顺序排序,每一列都依照从上往下递增的顺序排序.请写一个函数,输入一个二维数组和一个整数,推断数组中是否含有该整数. 比如以下的二维数组就是每行.每列 ...

  10. 【独立开发人员er Cocos2d-x实战 001】csb文件导出和载入

    使用cocos studio进行资源文件导出: 然后在cocosproject中进行载入csb文件:  auto myLayout = CSLoader::createNode("/res/ ...