通过对三个难点的分析,相信大家能够更好的掌握相关的知识点。
互联网上已经有很多介绍 JNI 的入门教程,为什么还要多此一举写本文呢?
相信大家在平时阅读一些教程类文章时都遇到过这样的情况,按照教程描述的步骤一步步的来操作,结果却并没有得到教程期望的结果。遇到各种各样的问题,最后解决不了这些问题,进而放弃,放弃的原因很简单那就是对于一个未知的领域,读者遇到问题后无法自己解决,而所阅读的教程类文章并没有对这一问题进行详细的描述,导致最后选择了放弃。
通过分析我们发现,大多的教程类文章都有一个共同的问题,就是重步骤而忽视读者在阅读过程中可能遇到的问题的分析。
我们期望改变这一现状,对于教程类文章,我们不仅介绍具体的操作步骤,而且更重要的是介绍读者在进行操作时可能遇到的一些关键问题,并对这些问题进行详细分析,从而帮助您彻底的解决问题。
下面将介绍编写 JNI 入门教程HelloNative程序的编写。
主要的步骤为:
1) 编写 HelloNative.java 程序;
2) 编译并得到 HelloNative.h 头文件;
3) 编写 HelloNative.c 程序;
4) 编译动态链接库libHelloNative.jnilib;
5) 运行HelloNative程序。
先从整体上了解一下我们需要做的事情有哪些,接下来我将介绍在mac 系统下每一个步骤的详细内容并标注难点。
public class HelloNative{
static{
System.loadLibrary("HelloNative"); //难点一
}
public static native voidsayHello();
public static void main(String[]args){
new HelloNative().sayHello();
}
}
执行如下命令:
javac HelloNative.java
javah HelloNative
#include
#include"HelloNative.h"
JNIEXPORTvoid JNICALL Java_HelloNative_sayHello(JNIEnv *env, jclass jc) //难点二
{
printf("Hello Native\n");
}
//难点三
gcc HelloNative.c
-o libHelloNative.jnilib
-dynamiclib
-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/
java HelloNative
虽然上面介绍的是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 -oHelloNative.dll
Linux 下:
gcc -shared HelloNative.c -olibHelloNative.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 下的路径,其他系统类似。
本文介绍了 JNI 的入门教程HelloNative程序的编写,相对于其他的教程,我们更加重视大家在实际学习过程中的一些难点,通过对三个难点的分析,相信大家能够更好的掌握相关的知识点。
如您对此种教程类文章感兴趣,欢迎持续关注“算法与编程之美”微信公众号,了解更多此类文章。