文档章节

在Java中调用C/C++本地库

刘学炜
 刘学炜
发布于 2012/07/12 21:30
字数 1809
阅读 134
收藏 0

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类

public class Sample1 {

    public native int intMethod(int n);

    public native boolean booleanMethod(boolean bool);

    public native String stringMethod(String text);

    public native int intArrayMethod(int[] intArray);

     

    public static void main(String[] args) {

        System.loadLibrary("Sample1");

        Sample1 sample = new Sample1();

        int square = sample.intMethod(5);

        boolean bool = sample.booleanMethod(true);

        String text = sample.stringMethod("Java");

        int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});

         

        System.out.println("intMethod: " + square);

        System.out.println("booleanMethod: " + bool);

        System.out.println("stringMethod: " + text);

        System.out.println("intArrayMethod: " + sum);

    }

}

上面有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

 
/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class Sample1 */

 

#ifndef _Included_Sample1

#define _Included_Sample1

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     Sample1

 * Method:    intMethod

 * Signature: (I)I

 */

JNIEXPORT jint JNICALL Java_Sample1_intMethod

  (JNIEnv *, jobject, jint);

 

/*

 * Class:     Sample1

 * Method:    booleanMethod

 * Signature: (Z)Z

 */

JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod

  (JNIEnv *, jobject, jboolean);

 

/*

 * Class:     Sample1

 * Method:    stringMethod

 * Signature: (Ljava/lang/String;)Ljava/lang/String;

 */

JNIEXPORT jstring JNICALL Java_Sample1_stringMethod

  (JNIEnv *, jobject, jstring);

 

/*

 * Class:     Sample1

 * Method:    intArrayMethod

 * Signature: ([I)I

 */

JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod

  (JNIEnv *, jobject, jintArray);

 

#ifdef __cplusplus

}

#endif

#endif

我们可以看到其中有四个函数声明, 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;

}

(*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代码: (*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.

本文转载自:

共有 人打赏支持
刘学炜

刘学炜

粉丝 227
博文 68
码字总数 66313
作品 4
广州
高级程序员
私信 提问
JNI和NDK的区别

NDK(Native Development Kit)“原生”也就是二进制 android常用的开发方式是java封装的库,而这些库的底层实现是由C/C++实现,如媒体,图形库等 java调用这样实现就需要用JNI(Java Native...

长平狐
2013/01/06
62
0
Android NDK开发简介

最近由于项目的需要,使用到了Android的NDK技术,对项目核心算法跨平台的移植。简答而言,就是使用C对原来的算法进行了改进,并集成到原来的app项目里。 从前的项目一直没有使用NDK进行开发的...

zhiweiofli
2013/03/07
0
0
JNI和NDK的区别

NDK(Native Development Kit)“原生”也就是二进制 android常用的开发方式是java封装的库,而这些库的底层实现是由C/C++实现,如媒体,图形库等 java调用这样实现就需要用JNI(Java Native...

鉴客
2012/03/09
181
0
JNI和NDK的区别

NDK(Native Development Kit)“原生”也就是二进制 android常用的开发方式是java封装的库,而这些库的底层实现是由C/C++实现,如媒体,图形库等 java调用这样实现就需要用JNI(Java Native...

晨曦之光
2012/03/01
1K
0
Android JNI(一)——NDK与JNI基础

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头
2018/05/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

将svn服务从Windows迁移到Linux

因为更换服务器,需要将svn服务从Windows迁移到Linux 0.如果原服务器和目标服务器的svn版本不一致,会导致问题,使用 svnserve --version 分别在两个服务器上查看svnserve版本。此文以1.9为例...

YangTao
18分钟前
1
0
Set.js 值的任意集合 见JavaScript权威指南P217

/* Set.js 值的任意集合 见JavaScript权威指南P217 省略掉了部分方法 */ function Set(){ this.values={};//集合数据保存在...

十万猛虎下画山
24分钟前
2
0
如何合理的规划jvm性能调优

JVM性能调优涉及到方方面面的取舍,往往是牵一发而动全身,需要全盘考虑各方面的影响。但也有一些基础的理论和原则,理解这些理论并遵循这些原则会让你的性能调优任务将会更加轻松。为了更好...

阿里云官方博客
27分钟前
1
0
智能合约编程/Dapp漏洞 -- 小心使用构造函数

构造函数是一个比较特殊的函数,在构造函数里会执行一些初始化合约是比较关键的功能。在Solidity 版本0.4.22之前,构造函数是一个和合约同名的函数。所以如果在开发过程中,合约名变了的话,...

怎当她临去时秋波那一转
36分钟前
2
0
JDK8发送邮件报错:Network is unreachable -- preferIPv4Stack

摘要: 使用javamail发送邮件时,老是提示Network is Network: ? 1 2 3 4 com.sun.mail.util.MailConnectException: Couldn't connect to host, port: smtp. com.sun.mail.util.MailConnec......

spinachgit
43分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部