如何在Java中调用C_C++方法

有时,需要在Java中直接调用本地(native)方法,把一些耗时的操作使用效率更高C/C++实现。在Java中调用本地方法通常被称为Java Native Interface(JNI)。那么,什么时候需要用到JNI了?

  • 需要在底层实现耗时更小、更快的程序
  • 在Java中需要调用本地代码库
  • 需要使用平台相关但Java标准库文件不支持的特性

接下来,我们就来看一看,如何在Java中通过JNI来调用本地方法?

准备工作

在开始JNI编程之前,需要确保在系统环境中有配置好了如下程序:

  • Java虚拟机程序, Java编译器(javac),一个本地方法的头文件生成程序(javah)
  • 定义JNI的库文件以及本地头文件: jni.h,jvm.lib,jvm.dll,jvm.so
  • c/C++编译器,用于创建共享库代码

前面两条中的程序只要安装了JDK开发环境,都已包含在系统中了,可以直接使用。另外,需要注意的是,利用System.loadLibrary来加载本地库文件时,不同系统对于生成的库文件命名存在差异,例如在HelloWorld.java使用System.loadLibrary(“HelloWorld”):

  • Solaris: libHelloWorld.so
  • Linux: libHelloWorld.so
  • Win: HelloWorld.dll
  • Mac: libHelloWorld.jnilib

在生成本地库文件时需要留意。接下来,我们就一步步来看下如何在Java中调用本地方法(以下实现均基于Ubuntu 16.04 64bit版本)。

六步实现在Java中调用本地方法

1.编写Java文件

指定一个文件夹下,新建一个HelloJni.java文件,并在其中声明需要调用的本地方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public class HelloJni{
public native int add(int x, int y);
public native String talkBack(String str);
public native void sayHello(String str);

static{
System.loadLibrary("HelloJni");
}


public static void main(String[] args){

HelloJni jni = new HelloJni();

int add = jni.add(5,3);
jni.sayHello("hello jni");

System.out.println("add: " + add);
System.out.println("talkBack: " + jni.talkBack("hello"));
}
}

2. 编译Java文件,生成.class文件

在HelloJni.java目录下,打开命令行工具,输入:

1
2
3

javac HelloJni.java

查看该文件夹,可以看到已经生成了相应的.class文件。

3. 生成本地方法对应的头文件

利用JDK中提供的javah程序,我们可以生成Java文件中对应的本地方法头文件,在命令行中输入:

1
2
3

javah HelloJni

在对应的目录下,可以看到一个名为HelloJni.h的文件,长成这样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJni */

#ifndef _Included_HelloJni
#define _Included_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJni
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_HelloJni_add
(JNIEnv *, jobject, jint, jint);

/*
* Class: HelloJni
* Method: talkBack
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloJni_talkBack
(JNIEnv *, jobject, jstring);

/*
* Class: HelloJni
* Method: sayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloJni_sayHello
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

4. 编写对应的C/C++方法

按照上述生成的HelloJni.h文件,注意函数的名称与返回值必须与HelloJni.h中完全一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

#include "HelloJni.h"
#include <stdio.h>
#include <string.h>


JNIEXPORT jint JNICALL Java_HelloJni_add
(JNIEnv *env, jobject obj, jint n1, jint n2){
return n1 + n2;
}


JNIEXPORT jstring JNICALL Java_HelloJni_talkBack
(JNIEnv *env, jobject obj, jstring str){
//注意在C中,应为:const char *s = (*env)->GetStringUTFChars(str,0)
const char *s = env->GetStringUTFChars(str,0);
if(strcmp(s,"hello") == 0 || strcmp(s,"hi") == 0){
return env->NewStringUTF("jni from c/c++");
}

return env->NewStringUTF("unknow talking");
}


JNIEXPORT void JNICALL Java_HelloJni_sayHello
(JNIEnv *env, jobject obj, jstring str){
const char *s = env->GetStringUTFChars(str,0);
char chs[100];
strcpy(chs,s);
printf(chs);
}

5. 生成共享库文件

这里利用Ubuntu系统自带的编译器gcc来产生共享库文件。有关gcc命令的参数可参考 http://man7.org/linux/man-pages/man1/gcc.1.html;在这里,需要主义两点,一是编译时需要包含两个JNI相关的头文件jni.h以及jni_md.h,否则编译时会出现错误:

对于Ubuntu系统,JDK一般安装在:/usr/lib/jvm

1
2
3
4
5
6

:~/Java$ gcc -shared -fPIC libHelloJni.so HelloJni.cpp
In file included from HelloJni.cpp:1:0:
HelloJni.h:2:17: fatal error: jni.h: No such file or directory
compilation terminated.

一个时生成的库文件名前面需要加上lib,如libHelloJni.so,使用System.loadLibrary调用时则去掉lib:System.loadLibrary(“HelloJni”),否则调用时可能出现无法找到库文件的错误java.lang.UnsatisfiedLinkError,完整的编译命令如下:

1
2
3

gcc -I/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux -fPIC -shared -o libHelloJni.so HelloJni.cpp

6. 运行java程序

在命令行中输入(需要指定.class文件的搜索路径):

1
2
3

java -Djava.library.path=/home/jason/Java HelloJni

java命令可参考:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html

需要注意的地方

  • 确保.c/.cpp文件中函数名称与生成的.h头文件中的一致
  • 不同操作系统中,生成的共享库文件名称不一样,例如: 在HelloJni.java,System.loadLibrary(“HelloJni”);
    Solaris: libHelloJni.so
    Linux: libHelloJni.so
    Win: HelloJni.dll
    Mac: libHelloJni.jnilib
  • 执行Java程序时,添加参数 -Djava.library.path=PATH,这里PATH就是JNI库所在位置

参考文献