Linux环境下JNI say hello的例子
参考文章:https://www.ibm.com/developerworks/cn/java/l-linux-jni/
JNI是Java Native Interface的缩写,JVM可以通过JNI去调用本地(Native)方法,目前做后端开发的还是挺少会去写JNI的,因为本身JNI就有一定的性能开销。目前来说可能客户端用的比较多,下面说下碰到的使用场景吧:
- android一些计算密集型的代码限于机能可能会通过JNI来去实现
- 还有一些对安全要求较高的加密解密的代码会放到so中去,毕竟java代码反编译比较容易,本身so文件反编译就比较麻烦,又可以继续通过加固来增加反编译的难度
- 一些跨平台的游戏引擎比如cocos是用c++开发的,编译到安卓平台需要通过JNI来实现
下面通过一个简单的say hello的例子来简单了解一下jni
第一步实现一个Java类,Hello 它提供SayHello方法:
public class Hello {
static {
try {
// 需要加载的动态链接库
System.loadLibrary("hello");
} catch (UnsatisfiedLinkError e) {
System.err.println("Cannot load hello library:\n" + e.toString());
}
}
// 要使用的本地方法的声明
public native void SayHello(String str);
}
然后通过javac编译生成Hello.class文件
第二步,生成本地动态链接库
1、要为Hello这个类生成Java本地接口的头文件,JDK中为我们提供了javah来去生产JNI的头文件,不再需要我们再去写头文件通过如下命令:
%JAVA_HOME%/bin/javah Hello
之后可以看到当前目录多了一个Hello.h文件,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: SayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Hello_SayHello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
2、创建Hello.cpp文件实现Hello.h中声明的函数,内容如下:
#include "Hello.h"
#include <stdio.h>
// Hello.h中函数声明的实现
JNIEXPORT void JNICALL Java_Hello_SayHello
(JNIEnv* env, jobject arg, jstring instring) {
// 从 instring字符串取得指向字符串 UTF 编码指针
const jbyte* str = (const jbyte*) env->GetStringUTFChars(instring, JNI_FALSE);
printf("Hello, %s\n", str);
// 通知虚拟机本地代码不再需要通过 str 访问 Java字符串。
env->ReleaseStringUTFChars(instring, (const char*)str);
return;
}
所有的 JNI 调用都使用了 JNIEnv * 类型的指针,习惯上在 CPP 文件中将这个变量定义为 env,它是任意一个本地方法的第一个参数。env 指针指向一个函数指针表,在c++中可以直接用"->"操作符访问其中的函数。
jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction 的一个句柄,相当于 this 指针。
后续的参数就是本地调用中有 Java 程序传进的参数,本例中只有一个 String 型参数。 对于字符串型参数,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C /C++ 字符串或 Unicode。
3、编译生成动态链接库
使用gcc编译时,需要声明jdk include的头文件地址, 通过 -fPIC -c 来生成.o的目标文件
gcc -I/usr/lib/jvm/java/include -I/usr/lib/jvm/java/include/linux -fPIC -c Hello.cpp
生成 libhello.so文件(动态链接库需要lib开头),命令如下:
gcc -shared -o libhello.so Hello.o
设置环境变量,让程序能找到so文件.
export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH
4、编译一个简单的java代码来测试我们实现的本地方法。
public class ToSay {
public static void main(String[] args) {
Hello hello = new Hello();
hello.SayHello("张三");
}
}
使用javac编译ToSay.java,生成ToSay.class
执行 java ToSay后我们会看到屏幕上出现 Hello, 张三