学习使用Java Native Interface
我们知道Java本身的实现,很大一部分是用C++写的。实际上,Java也允许我们和原生平台的代码进行交互。
Java定义了一个接口规范,就叫做Java Native Interface
,通过这个接口规范,我们就可以让Java代码运行原生平台的代码。
首先写一个Java的Class1:
public class HelloJNI {
static {
System.loadLibrary("hello"); // Load native library at runtime
// hello.dll (Windows) or libhello.so (Unixes)
}
// Declare a native method sayHello() that receives nothing and returns void
private native void sayHello();
// Test Driver
public static void main(String[] args) {
new HelloJNI().sayHello(); // invoke the native method
}
}
从上面的代码,我们看到一些平常写Java代码的时候不常看见的东西,首先是这个:
System.loadLibrary("hello");
这个System.loadLibrary
方法可以用来调用平台的原生库。然后是这个:
private native void sayHello();
这个native
关键字意味着这个代码的实现是原生平台实现,而不是Java代码。因此我们要做一个hello
库,提供这个sayHello
方法。
接下来我们用javah
命令生成.h
文件:
$ ls HelloJNI.java
HelloJNI.java
$ javah HelloJNI
$ ls *.h
HelloJNI.h
我们得到了HelloJNI.h
文件,它的内容是根据HelloJNI.java
生成的,我们看看里面的内容:
/* 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: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
可以看到上面的文件里自动生成了JNI
接口规范的一些约定代码。比如jni.h
这个是由JDK提供的,我们稍后要引用。还有sayHello
对应的C/C++
的方法是Java_HelloJNI_sayHello
,这个命名方式可以看出来是Java_ClassName_MethodName
,这个也是JNI
的约定。
接下来我们写一个HelloJNI.c
来实现这个HelloJNI.h
:
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
这个代码就非常简单,就是实现了Java_HelloJNI_sayHello
,功能就是printf("Hello World!\n");
。我们编译这个代码:
$ c++ -c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" HelloJNI.c
注意我是在MacOS上编译,所以引用的是MacOS的JDK的include
目录,此外MacOS的内核名叫darwin
,你如果用Linux系统,你的JDK的include
目录里应该有一个linux
目录,对应上面的darwin
目录。
这两个-I
主要是为了引用JNI
相关的库文件:
$ ls $JAVA_HOME/include/jni*
/Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/include/jni.h
$ ls $JAVA_HOME/include/darwin/jni*
/Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/include/darwin/jni_md.h
这两个.h
文件是必须的。编译完成后,我们就得到了object文件:
$ ls *.o
HelloJNI.o
用nm
命令看一下这个object file里面的符号:
$ nm HelloJNI.o
0000000000000000 T _Java_HelloJNI_sayHello
U _printf
可以看到包含的的methods。我们要把这个object文件链接成library,在MacOS下命令如下:
$ c++ -dynamiclib -o libhello.jnilib HelloJNI.o
这样我们就得到了Java可以调用的libhello.jnilib
:
$ ls *.jnilib
libhello.jnilib
我们在Java代码里写的是System.loadLibrary("hello")
,但是这里的library名字叫做libhello
,前面多了lib
,这是一种命名约定。
如果你在Linux系统下,生成library的命令不太一样,需要用下面的命令生成.so
文件,也就是shared library文件:
cc -shared -fpic -o libhello.so -I/usr/java/include -I/usr/java/include/linux HelloJNI.c
接下来就是编译Java代码:
$ javac HelloJNI.java
我们得到class文件:
$ ls *.class
HelloJNI.class
此时目录里应该有我们的library还有class文件:
$ ls *.class *.jnilib
HelloJNI.class libhello.jnilib
此时我们可以使用java
命令运行HelloJNI.class
:
$ java HelloJNI
Hello World!
可以看到我们这个Hello World!
字符串是来自于native method的Java_HelloJNI_sayHello
里面的printf
。我们的Java代码与原生平台进行了交互。
参考资料:
-
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html ↩