史上最详细的JNI入门教程HelloNative

简介: 史上最详细的JNI入门教程HelloNative

 

1 为什么写本文

互联网上已经有很多介绍 JNI 的入门教程,为什么还要多此一举写本文呢?

 

相信大家在平时阅读一些教程类文章时都遇到过这样的情况,按照教程描述的步骤一步步的来操作,结果却并没有得到教程期望的结果。遇到各种各样的问题,最后解决不了这些问题,进而放弃,放弃的原因很简单那就是对于一个未知的领域,读者遇到问题后无法自己解决,而所阅读的教程类文章并没有对这一问题进行详细的描述,导致最后选择了放弃。

 

通过分析我们发现,大多的教程类文章都有一个共同的问题,就是重步骤而忽视读者在阅读过程中可能遇到的问题的分析。

 

我们期望改变这一现状,对于教程类文章,我们不仅介绍具体的操作步骤,而且更重要的是介绍读者在进行操作时可能遇到的一些关键问题,并对这些问题进行详细分析,从而帮助您彻底的解决问题。

 

2 HelloNative教程

下面将介绍编写 JNI 入门教程HelloNative程序的编写。

 

主要的步骤为:

1) 编写 HelloNative.java 程序;

2) 编译并得到 HelloNative.h 头文件;

3) 编写 HelloNative.c 程序;

4) 编译动态链接库libHelloNative.jnilib;

5) 运行HelloNative程序。

 

先从整体上了解一下我们需要做的事情有哪些,接下来我将介绍在mac 系统下每一个步骤的详细内容并标注难点。

 

2.1 编写 HelloNative.java 程序

 

publicclass HelloNative{

static{

System.loadLibrary("HelloNative");  //点一

}

 

public static native voidsayHello();

 

public static void main(String[]args){

new HelloNative().sayHello();

}

}

 

2.2 编译并得到 HelloNative.h 头文件

 

执行如下命令:

javacHelloNative.java

javahHelloNative

 

 

2.3 编写 HelloNative.c 程序

 

#include<stdio.h>

#include"HelloNative.h"

 

JNIEXPORTvoid JNICALL Java_HelloNative_sayHello(JNIEnv *env, jclass jc) //点二

{

printf("Hello Native\n");

}

 

 

2.4 编译动态链接库libHelloNative.jnilib

 

//点三

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/

 

2. 5运行HelloNative程序

 

java HelloNative

 

 

3 难点分析

虽然上面介绍的是mac 系统下程序的编写,读者的系统可能大多是windows,但是不影响大家的编写。

 

接下来将对第2节中标记的三个难点进行分析,这三个难点也是大家遇到的最多的问题。

 

1) 难点一

JNI存在的意义就在于能够让 Java 程序和 C/C++等其他编程语言之间能够非常方便的交互,通过JNI 我们就能够非常方便的做到这一点。

 

有些同学可能会问为什么要这样做?所有的任务我都用 Java 来做不是更好吗,为什么一会用 Java 一会用C/C++,这样多语言岂不是更麻烦吗。

 

这个问题其实有多方面的原因,以下将列举几点原因:

- Java语言是运行在 JVM 之上的,因此对JVM 依赖的非常高。众所周知,这样的机制使得 Java 语言相对其他C 语言来说效率变得低下,因此一些对执行效率要求较高的任务我们可以用 C 语言来编写,然后上面的程序可以通过JNI 来调用 C 编写的模块。

 

- 假设你的项目组是一个多语言的组,存在着 JavaRubyPython 等多种编程语言的人员,如何让这些人员编程的程序能够相关调用呢?

 

那么 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

 

这个知识点非常有用,难点中会再次涉及,请大家务必提前掌握。

 

2) 难点二

 

这个函数的定义大家可能会写错,而且大家通常情况下不能完全按照教程来写。

 

如果大家写过 c/c++ 语言的程序应该都知道,在c/c++中,一个程序分为头文件 hello.h 和其实现文件hello.c,其中在头文件中定义了函数声明,而在实现文件中对函数进行实现。

 

在了解这个知识点以后,我们就知道 HelloNative.c 文件中应该如何突破难点2了。

 

打开 HelloNative.h 文件,找到对应的函数声明。

JNIEXPORT void JNICALL Java_HelloNative_sayHello  (JNIEnv *, jclass);

 

函数声明大家应该都能看懂,本例中大家需要注意的是,在形参的声明中可以不指定形参的名称,而只是给出形参的类型。因此本例中的JNIEnv* jclass都是形参的类型。

 

所以难点2中大家看到的代码是下面这样,其中envjc是具体的形参名称。

JNIEXPORTvoid JNICALL Java_HelloNative_sayHello(JNIEnv *env, jclass jc) //点二

 

3) 难点三

这个难点也是大家遇到的最多问题的地方,需要重点阐述。

 

如何编译一个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

 

在难点1中我们给大家阐述了不同的系统中动态库的名称是不一样的,本节就有所体现。

 

其次,我们还需要给出gcc编译动态库中jni.hjni_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 下的路径,其他系统类似。

 

4 总结

本文介绍了 JNI 的入门教程HelloNative程序的编写,相对于其他的教程,我们更加重视大家在实际学习过程中的一些难点,通过对三个难点的分析,相信大家能够更好的掌握相关的知识点。

目录
相关文章
|
Shell Android开发 C++
利用Wrap Shell Script定位Android Native内存泄漏
## 前提条件 - Android版本为8.0以上 ## 环境配置 - cd到/src/main目录下,新建shell目录,同时shell目录下配置与libs目录下相同平台的目录,如下app下的层级结构,可看到shell/lib下具有与libs下相同的平台目录结构 ```c ── AndroidManifest.xml ├── java ├── libs │   ├── a
3918 0
|
机器学习/深度学习 人工智能 算法
目标检测的国内外研究现状
作为计算机领域的一个重要的研究成果,深度卷积神经网络已经广泛用于图像分类问题。随着图像分类的准确度提高,基于卷积神经网络的图像目标检测算法已逐渐成为当前的研究热点。
6418 0
|
存储 运维 安全
防盗、防泄露、防篡改,我们把 ZooKeeper 的这种认证模式玩明白了
ZooKeeper 作为应用的核心中间件在业务流程中存储着敏感数据,具有关键作用。正确且规范的使用方法对确保数据安全至关重要,否则可能会因操作不当而导致内部数据泄露,进而带来严重的安全风险。因此,在日常的 ZooKeeper 运维和使用过程中,标准化和安全的操作对于加强企业安全防护和能力建设显得格外关键。为了实现这一目标,MSE 提供了一整套标准化流程,帮助用户以更安全、更简便的方式使用 ZooKeeper,从而加速企业安全能力的提升同时最大程度地降低在变更过程中可能出现的风险。
9390 107
|
安全 网络安全 数据安全/隐私保护
政务内网实现https访问教程
政务内网实现HTTPS访问需经过多个步骤:了解HTTPS原理,选择并申请适合的SSL证书,配置SSL证书至服务器,设置端口映射与访问控制,测试验证HTTPS访问功能,注意证书安全性和兼容性,定期备份与恢复。这些措施确保了数据传输的安全性,提升了政务服务的效率与安全性。
|
网络协议 Ubuntu Linux
iPad Pro “买后生产力” - 在iPad上远程连接服务器编程写代码【公网远程】
iPad Pro “买后生产力” - 在iPad上远程连接服务器编程写代码【公网远程】
930 1
|
存储 安全 开发者
C 标准库 - <string.h>详解
`&lt;string.h&gt;` 是 C 标准库中用于处理字符串的头文件,提供了复制、拼接、比较、查找等操作。常用函数包括 `strcpy`、`strncpy`、`strcat`、`strncat`、`strlen`、`strcmp`、`strncmp`、`strchr` 和 `strstr`。此外,还提供了辅助函数如 `memcpy` 和 `memset`。这些函数帮助开发者有效处理字符串,构建更强大的 C 程序。注意事项包括确保目标数组空间足够、正确处理 null 结束符,并使用安全版本函数减少风险。
728 11
|
安全 Java 测试技术
最佳实践:通义灵码生成单元测试,让单测更简单
本文首先讲述了什么是单元测试、单元测试的价值、一个好的单元测试所具备的原则,进而引入如何去编写一个好的单元测试,通义灵码是如何快速生成单元测试的。
|
IDE Unix 编译器
Windows下配置CMake(入门级教程,适合新人收藏学习)
Windows下配置CMake(入门级教程,适合新人收藏学习)
5940 1
|
Java Perl
JVM内存问题之如何统计在JVM的类加载中,每一个类的实例数量,并按照数量降序排列
JVM内存问题之如何统计在JVM的类加载中,每一个类的实例数量,并按照数量降序排列
271 0
|
Java Android开发 C++
JNI中如何实现日志功能
JNI中如何实现日志功能
299 0