JNI即Java Native Interface(Java本地接口),是一个协议,主要作用为:实现Java调用c/c++代码(类库),或者C/C++调用Java代码。本文主要介绍通过JNI实现java调用C/C++

通过Java jni来调用native code,需要如下几个步骤:

  1. 编写java代码,使用native关键字声明本地方法接口(例如:native void printstr(String str);),以及指定加载的C/C++库(System.load("/data/javaJNI.so"););
  2. javac编译出class文件,然后在用javah导出头文件;
  3. 编写C/C++代码,实现本地方法接口;
  4. 将native code编译成shared library供Java调用;

接下来我们看一个例子:

1)java代码:JavaJNI.java

public class JavaJNI {
static{
System.load("/data/javaJNI.so");
}

native void printstr();
native void printstr(String str);

public static void main(String[] args) {
JavaJNI javaJNI = new JavaJNI();
javaJNI.printstr();
javaJNI.printstr("wo shi ben di diao yong ce shi");
}
}

这里声明了两个本地方法接口;同时指定了将来C/C++实现了本地方法接口的动态库路径。

2)编译、导出头文件:

$ pwd
/data/kevinliu/jni

$ javac JavaJNI.java
$ javah JavaJNI
$ ll
-rw-r--r-- 1 root root 521 Oct 12 10:48 JavaJNI.class
-rw-r--r-- 1 root root 557 Oct 12 10:48 JavaJNI.h
-rw-r--r-- 1 root root 324 Oct 12 10:46 JavaJNI.java

注:javah后面跟得是类全路径。这里生成的JavaJNI.h是自动生成的,我们大致看下内容:

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

#ifndef _Included_JavaJNI
#define _Included_JavaJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JavaJNI
* Method: printstr
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JavaJNI_printstr__
(JNIEnv *, jobject);

/*
* Class: JavaJNI
* Method: printstr
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_JavaJNI_printstr__Ljava_lang_String_2
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

3)编写C代码,实现对应的方法:JavaJNI.c

#include "JavaJNI.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_JavaJNI_printstr__(JNIEnv *env, jobject obj) {
printf("%s\n","hello jni");
return;
}

JNIEXPORT void JNICALL Java_JavaJNI_printstr__Ljava_lang_String_2(JNIEnv *env, jobject obj, jstring string){
const char *str = (*env)->GetStringUTFChars(env,string,0);
printf("%s\n",str);
}
  • 导入刚才生成的头文件;
  • 头文件中的方法实现,直接将头文件中的方法原型复制过来加以实现即可,注意添加形参变量;

4)编译c,生成动态链接库:

首先,我们先编一下JavaJNI.c,看是否有语法错误:

$ gcc -c JavaJNI.c
$ ll
-rw-r--r-- 1 root root 353 Oct 12 10:48 JavaJNI.c
-rw-r--r-- 1 root root 521 Oct 12 10:48 JavaJNI.class
-rw-r--r-- 1 root root 557 Oct 12 10:48 JavaJNI.h
-rw-r--r-- 1 root root 324 Oct 12 10:46 JavaJNI.java
-rw-r--r-- 1 root root 1712 Oct 12 10:49 JavaJNI.o

$ rm -rf JavaJNI.o

生成动态链接库:

$ find / -name jni.h
/usr/local/lib/gcc/x86_64-unknown-linux-gnu/4.8.5/include/jni.h
/usr/java/jdk1.8.0_121/include/jni.h

$ gcc -fPIC -shared -I/usr/java/jdk1.8.0_121/include -I/usr/java/jdk1.8.0_121/include/linux -o javaJNI.so JavaJNI.c
$ ll
total 24
-rw-r--r-- 1 root root 353 Oct 12 10:48 JavaJNI.c
-rw-r--r-- 1 root root 521 Oct 12 10:48 JavaJNI.class
-rw-r--r-- 1 root root 557 Oct 12 10:48 JavaJNI.h
-rw-r--r-- 1 root root 324 Oct 12 10:46 JavaJNI.java
-rwxr-xr-x 1 root root 7954 Oct 12 10:51 javaJNI.so

5)运行:

$ pwd
/data/kevinliu/jni
$ mv javaJNI.so /data
$ java JavaJNI
hello jni
wo shi ben di diao yong ce shi

到这里,我们就完成了通过jni让java调用C代码。接下来,我们试试java调用C++代码。

1)java代码:JavaJNI.java

同上

2)编译、导出头文件:

同上

3)编写C++代码,实现对应的方法:JavaJNI.cpp

#include "JavaJNI.h"
#include <iostream>

using namespace std;

JNIEXPORT void JNICALL Java_JavaJNI_printstr__(JNIEnv *env, jobject obj) {
cout<<"hello jni cpp..."<<endl;
return;
}

JNIEXPORT void JNICALL Java_JavaJNI_printstr__Ljava_lang_String_2(JNIEnv *env, jobject obj, jstring string){
const char *str = env->GetStringUTFChars(string,0);
cout<<str<<endl;
}

这里需要注意JNIEnv 的使用:

对于JNIEnv *env来说,在C中调用:(*env)->NewStringUTF(env, "Hello from JNI!");
而在C++中如果按照上述调用则会发生'base operand of '->' has non-pointer type '_JNIEnv''错误,需要如下调用:
env->NewStringUTF("Hello from JNI!");
原因:参见jni.h中对于JNIEnv的定义:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
#else
typedef const struct JNINativeInterface* JNIEnv;
#endif

4)编译c++,生成动态链接库:

$ g++ -c JavaJNI.cpp 
JavaJNI.cpp: In function ‘void Java_JavaJNI_printstr__Ljava_lang_String_2(JNIEnv*, jobject, jstring)’:
JavaJNI.cpp:12:27: error: base operand of ‘->’ has non-pointer type ‘JNIEnv {aka _Jv_JNIEnv}’
const char *str = (*env)->GetStringUTFChars(env,string,0);
#需要按照上面的方式改动env


$ g++ -fPIC -shared -I/usr/java/jdk1.8.0_121/include -I/usr/java/jdk1.8.0_121/include/linux -o javaJNI.so JavaJNI.cpp

$ ll
-rw-r--r-- 1 root root 521 Oct 12 10:48 JavaJNI.class
-rw-r--r-- 1 root root 370 Oct 12 10:56 JavaJNI.cpp
-rw-r--r-- 1 root root 557 Oct 12 10:48 JavaJNI.h
-rw-r--r-- 1 root root 324 Oct 12 10:46 JavaJNI.java
-rwxr-xr-x 1 root root 8638 Oct 12 10:57 javaJNI.so

5)运行:

$ mv javaJNI.so /data
$ java JavaJNI
hello jni cpp...
wo shi ben di diao yong ce shi

 最后

  • System.load(这里是.so的绝对路径);
  • System.loadLibrary(这里是.so的文件名),系统会自动匹配,但是.so文件需要放在java.library.path路径中,该路径可以通过System.getProperty(“java.library.path”)查看,我也不是很懂,就不误导大家了;