zoukankan      html  css  js  c++  java
  • 在Java中调用C/C++本地库

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

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

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

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

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

    1. 编写带有native方法的Java类, 使用javac工具编译Java类
    2. 使用javah来生成与native方法对应的头文件
    3. 实现相应的头文件, 并编译为动态链接库(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     }
    20 } 
    Java Sample1

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

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

    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
    View Code

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

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

    java类型 Signature 备注
    boolean Z  
    byte B  
    char C  
    short S  
    int I  
    long L  
    float F  
    double D  
    void V  
    object L用/分割的完整类名 例如: Ljava/lang/String表示String类型
    Array [签名 例如: [I表示int数组, [Ljava/lang/String表示String数组
    Method (参数签名)返回类型签名 例如: ([I)I表示参数类型为int数组, 返回int类型的方法

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

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

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

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

    实现头文件中的函数

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

    #include "Sample1.h"
    #include <string.h>
    
    JNIEXPORT jint JNICALL Java_Sample1_intMethod
      (JNIEnv *env, jobject obj, jint num)
    {
        return num * num;
    }
    
    JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
      (JNIEnv *env, jobject obj, jboolean boolean)
    {
        return !boolean;
    }
    
    JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
    
      (JNIEnv *env, jobject obj, jstring string)
    {
        const char* str = (*env)->GetStringUTFChars(env, string, 0);
        char cap[128];
        strcpy(cap, str);
        (*env)->ReleaseStringUTFChars(env, string, 0);
        return (*env)->NewStringUTF(env, strupr(cap));
    }
    
    JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
      (JNIEnv *env, jobject obj, jintArray array)
    {
        int i, sum = 0;
        jsize len = (*env)->GetArrayLength(env, array);
        jint *body = (*env)->GetIntArrayElements(env, array, 0);
    
        for (i = 0; i < len; ++i)
        {
            sum += body[i];
        }
        (*env)->ReleaseIntArrayElements(env, array, body, 0);
        return sum;
    }
    View Code

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

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

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

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

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

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

    下面是C++的代码

    #include "Sample1.h"
    #include <string.h>
    
    JNIEXPORT jint JNICALL Java_Sample1_intMethod
      (JNIEnv *env, jobject obj, jint num)
    {
        return num * num;
    }
    
    JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
      (JNIEnv *env, jobject obj, jboolean boolean)
    {
        return !boolean;
    }
    
    JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
      (JNIEnv *env, jobject obj, jstring string)
    {
        const char* str = env->GetStringUTFChars(string, 0);
        char cap[128];
        strcpy(cap, str);
        env->ReleaseStringUTFChars(string, 0);
        return env->NewStringUTF(_strupr(cap));
    }
    
    JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
      (JNIEnv *env, jobject obj, jintArray array)
    {
        int i, sum = 0;
        jsize len = env->GetArrayLength(array);
        jint *body = env->GetIntArrayElements(array, 0);
        
        for (i = 0; i < len; ++i)
        {
            sum += body[i];
        }
        env->ReleaseIntArrayElements(array, body, 0);
        return sum;
    }
    C++实现

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

    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%includewin32 -LD Sample1.c -FeSample1.dll

    编译C++版本的dll

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

    方法二、用vs新建dll项目

    1.新建dll空项目:文件-》新建项目-》Win32控制台应用程序-》应用程序类型为DLL-》附加选项为空项目-》完成

    2.项目-》属性-》c/c++ -》常规-》附加包含目录是%JAVA_HOME%include和%JAVA_HOME%includewin32对应的路径。

    3.有的版本的vs可能需要在预处理器中加入_CRT_SECURE_NO_WARNINGS选项。

    运行

    >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
     
     
     
     
    原作者:icejoywoo
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 短网址: http://goo.gl/ZiZCi
  • 相关阅读:
    590. N-ary Tree Postorder Traversal
    C++——指针3
    C++——指针2-指向数组的指针和指针数组
    C++——指针1
    C++——二维数组和对象数组
    C++——一维数组
    C++——程序的结构
    C++——类与对象
    C++——函数
    C++——简单程序设计
  • 原文地址:https://www.cnblogs.com/jaejaking/p/6840530.html
Copyright © 2011-2022 走看看