JNI是什么意思,jni的使用,详解JNI到底是什么

JNI是什么意思,jni的使用,详解JNI到底是什么

JNI是Java本地接口的缩写。通过使用Java原生接口编写程序,可以保证代码在不同平台上的移植性。从java1.1开始,JNI标准已经成为java平台的一部分,它允许Java代码与用其他语言编写的代码进行交互。

一.序言二java代码编写III。头文件的生成。gcc环境的安装v .动态链接库的生成VI。摘要

目录

首先回顾一下jni的主要功能。从jdk1.1开始,jni标准已经成为java平台的一部分。它提供了一系列允许java与其他语言交互的API,实现了java代码调用其他语言的功能。通过调用jni,可以实现这些功能:

通常我们用jni来调用C或者C代码。在上一篇文章中,我们使用了以下流程来描述native方法的调用过程:

Java代码- JNI - C/C代码

但确切的说,这个过程并不严谨,因为最终执行的并不是原来的c/c代码,而是编译连接的动态链接库。所以我们把这个过程从简单的代码调用层面升级,把jni的调用过程上升到jvm和操作系统层面,增加一些细节来完善它:

看到这里,可能会有朋友提问。他们不是说java语言是跨平台的吗?这种与操作系统本地编译的动态链接库的交互会不会让java失去跨平台的可移植性?

要解决这个问题,可以回忆一下之前安装jdk的经历。官网的下载列表为各种操作系统提供了不同版本的jdk,如windows、linux、mac os等。在这些jdk中,为不同的系统实现了不同的JVM。然而,java语言的跨平台特性与其底层jvm密切相关。只有通过不同操作系统下不同版本jvm的“翻译”,编译出来的字节码才能在不同平台上流畅运行。

在不同的操作系统下,c/c或其他代码生成的动态链接库会有所不同。比如在window平台下会编译成dll文件,linux平台下的so文件,mac os下的jnilib文件。不同平台上的JVM会“常规”加载固定类型的动态链接库文件,这样依赖于操作系统的函数才能正常调用。这个过程可以参考下图来理解:

在对jni的整体调用过程有了一定的了解之后,你会好奇它是如何调用其他语言的函数的吗?我们通过写一个java调用C代码的例子来了解它的调用过程。

一、前言

首先如下定义一个包含原生方法的类,然后我们将使用这个类中的原生方法,通过jni调用C编写的动态链接库中的方法:

公共类JniTest {

静态{

system . loadlibrary( MyNativeDll );

}

公共静态native void callCppMethod();

公共静态void main(String[] args) {

system . out . println( DLL path: system . getproperty( Java . library . path ));

callCppMethod();

}

}

代码中的主要工作如下:

在静态代码块中,调用loadLibrary方法加载本地DLL,参数为不带扩展名的DLL库名。window平台下会加载dll文件,所以linux平台下会加载文件,mac os下会加载jnilib文件。

声明了一个本地方法,native关键字负责通知jvm这里调用的方法是一个本地方法,是外部定义的。

在main方法中,打印加载的dll文件的路径,并调用本地方法。

二、准备java代码

当使用c/c实现本地方法时,需要创建一个。首先是h头文件。简单地说,c/c程序通常由一个头文件(。h)和一个定义文件(。c或者。cpp)。头文件包含函数函数和数据接口的声明,而定义文件用于编写程序的实现。

在jdk8中,可以直接使用javac -h指令生成c/c语言的头文件。如果使用的是早期版本的jdk,需要执行javac编译类文件,然后执行javah -jni生成c/c风格的头文件(jdk10的新特性中已经删除了javah指令)。我们用的jdk8简化了这一步,这样一步就可以完成,命令可以在命令行窗口下执行:

贾瓦茨-h ./JNI JniTest.java

指令使用-h参数指定生成的头文件的位置,最后一个参数是java源文件的名称。在这个过程中,完成两个任务,第一个是形成一个类文件,第二个是在参数指定的目录下生成一个头文件。生成的头文件com_cn_jni_JniTest.h有以下内容:

/*不要编辑此文件-它是机器生成的*/

#包含jni.h

com _ cn _ jni _ JniTest类的头*/

# IFN def _ Included _ com _ cn _ JNI _ JniTest

# define _ Included _ com _ cn _ JNI _ JniTest

#ifdef __cplusplus

外部 C {

#endif

/*

* Class: com_cn_jni_JniTest

*方法:callCppMethod

*签名:()V

*/

JNI export void JNI call Java _ com _ cn _ JNI _ JniTest _ callCppMethod

(JNIEnv *,jclass);

#ifdef __cplusplus

}

#endif

#endif

生成的头文件有点类似于大家熟悉的java接口,只有函数的声明,没有具体的实现。简要解释头文件中的代码:

Extern C 告诉编译器这部分代码是用C语言规则编译的。

JniEXPORT和JNICALL是JNI定义的两个宏。JNIEXPORT用于支持外部程序代码中动态库中的调用方法,JNICALL用于定义调用函数时参数的堆栈和卸载约定。

函数名由包名、类名和方法名组成。这个方法有两个参数。带有第一个参数JNIEnv *的对象可以调用大量封装在jni.h中的函数,第二个参数代表本机方法的调用者。当java代码中定义的原生方法是静态方法时,这里的参数是jclass,非静态方法的参数是jobject。

接下来,我们创建一个cpp文件,引用头文件并实现其中的函数,也就是本机方法将实际执行的逻辑:

#include com_cn_jni_JniTest.h

#包含stdio.h

JNI export void JNI call Java _ com _ cn _ JNI _ JniTest _ callCppMethod

(JNIEnv *,jclass)

{

printf(从Cpp打印: n );

printf(‘我是cpp法! n’);

}

将简单的printf print语句添加到方法的实现中。方法实现后,我们需要将上面的cpp文件编译成动态链接库,提供给java中的原生方法调用。因此,我们需要在下面的窗口环境中安装gcc环境。

三、生成头文件

在windows环境下,如果你不想下载庞大的Visual Studio来生成一个dll,MinGW是一个不错的选择。简单来说就是Windows版本下的一个gcc。那么有的同学又要问了,gcc是什么?简单来说就是linux系统下的C/C编译器,通过它可以将源代码编译成可执行的程序。首先从以下URL下载mingw-get-setup的安装程序:

http://sourceforge.net/projects/mingw/第32位

https://sourceforge.net/projects/mingw-w64/第64位

注意一定要根据系统的位数安装相应的版本,否则后面生成的dll运行时可能会因为位不匹配而报错。我在实验中第一次错误地安装了32位MinGw,导致程序运行时报错如下:

线程“main”Java . lang . unsatisfiedlinkerror中出现异常:

f: workspace 20 unsafe-test src main Java com cn JNI JNI mynativedll . dll:

无法加载IA 32位。AMD 64位平台上的dll

安装完成后,将MinGWbin目录添加到系统环境变量PATH中,并输入以下命令来测试gcc是否可以使用:

海湾合作委员会第五届会议

如果gcc的版本信息可以正常输出,则gcc安装成功:

在测试的过程中发现,如果安装了64位的mingw,安装完成后gcc将直接可用。但是如果安装32位mingw,则需要使用以下命令单独安装gcc:

mingw-get安装gcc

gcc安装后,如果要安装gdb或make等其他指令进行调试或编译,也可以使用功能强大的mingw-get命令进行独立安装。

四、gcc环境安装

当gcc环境准备就绪后,接下来,使用以下命令生成dll动态链接库:

gcc -m64 -Wl,-add-stdcall-alias-I d: Program Files Java JDK 1 . 8 . 0 _ 261 include

-I d: Program Files Java JDK 1 . 8 . 0 _ 261 include win32

-分享-o MyNativeDll.dll jnitestimpl . CPP

简要解释每个参数的含义:

-m64:将cpp代码编译成64位应用程序

-wl,-add-stdcall-alias:-wl表示下面的参数被传递给链接器,参数-add-stdcall-alias表示带有标准调用后缀@NN的符号将被剥离并导出。

-I:指定头文件的路径。生成的头文件代码中引入的jni.h就在这个目录下。

-shared:指定生成动态链接库。如果不使用此标志,外部程序将无法连接。

-o:指定目标的名称,这里生成的动态链接库命名为MyNativeDll.dll。

JniTestImpl.cpp:编译后的源程序的文件名。

在指令执行期间做了什么?请参考下图:

在执行过程中。cpp源代码和。h头文件作为源文件,首先进行预处理、编译和汇编。图中省略了在此阶段生成的一些中间文件。o编译后生成的二进制文件比较重要,它依赖于这个文件,最后生成一个动态链接库。

执行上述指令后,当前目录下会生成一个MyNativeDll.dll文件,然后运行之前准备好的java代码:

程序报告了一个错误,因为在加载库文件的默认目录中找不到我们的dll文件。有两种方法可以解决这个问题:

将dll文件直接复制到默认加载目录。具体路径可以通过系统获取。GetProperty (java.library.path )。这个方法可以获得多个目录,并把它们放在任何目录中。

是修改VM选项中的启动参数并指定dll的存储目录:

-DJ ava . library . path=F: workspace 20 unsafe-test src main Java com cn JNI JNI

再次执行,并输出结果:

DLL路径:F: workspace 20 unsafe-test src main Java com cn JNI JNI

从Cpp打印:

我是cpp法!

可以看到程序加载dll的路径已经切换到它的存储路径,jni调用成功,输出C中的代码逻辑。下图可以用来总结上述实现jni调用的过程:

对jni调用有了整体的了解后,如果熟悉代理模式,也可以从代理模式的角度来理解jni,将jni调用过程中的角色带入代理模式:

代理角色:包含本机方法的jni类

实现角色:用c/c或其他语言实现的动态链接库。

客户端:调用本地方法的java类程序。

接口(抽象角色):在jni中,接口的作用相对较弱。因为jni是跨语言的,所以不可能严格定义一个接口同时应用于java和其他语言。但是,通过生成的。h头文件,某种程度上java中的native方法和其他语言中的函数都是从接口规范统一过来的。

概述代理模式:

在标准代理模式的基础上,做了一些修改,使其更容易理解。因为这里的接口只是作为一个规范约束,客户端的调用过程跳过接口直接指向代理角色,然后代理角色调用实现角色完成函数的调用。一般来说,jni扮演的是代理或中介的角色。与常见的代理不同,它只调用方法,不实现逻辑增强。通过这种模式,对java程序员隐藏了底层c/c代码的实现细节,让我们可以专注于业务代码的编写。

五、生成动态链接库

在了解原生方法的基础上,介绍了jni的相关知识。通过本文的学习,它将帮助我们:

了解java为什么可以跨平台,依赖于操作系统的底层操作是如何实现的。

了解native method的调用过程,必要时可以自己调用jni类接口。

学一点C知识

当然,使用jni也会带来一些弊端:

当在操作系统中使用jni标准时,编译本地代码以生成动态链接库。如果这个程序要移植到其他操作系统上,就需要在新的平台上重新编译代码,生成动态链接库。

其他语言使用不当可能会导致程序出错,比如使用C语言进行内存操作时没有及时回收内存导致的内存泄漏。

过于依赖其他语言会提高java与其他语言的耦合性,也会增加项目代码的维护成本。

以上是对什么是JNI的详细解释。更多关于JNI的信息,请关注我们的其他相关文章!

JNI是什么意思,jni的使用,详解JNI到底是什么