转载一篇关于JNI实践的博客---以及编写自己的native方法
编写自己的native方法
- 2 HelloNative教程
- 2.1 编写 HelloNative.java 程序
- 2.2 编译并得到 HelloNative.h 头文件
- 2.3 编写 HelloNative.c 程序
- 2.4 编译动态链接库libHelloNative.jnilib
- 2.5 运行HelloNative程序
- 3 难点分析
转载自史上最详细的JNI入门教程HelloNative
2 HelloNative教程
下面将介绍编写 JNI 入门教程HelloNative程序的编写。
主要的步骤为:
-
- 编写 HelloNative.java 程序;
-
- 编译并得到 HelloNative.h 头文件;
-
- 编写 HelloNative.c 程序;
-
- 编译动态链接库libHelloNative.jnilib;
-
- 运行HelloNative程序。
先从整体上了解一下我们需要做的事情有哪些,接下来我将介绍在mac 系统下每一个步骤的详细内容并标注难点。
2.1 编写 HelloNative.java 程序
public class HelloNative {
static{
System.loadLibrary("HelloNative"); //难点一
}
public static native void sayHello();
public static void main(String[] args){
HelloNative.sayHello();
}
}
2.2 编译并得到 HelloNative.h 头文件
执行如下命令:
javac HelloNative.java
生成HelloNative.class
文件
javah HelloNative
生成HelloNative.h
文件,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloNative */
#ifndef _Included_HelloNative
#define _Included_HelloNative
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloNative
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloNative_sayHello
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
注意:jdk版本需要小于等于jdk9,因为jdk10及以上没有javah
指令;
2.3 编写 HelloNative.c 程序
#include <stdio.h>
#include "HelloNative.h"
JNIEXPORT void JNICALL Java_HelloNative_sayHello(JNIEnv *env, jclass jc) //难点二
{
printf("Hello Native 和 世界 JNI \n");
}
2.4 编译动态链接库libHelloNative.jnilib
gcc HelloNative.c -o libHelloNative.jnilib -dynamiclib -I/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home/include/ -I/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home/include/darwin/
注意点:
- 需要安装
gcc
-I
与后面的include
目录之间没有空格- 此处的jdk版本为
jdk1.8.0_202.jdk
,请换成你本地的版本及路径 - 查看
/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home/include/
目录下的文件会发现里面都是一些*.h
文件
-rw-r--r-- 1 root wheel 20K 12 16 12:37 classfile_constants.h
drwxr-xr-x 4 root wheel 128B 12 16 12:37 darwin
-rw-r--r-- 1 root wheel 8.5K 12 16 12:37 jawt.h
-rw-r--r-- 1 root wheel 6.3K 12 16 12:37 jdwpTransport.h
-rw-r--r-- 1 root wheel 72K 12 16 12:37 jni.h
-rw-r--r-- 1 root wheel 76K 12 16 12:37 jvmti.h
-rw-r--r-- 1 root wheel 3.7K 12 16 12:37 jvmticmlr.h
2.5 运行HelloNative程序
java HelloNative
结果:
➜ java HelloNative
Hello Native 和 世界 JNI
注意:
- 以上执行的
javac
javah
java
指令保存在同一java版本环境下;
3 难点分析
虽然上面介绍的是mac 系统下程序的编写,读者的系统可能大多是windows,但是不影响大家的编写。
接下来将对第2节中标记的三个难点进行分析,这三个难点也是大家遇到的最多的问题。
难点一
JNI存在的意义就在于能够让 Java 程序和 C/C++等其他编程语言之间能够非常方便的交互,通过JNI 我们就能够非常方便的做到这一点。
有些同学可能会问为什么要这样做?所有的任务我都用 Java 来做不是更好吗,为什么一会用 Java 一会用C/C++,这样多语言岂不是更麻烦吗。
这个问题其实有多方面的原因,以下将列举几点原因:
- Java语言是运行在 JVM 之上的,因此对JVM 依赖的非常高。众所周知,这样的机制使得 Java 语言相对其他C 语言来说效率变得低下,因此一些对执行效率要求较高的任务我们可以用 C 语言来编写,然后上面的程序可以通过JNI 来调用 C 编写的模块。
- 假设你的项目组是一个多语言的组,存在着 Java、Ruby、Python 等多种编程语言的人员,如何让这些人员编程的程序能够相关调用呢?
那么 Java 是如何做到这一点的呢?
将其他语言编写的模块编译成动态库,然后在Java程序中加载这个动态库,进而使用该库。
System.loadLibrary("HelloNative"); //难点一
因此大家看到的这行代码就是 Java 程序加载编译后的动态库HelloNative。这里面大家需要注意的是HelloNative 并不是最终动态库的全称,不同的操作系统下这个动态库的名称是不一样的,如:
Windows 下叫*.dll
Linux 下叫*.so
Mac 下叫*.jnilib
大家都知道 Java 是跨平台的程序,因此在Java 代码里面肯定不能指定某一种平台具体的动态库完整名称,因此只是给出了这个动态库的名称而非全称。
对于本例我们最终在不同平台下生成的动态库全称是:
Windows HelloNative.dll
Linux libHelloNative.so
Mac libHelloNative.jnilib
这个知识点非常有用,难点3中会再次涉及,请大家务必提前掌握。
难点二
这个函数的定义大家可能会写错,而且大家通常情况下不能完全按照教程来写。
如果大家写过 c/c++ 语言的程序应该都知道,在c/c++中,一个程序分为头文件 hello.h 和其实现文件hello.c,其中在头文件中定义了函数声明,而在实现文件中对函数进行实现。
在了解这个知识点以后,我们就知道 HelloNative.c 文件中应该如何突破难点2了。
打开 HelloNative.h 文件,找到对应的函数声明。
JNIEXPORT void JNICALL Java_HelloNative_sayHello (JNIEnv *, jclass);
函数声明大家应该都能看懂,本例中大家需要注意的是,在形参的声明中可以不指定形参的名称,而只是给出形参的类型。因此本例中的JNIEnv* 和jclass都是形参的类型。
所以难点二中大家看到的代码是下面这样,其中env和jc是具体的形参名称。
JNIEXPORTvoid JNICALL Java_HelloNative_sayHello(JNIEnv *env, jclass jc) //难点二
难点三
这个难点也是大家遇到的最多问题的地方,需要重点阐述。
如何编译一个C 语言的动态库?
大家都知道 C 语言一个非常著名的编译器叫gcc,因此本文介绍如何用 gcc 来编译动态库。(这里面大家需要注意的是 gcc 只是c语言编译器其中的一种,除了 gcc 还有很多其他的编译器如微软等公司出品的。)
不同的操作系统下编译动态库的命令也是不一样的,如:
Windows下:
gcc -shared HelloNative.c -o HelloNative.dll
Linux 下:
gcc -shared HelloNative.c -o libHelloNative.so
Mac 下:
gcc -dynamiclib HelloNative.c -o libHelloNative.jnilib
在难点一中我们给大家阐述了不同的系统中动态库的名称是不一样的,本节就有所体现。
其次,我们还需要给出gcc编译动态库中jni.h和jni_md.h两个头文件的路径。
因此,我们接下来要找到这两个头文件所在的目录,你可以通过各种各样的文件搜索工具(如 windows 下的强大的搜索神器everything)找到他们。在 Linux 和Mac 下面我们可以通过下面的命令快速找到:
locate jni.h
>/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/include/jni.h
locate jni_md.h
>/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/include/darwin/jni_md.h
注意,我们只需要两个文件所在的目录:
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/include/
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/include/darwin/
准备工作做好了,最后我们加上这个头文件的路径就大功告成了。
Mac 下:
gcc -dynamiclib HelloNative.c -o libHelloNative.jnilib
-I/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/include/
-I/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/include/darwin/
注意:
-I中的I为大写;
-I/Library中的I与/Library之间没有空格。
此处我们只给出 mac 下的路径,其他系统类似。