当前位置: 首页 > news >正文

转载一篇关于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程序的编写。

主要的步骤为:

    1. 编写 HelloNative.java 程序;
    1. 编译并得到 HelloNative.h 头文件;
    1. 编写 HelloNative.c 程序;
    1. 编译动态链接库libHelloNative.jnilib;
    1. 运行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 下的路径,其他系统类似。

相关文章:

  • 竞标项目,何必这样呢?
  • 关于Netty的一点初步认识
  • 骨干跳槽让系统成鸡肋 IT主管如何是好
  • 关于 Unsafe 的一点认识与总结
  • Apache服务器虚拟主机设置技术深入解析
  • 转载文章:在Spring Boot中使用条件化的Bean
  • 《赢在用户》8月19日北京书友会报道!
  • kryo浅析
  • 六招彻底防范ARP病毒反复发作
  • 转载:面试前必须要知道的Redis面试题
  • 如何检测网内IP地址是否被占用
  • 转载:Java并发编程-原子类实现
  • Java 中的四种引用类型
  • 美国国家安全局在监视全球网民?
  • 评论:微软的SOA战略
  • 11111111
  • ES10 特性的完整指南
  • extract-text-webpack-plugin用法
  • JavaScript 基本功--面试宝典
  • KMP算法及优化
  • 阿里云ubuntu14.04 Nginx反向代理Nodejs
  • 从PHP迁移至Golang - 基础篇
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 机器学习 vs. 深度学习
  • 技术:超级实用的电脑小技巧
  • 人脸识别最新开发经验demo
  • 数据结构java版之冒泡排序及优化
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • ###STL(标准模板库)
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (delphi11最新学习资料) Object Pascal 学习笔记---第2章第五节(日期和时间)
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET DataGridView数据绑定说明
  • .NET Framework .NET Core与 .NET 的区别
  • .NET/C# 如何获取当前进程的 CPU 和内存占用?如何获取全局 CPU 和内存占用?
  • .NET6 命令行启动及发布单个Exe文件
  • .NET的微型Web框架 Nancy
  • .NET运行机制
  • .net中调用windows performance记录性能信息
  • /proc/interrupts 和 /proc/stat 查看中断的情况
  • @Repository 注解
  • @SentinelResource详解
  • [ CTF ] WriteUp-2022年春秋杯网络安全联赛-冬季赛
  • [100天算法】-二叉树剪枝(day 48)
  • [Android 13]Input系列--获取触摸窗口
  • [Android Studio 权威教程]断点调试和高级调试
  • [Apio2012]dispatching 左偏树
  • [AX]AX2012开发新特性-禁止表或者表字段
  • [I2C]I2C通信协议详解(一) --- 什么是I2C
  • [js高手之路] dom常用API【appendChild,insertBefore,removeChild,replaceChild,cloneNode】详解与应用...
  • [LeetCode] NO. 387 First Unique Character in a String
  • [Luogu 2816]宋荣子搭积木
  • [NHibernate]一对多关系(关联查询)