• 较具体的介绍JNI


    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.   
    3. public class SayHellotoCPP {  
    4.       
    5.     public SayHellotoCPP(){  
    6.     }  
    7.     public native void sayHello(String name);  
    8. }  

     

     一般的第一个程序总是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  

     

    命令运行完之后在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);  

     

    细致观察一下这种方法,在凝视上标注类名、方法名、签名(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.   
    4.   
    5. JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello   
    6.   (JNIEnv* env, jobject obj, jstring name)  
    7. {  
    8.     const char* pname = env->GetStringUTFChars(name, NULL);  
    9.     cout << "Hello, " << pname << endl;  
    10. }  

     

    由于我们生成的那个头文件是在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.   
    3. public class SayHellotoCPP {  
    4.       
    5.     static{  
    6.         System.loadLibrary("HelloEnd");  
    7.     }  
    8.     public SayHellotoCPP(){  
    9.     }  
    10.     public native void sayHello(String name);  
    11.       
    12. }  

     

    这样我们的代码就能认识并载入这个动态链接库文件了。万事俱备,仅仅欠測试代码了,接下来编写測试代码。

    Java代码
    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);  

     

     这句有疑问,这个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.   
    3. import com.chnic.bean.Order;  
    4.   
    5. public class Business {  
    6.     static{  
    7.         System.loadLibrary("FruitFactory");  
    8.     }  
    9.       
    10.     public Business(){  
    11.           
    12.     }  
    13.       
    14.     public native double getPrice(String name);  
    15.     public native Order getOrder(String name, int amount);  
    16.     public native Order getRamdomOrder();  
    17.     public native void analyzeOrder(Order order);  
    18.       
    19.     public void notification(){  
    20.         System.out.println("Got a notification.");  
    21.     }  
    22.       
    23.     public static void notificationByStatic(){  
    24.         System.out.println("Got a notification in a static method.");  
    25.     }  
    26. }  

     

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

     

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

     

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

     

    能够看到,在我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"));  

     

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

     

    Before release: Apple
    After release: ??
    1.2

     

    在release之前打印出来的是我们“须要”的Apple,release之后就成了乱码了。因为传递的是Apple,所以得到1.2。測试成功。

     

    2. getOrder(String name, int amount)

     

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

     

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

     

    是不是认为这句代码似曾相识?没错,这句代码非常像我们java里写的Class.forName(className)反射的代码。事实上在这里 FindClass的作用和上面的forName是相似的。仅仅只是在forName中要用完整的类名,可是在这里必须用"/"来取代“.”。这种方法会返回一个jclass的对象,事实上也就是我们在Java中说的类对象。

     

    Java代码
    1. jmethodID construction_id = env->GetMethodID(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: 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);  

     

    这一句不难理解,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;  

     

    之前我为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());  

     

    执行上述的測试代码之后,控制台上打出了

     

    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));  
     

    取到了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);  
     

    控制台上打印出

     

    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中的反射机制,反之亦然。

     

    因此假设有对JNI编程有兴趣或者有更深入的须要,能够參考一下sun的相关文档。在这里上传sun提供的JNI的API手冊,还有上面样例中所用的演示代码给大家參考。

  • 相关阅读:
    (BFS 二叉树) leetcode 515. Find Largest Value in Each Tree Row
    (二叉树 BFS) leetcode513. Find Bottom Left Tree Value
    (二叉树 BFS DFS) leetcode 104. Maximum Depth of Binary Tree
    (二叉树 BFS DFS) leetcode 111. Minimum Depth of Binary Tree
    (BFS) leetcode 690. Employee Importance
    (BFS/DFS) leetcode 200. Number of Islands
    (最长回文子串 线性DP) 51nod 1088 最长回文子串
    (链表 importance) leetcode 2. Add Two Numbers
    (链表 set) leetcode 817. Linked List Components
    (链表 双指针) leetcode 142. Linked List Cycle II
  • 原文地址:https://www.cnblogs.com/yxwkf/p/3840057.html
Copyright © 2020-2023  润新知