c调用其他类的方法_Java 的 Native 方法——今天又进步了

1. 简介

1.1 个人理解

初次遇见 native 是在 java.lang.String 源码中的一个 intern 方法:

public native String intern();

因为还是第一次遇到,所以就去搜了一些文章进行了解。下面就对一些 Native 关键字进行一些总结。

native 也即 JNI —— Java Native Interface(Java 本地接口)。凡是一种语言,都希望是纯的。比如解决某一个方案就单单使用同一个语言来实现。而 Java 却不然,Java 平台有个用户和本地 C 代码进行相互操作的 API,称为 Java Native Interface (Java 本地接口)。也就是说,相当于使用 Java 语言声明了一个方法,而这个方法的具体实现是在其他语言(如 C、C++等)中实现的,所以 Java 中编写的也就类似于一个接口,只是这个接口被称作本地接口。

Java 使用本地接口也是有原因的,因为 Java 的平台无关性,有优势当然也有牺牲,它的缺点就是不能使用 Java 代码直接对一些底层进行操作,但是对底层的操作又是一个语言必不可少的,于是 Java 就想到了间接去操作底层,而中间利用的就是操作系统。所以有些方法,Java 声明为了 native ,具体的实现是在 DLL 中,JVM 去进行真正的操作。

「简单记忆:native 方法是 Java 中声明,由操作系统中具体方法实现。」

1.2 其他介绍

网友见解:

  • native 是与 C++ 联合开发的时候用的!java 自己开发不用的!
  • 使用 native 关键字说明这个方法是「原生函数」,也就是这个方法是用 C/C++ 语言实现的,并且被编译成了 DLL,由 Java去调用。这些函数的实现体在 DLL 中,JDK 的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是 Java 的底层机制,实际上 Java 就是在不同的平台上调用不同的 native 方法实现对操作系统的访问的。
  • native 是用做 java 和其他语言(如c++)进行协作时用的也就是 native 后的函数的实现不是用 Java 写的,既然都不是 Java,那就别管它的源代码了,呵呵。
  • native 的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。所以 native 关键字的函数都是操作系统实现的,Java 只能调用。
  • Java 是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而 Java 要实现对底层的控制,就需要一些其他语言的帮助,这个就是 native 的作用了
  • Java 不是完美的,Java 的不足除了体现在运行速度上要比传统的 C++ 慢许多之外,Java 无法直接访问到操作系统底层(如系统硬件等),为此 Java 使用 native 方法来扩展 Java 程序的功能。

Java 本地方法适用的情况:

  1. 为了使用底层的主机平台的某个特性,而这个特性不能通过 Java API 访问;
  2. 为了访问一个老的系统或者使用一个已有的库,而这个系统或这个库不是用 Java 编写的;
  3. 为了加快程序的性能,而将一段时间敏感的代码作为本地方法实现。

2. 用 Java 调用 C 的实例

为了更好的理解 Java 中调用 Native 方法,特来编写一个具体的小的测试。

以下所有文件都存于个人本地文件夹:C:\Users\Eric\Desktop\NativeTest。

2.1 创建包含本地方法的类

在文件夹下创建一个 HelloNative.java 文件,里面包含着一个 native 的方法和加载库的方法 loadLibrary。代码如下:

public class HelloNative {
    static {
        // 注意加载库的名字为 HelloNative,需要与下文的生成文件保持一致
        System.loadLibrary("HelloNative");
    }
     
    public static native void sayHello();
     
    @SuppressWarnings("static-access")
    public static void main(String[] args) {
        new HelloNative().sayHello();
    }
}

首先注意的是 native 方法,然后那个加载库的静态代码块在后面也起作用。native 关键字告诉编译器(其实是 JVM)调用的是该方法在外部定义,这里指的是 C。

2.2 编译运行

在当前文件夹下使用 CMD 命令行编译 HelloNative.java,如下。

b6dda867c4b615d96a35ba36f551b178.png

如果当前类中没有 Native 方法,那么我们可以直接使用 java 命令直接运行,但是此时大家直接运行这个代码,会出现以下结果:

531256a3d42e42eb64df8417b8f03f77.png

意思是虚拟机说不知道如何找到 sayHello。因为我们定义的 sayHello 方法为 native 类型,所以我们还需要再进行下文的操作步骤。

2.3 获得头文件

在当前文件目录下运行 javah,得到包含该方法的 C 声明头文件 。命令如下:

javah HelloNative       # 生成 .h文件

得到的结果如下:

b25740d743bbbedc7128ef438301899e.png

得到的 HelloNative.h 文件,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* 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

这个头文件中可以看见我们声明的 Java 本地化 sayHello 方法,对应 C 的声明:JNIEXPORT void JNICALL Java_HelloNative_sayHello(JNIEnv *, jclass);,我们只要实现这个方法即可。

注意:头文件中 jni.h 这个文件,是在本地 JDK 目录下的 include 文件夹中,例如我的目录:

08d6b8aa861e3e4ad05159546da7074a.png

2.3 C 实现头文件的声明方法

生成了头文件之后,我们再在当前文件夹下创建一个 HelloNative.c 文件,并简单地实现 HelloNative.h 文件中声明的 sayHello 方法,代码如下:

// 包含刚才生成的.h文件
#include "HelloNative.h"
#include 

JNIEXPORT void JNICALL Java_HelloNative_sayHello(JNIEnv *env, jclass thisClass) {
    printf("Hello, Native!!");
}

结果:

4dc7818adf0f624090af723cae7da3b3.png

2.4 生成动态链接库

到了这一步,我们需要将上述两个文件 HelloNative.cHelloNative.h 编译为动态链接库。

这里说明「两种」方法:分步编译或一次性编译形成动态链接库文件。

「(1)一次性编译」

在 Windows CMD 命令行里,使用如下命令:

gcc -m64  -Wl,--add-stdcall-alias -I"C:/Program Files/Java/jdk1.8.0_181/include" -I"C:/Program Files/Java/jdk1.8.0_181/include/win32" -shared -o HelloNative.dll HelloNative.c

注意:上述 JDK 为个人本地路径,需要根据个人情况进行修改。-m64 表示生成 dll 库是 64 位的,参数 -I 指定头文件路径上述命令运行后,我们会在目录文件夹下生成相应的动态链接库文件,如下:

005a57a4b48e576fe88cdf69b739174f.png

如果使用的 Windows 上面没有 gcc,需要先下载压缩包然后配置一下环境变量即可使用(两分钟就搞定),压缩包及配置步骤,这里推荐一条有关的配置博文:https://blog.csdn.net/jbk3311/article/details/103886095

「(2)分步编译」

为了演示分步编译,我们先把上面一次性编译生成的动态链接库文件 HelloNative.dll 给删除掉。然后再执行如下命令:

$ gcc -c -I"C:/Program Files/Java/jdk1.8.0_181/include" -I"C:/Program Files/Java/jdk1.8.0_181/include/win32" HelloNative.c  HelloNative.h

结果如下:

cb69226b25e56e436f2ad8b12becffe0.png

在这里我们只需要先关注生成的 HelloNative.o 文件,然后我们执行第二步骤的命令,将该文件编译为 dll 文件:

$ gcc -Wl,--add-stdcall-alias -shared -o HelloNative.dll HelloNative.o

结果如下:

a25e5fd1553b1cfe87668c34233200d3.png

2.5 再次运行Java类

使用 2.4 中的任意一种方法生成动态链接库 HelloNative.dll 文件后,我们再次使用 java 命令运行 Java 类,结果如下:

d93e6863928760b7f10991f71821f4b4.png

我们可以看到 Java 类已经可以成功运行了,并且我们也可以看出它运行的实际是我们使用 C 语言编写的实现方法,它作为本地方法 native 来被 Java 代码调用。

2.6 总结

可以将 native 方法比作 Java 程序同C程序的接口,其实现步骤:

  1. 在 Java 中声明 native 方法,然后编译成 .class 文件;
  2. 用 javah 产生一个 .h 文件;
  3. 写一个 .c 或 .cpp 文件实现 native 导出方法,其中需要包含第二步产生的 .h 文件(注意其中又包含了JDK带的jni.h文件);
  4. 将第三步的 .c 或 .cpp 文件编译成动态链接库文件;
  5. 在 Java 中用 System.loadLibrary() 方法加载第四步产生的动态链接库文件,这个 native 方法就可以在 Java 中被访问了。

「JNI 调用 C 流程图」

ba8af97fc75099f046a3cf0097aa348d.png

「参考文章」

  • Java native方法:https://www.jianshu.com/p/1eb6d859175d
  • Java Programming TutorialJava Native Interface (JNI):https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
  • Java的native方法是什么:https://blog.csdn.net/qq_29229567/article/details/80695742?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-4-80695742.nonecase

b7d2c7190279987eed97d22bf7ec6bd8.gif

我是阿数,葛立恒数的数,我们下期再见。

828f3c7a4245964905ea156abe69793a.png

「分享、点赞、在看」f48f06cbb0d5ff47dca39231ad491015.gif

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值