start with java and c
这个库函数应该包含在java的库的路径中(这里有一个专门的java系统变量是处理这个的
java.library.path),对于我们这种自定义的library,在编译java程序时可以传递过去。
-Djava.library.path=/path/to/lib
,如果java找不到的话,则会throw一个
UnsatisfiedLinkError的错误。
public class myJni { // save as JNI.java
static {
System.loadLibrary("hello"); // load library libhello.so on UBIX
// at runtime, the library contains
} // a native method called sayHello()
private native void sayHello();
public static void main(String[] args){
new Jni().sayHello(); // Create an instance and invoke the native method
}
}
Compile the java program myJni.java
javac -h . myJni.java
:我们来看一下javac -h.
-h <directory> 指定放置生成的本机标头文件的位置
ls:
vimer@host:~/src/test/java$ ls
myJni.class myJni.h myJni.java
hit: 我这是OpenJdk9.其他版本的请自己寻找一下相关的用法。
我们来看一下会生成的相关的头文件。
vimer@host:~/src/test/java$ cat myJni.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class myJni */
#ifndef _Included_myJni
#define _Included_myJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: myJni
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_myJni_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
其中最主要的就是下面这个:
JNIEXPORT void JNICALL Java_myJni_sayHello
(JNIEnv *, jobject);
这个命名规则是Java_{package-and-classname}_{function-name}(JNI-argument)
这里就有一个大名鼎鼎的dot代替下划线的替换。
这两个参数是:
-
JNIEnv\*
: JNI环境,请注意这个JNI是广泛的特指,而不是 本例的JNI.java中的JNI。
-
jobject
: “this” object in java.
“extern c”是针对c++编译器而言的。它通知c++的编译器这些函数使用c的命名规则而不是c++的 命名规则。
implement c program myJni.c
我们首先对应的c程序,不然就算写出来了h文件也没有什么用途。
#include <stdlib.h>
#include <unistd.h>
#include <jni.h> // JNI header provided by JDK
#include "myJni.h" // Generated file from javac
JNIEXPORT void JNICALL Java_myJni_sayHello(JNIEnv *env, jobject thisObj){
printf("Hello, Jni world\n");
return;
}
使用命令去编译这个c程序生成动态加载库:
vimer@host:~/src/test/java$ gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJni.c
vimer@host:~/src/test/java$ ls
libhello.so myJni.c myJni.class myJni.h myJni.java
vimer@host:~/src/test/java$ nm libhello.so | grep say
000000000000061a T Java_myJni_sayHello
vimer@host:~/src/test/java$ java -Djava.library.path=. myJni
Hello, Jni world
可以使用tool nm查看一下动态库有没有定义这个符号,我们可以清楚地看到,为T(定义)。 然后使用java命令就可以将动态库的方法加载进去了。
c++
将上面的java代码保持不变,还是使用这个。唯一的不同是使用:
#include <iostream>
#include <jni.h>
#include "myJni.h"
using namespace std;
// Implementions of native method sayHello()
JNIEXPORT void JNICALL Java_myJni_sayHello(JNIEnv *env, jobject thisObj){
cout << "Hello jni world from c++" << endl;
return;
}
接下来不变:
g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJni.cpp
vimer@host:~/src/test/java$ java -Djava.library.path=. myJni
Hello jni world from c++
JNI with C/C++ mixture
这一个例子是揭示如何利用一个java代码同时使用c或者c++的程序。这次的java代码就有几处 不同。
public class myJniCpp { // save as JNI.java
static {
System.loadLibrary("hello"); // load library libhello.so on UBIX
// at runtime, the library contains
} // a native method called sayHello()
private native void sayHello();
public static void main(String[] args){
new myJniCpp().sayHello(); // Create an instance and invoke the native method
}
}
接着产生myJniCpp.h文件。
vimer@host:~/src/test/java$ javac -h . myJniCpp.java
详细看一下这个文件的内容。
cat myJniCpp.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class myJniCpp */
#ifndef _Included_myJniCpp
#define _Included_myJniCpp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: myJniCpp
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_myJniCpp_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
当然,我们最关心的应该还是sayHello()方法的实现。
然后我们利用这个头文件分别实现对应的c和c++的头文件。什么?需要吗?按照我的理解是这样的 一个java服务程序,当然支持c或者c++的程序最好了,那既然上面的方法我们分别实现了对应的 native代码,说明肯定有冲突的地方。
myJniCppImpl.h myJniCppImpl.cpp myJniCpp.c
这三个文件各自有自己的用途,我们先看一下myJniCppImpl.h, 这是一个C++的头文件。
#ifndef _MY_JNI_CPP_IMPL_H
#define _MY_JNI_CPP_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
void sayHello();
#ifdef __cplusplus
}
#endif
#endif
cat myJniCppImpl.cpp:
#include <iostream>
#include "myJniCppImpl.h"
using namespace std;
void sayHello(){
cout << "hello wrold from c++!" << endl;
return;
}
很明显这里就是在c++的程序中实现了c库的函数,其实这里很复杂的,涉及到了编译器 对c++ c函数的编译声明方式。 cat myJniCpp.c:
#include <jni.h>
#include "myJniCpp.h"
#include "myJniCppImpl.h"
JNIEXPORT void JNICALL Java_myJniCpp_sayHello(JNIEnv *env, jobject thisObj){
sayHello();// invoke c++ function
return;
}
这个c程序负责与java程序的交互。下面是编译参数:
g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJniCpp.c myJniCppImpl.cpp
如果不注意的话则会出错,比如下面这个。
In file included from myJniCpp.c:3:0:
myJniCppImpl.h:5:10: error: language string ‘"c"’ not recognized
extern "c" {
^~~
In file included from myJniCppImpl.cpp:2:0:
myJniCppImpl.h:5:10: error: language string ‘"c"’ not recognized
extern "c" {
^~~
记住,调用c库的函数时一定注意这个错误,这个错误很牛逼,就是: 你把该大写的”C”写成了小写”c”.
Run java
java -Djava.library.path=. myJniCpp
hello wrold from c++!
我们总结一下这个过程,首先在java程序中使用native关键词说明本java方法使用底层库 的一个函数。然后我们使用c++实现了具体的方法,后面使用c程序invoke c++的实现。