使用JNI进行混合编程:在Java中调用C/C++本地库

使用JNI进行混合编程:在Java中调用C/C++本地库

JNI是Java Native Interface的英文缩写, 中文翻译为本地调用, 自从Java 1.1开始就成为了Java标准的一部分。

AD:


    JNI是Java Native Interface的英文缩写, 中文翻译为本地调用, 自从Java 1.1开始就成为了Java标准的一部分。

    C/C++是系统级的编程语言, 可以用来开发任何和系统相关的程序和类库, 但是Java本身编写底层的应用比较难实现, 使用JNI可以调用现有的本地库, 极大地灵活了Java的开发。

    C/C++的效率是目前最好的语言, 可以使用C/C++来实现一些实时性非常高的部分. C/C++和Java本身都是非常流行的编程语言, 一些大型软件中经常使用语言之间的混合编程。

    鉴于目前网络上JNI的文章不是特别多, 我将自己的一些总结写在这里. 如有错漏, 欢迎指正!

    Java调用C/C++大概有这样几个步骤

    • 编写带有native方法的Java类, 使用javac工具编译Java类
    • 使用javah来生成与native方法对应的头文件
    • 实现相应的头文件, 并编译为动态链接库(windows下是.dll, linux下是.so)

    下面就完整的介绍一个简单的Java调用C/C++的例子, 这个例子是来自http://www.ibm.com/developerworks/cn/education/java/j-jni/index.html, 不过其中有一些错误, 这个文章是非常不错的JNI学习资料, 但是非常古老.

    编写Java类

    我们来编写一个Sample1的java类

       
       
    1. public class Sample1 {  
    2.     public native int intMethod(int n);  
    3.     public native boolean booleanMethod(boolean bool);  
    4.     public native String stringMethod(String text);  
    5.     public native int intArrayMethod(int[] intArray);  
    6.        
    7.     public static void main(String[] args) {  
    8.         System.loadLibrary("Sample1");  
    9.         Sample1 sample = new Sample1();  
    10.         int square = sample.intMethod(5);  
    11.         boolean bool = sample.booleanMethod(true);  
    12.         String text = sample.stringMethod("Java");  
    13.         int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});  
    14.            
    15.         System.out.println("intMethod: " + square);  
    16.         System.out.println("booleanMethod: " + bool);  
    17.         System.out.println("stringMethod: " + text);  
    18.         System.out.println("intArrayMethod: " + sum);  
    19.     }  

    上面有4个native方法, 分别是4种类型的参数, int, boolean, String, int[].

    其中有一句比较重要, 这句话加载了动态类库

       
       
    1. System.loadLibrary("Sample1"); 

    在windows下加载的就是Sample1.dll, 在linux下加载的就是Sample1.so。

    本文使用的windowws, 所以后面使用Sample1.dll来表示Sample1动态链接库。

    注意:不可以在代码中写上后缀dll或so. 还要保证Sample1.dll在path路径中. 这个Sample1.dll是我们后面需要编译出来的东西。

    4个native方法就是我们需要用C来实现的方法。

    编译Sample1.java, 使用命令行(windows是cmd, linux下一般是bash)

    >javac Sample1.java

    可以看到Sample1.class文件

    使用javah生成头文件

    在命令行中运行

    >javah Sample1

    可以在目录下看到一个新文件Sample1.h

       
       
    1. /* DO NOT EDIT THIS FILE - it is machine generated */ 
    2. #include <jni.h>  
    3. /* Header for class Sample1 */ 
    4.    
    5. #ifndef _Included_Sample1  
    6. #define _Included_Sample1  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /*  
    11.  * Class:     Sample1  
    12.  * Method:    intMethod  
    13.  * Signature: (I)I  
    14.  */ 
    15. JNIEXPORT jint JNICALL Java_Sample1_intMethod  
    16.   (JNIEnv *, jobject, jint);  
    17.    
    18. /*  
    19.  * Class:     Sample1  
    20.  * Method:    booleanMethod  
    21.  * Signature: (Z)Z  
    22.  */ 
    23. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod  
    24.   (JNIEnv *, jobject, jboolean);  
    25.    
    26. /*  
    27.  * Class:     Sample1  
    28.  * Method:    stringMethod  
    29.  * Signature: (Ljava/lang/String;)Ljava/lang/String;  
    30.  */ 
    31. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod  
    32.   (JNIEnv *, jobject, jstring);  
    33.    
    34. /*  
    35.  * Class:     Sample1  
    36.  * Method:    intArrayMethod  
    37.  * Signature: ([I)I  
    38.  */ 
    39. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod  
    40.   (JNIEnv *, jobject, jintArray);  
    41.    
    42. #ifdef __cplusplus  
    43. }  
    44. #endif  
    45. #endif 

    我们可以看到其中有四个函数声明, Java_完整类名_方法名, 完整类名包括了包名, 例如demo.Sample1是完整类名, 对应的这里就是demo_Sample1.

    在注释中我们可以看到这样一个东西 Signature, 这个是方法的签名. 关于Signature, 下面通过一个表格来说明。

    java类型Signature备注
    booleanZ 
    byteB 
    charC 
    shortS 
    intI 
    longL 
    floatF 
    doubleD 
    voidV 
    objectL用/分割的完整类名例如: Ljava/lang/String表示String类型
    Array[签名例如: [I表示int数组, [Ljava/lang/String表示String数组
    Method(参数签名)返回类型签名例如: ([I)I表示参数类型为int数组, 返回int类型的方法

    上面头文件的第一个函数声明

       
       
    1. JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint); 

    注释中的签名是 Signature: (I)I

    在每个函数的参数列表中都有JNIEnv *和 jobject两个参数, 这两个参数稍候说明。

    实现头文件中的函数

    可以使用C语言来实现, 也可以使用C++来实现, 下面先说说C语言的实现。

       
       
    1. #include "Sample1.h" 
    2. #include <string.h>  
    3.    
    4. JNIEXPORT jint JNICALL Java_Sample1_intMethod  
    5.   (JNIEnv *env, jobject obj, jint num)  
    6. {  
    7.     return num * num;  
    8. }  
    9.    
    10. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod  
    11.   (JNIEnv *env, jobject obj, jboolean boolean)  
    12. {  
    13.     return !boolean;  
    14. }  
    15.    
    16. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod  
    17.   (JNIEnv *env, jobject obj, jstring string)  
    18. {  
    19.     const char* str = (*env)->GetStringUTFChars(env, string, 0);  
    20.     char cap[128];  
    21.     strcpy(cap, str);  
    22.     (*env)->ReleaseStringUTFChars(env, string, 0);  
    23.     return (*env)->NewStringUTF(env, strupr(cap));  
    24. }  
    25.    
    26. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod  
    27.   (JNIEnv *env, jobject obj, jintArray array)  
    28. {  
    29.     int i, sum = 0;  
    30.     jsize len = (*env)->GetArrayLength(env, array);  
    31.     jint *body = (*env)->GetIntArrayElements(env, array, 0);  
    32.        
    33.     for (i = 0; i < len; ++i)  
    34.     {  
    35.         sum += body[i];  
    36.     }  
    37.     (*env)->ReleaseIntArrayElements(env, array, body, 0);  
    38.     return sum;  

    (*env)->GetStringUTFChars()这个方法, 是用来在Java和C之间转换字符串的, 因为Java本身都使用了双字节的字符, 而C语言本身都是单字节的字符, 所以需要进行转换.

    JNIEnv *是每个函数都有的参数, 它包含了很多有用的方法, 使用起来类似Java的反射, 也提供了这样一个编码转换的函数.

    GetStringUTFChars()和NewStringUTF(), 第一个是从UTF8转换为C的编码格式, 第二个是根据C的字符串返回一个UTF8字符串.

    ReleaseStringUTFChars()是用来释放对象的, 在Java中有虚拟机进行垃圾回收, 但是在C语言中, 这些对象必须手动回收. 否则可能造成内存泄漏.

    函数的名字一眼看到就可以猜出功能, jni.h中的大部分函数名都是这样.

    如果是C++的话, 这段代码该怎么写?

    下面是C++的代码

       
       
    1. #include "Sample1.h"  
    2. #include <string.h>  
    3.    
    4. JNIEXPORT jint JNICALL Java_Sample1_intMethod  
    5.   (JNIEnv *env, jobject obj, jint num)  
    6. {  
    7.     return num * num;  
    8. }  
    9.    
    10. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod  
    11.   (JNIEnv *env, jobject obj, jboolean boolean)  
    12. {  
    13.     return !boolean;  
    14. }  
    15.    
    16. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod  
    17.   (JNIEnv *env, jobject obj, jstring string)  
    18. {  
    19.     const char* str = env->GetStringUTFChars(string, 0);  
    20.     char cap[128];  
    21.     strcpy(cap, str);  
    22.     env->ReleaseStringUTFChars(string, 0);  
    23.     return env->NewStringUTF(strupr(cap));  
    24. }  
    25.    
    26. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod  
    27.   (JNIEnv *env, jobject obj, jintArray array)  
    28. {  
    29.     int i, sum = 0;  
    30.     jsize len = env->GetArrayLength(array);  
    31.     jint *body = env->GetIntArrayElements(array, 0);  
    32.        
    33.     for (i = 0; i < len; ++i)  
    34.     {  
    35.         sum += body[i];  
    36.     }  
    37.     env->ReleaseIntArrayElements(array, body, 0);  
    38.     return sum;  

    上述两端代码非常相似, 只有一个不同点

    C代码: (*env)->GetStringUTFChars(env, string, 0);

    C++代码: env->GetStringUTFChars(string, 0);

    C语言中使用的是结构体的函数指针, 而在C++中使用的还是struct, 我们知道struct在C++中和class的功能是几乎一样的, struct也可以用来定义类, 所以env在C++中是个类对象的指针.

    编译和运行

    这里使用的是微软编译器, 编译C语言版的dll

    >cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.c -FeSample1.dll

    编译C++版本的dll

    >cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.cpp -FeSample1.dll

    运行

    >java Sample1

    注意: 64位版本的JDK可能会在运行时报错:

    java.lang.UnsatisfiedLinkError: ...Sample1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform

    如果您有这样的错误, 请使用32位的JDK来重新运行.

    运行结果如下:

    intMethod: 25 
    booleanMethod: false 
    stringMethod: JAVA 
    intArrayMethod: 36

    源代码下载: Sample1.zip

    运行其中的build&run.bat文件即可, 如有错误请根据实际情况修改其中的一些参数.

    DLL工程文件VC6.0和VS2010的: VC6.0&VS2010.zip

    参考文献:

    1. Scott Stricker, 用 JNI 进行 Java 编程, http://www.ibm.com/developerworks/cn/education/java/j-jni/section2.html
    2. JDK 6u30 docs, Java Native Interface Specification, Chapter 3 JNI Types and Data Structures, Type Signatures.

    原文链接:http://www.cnblogs.com/icejoywoo/archive/2012/02/22/2363709.html

    • 0
      点赞
    • 2
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值