JAVA中调用C语言函数的实现方式

 更新时间:2023年08月02日 09:14:39   作者:猪哥-嵌入式  
这篇文章主要介绍了JAVA中调用C语言函数的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

背景知识

本地代码

在JAVA中使用其他语言的代码(如C/C++)称为本地代码。

历史原因

JAVA的早期阶段,很多人认为使用C和C++来加速JAVA应用中的关键部分是个好主意,但是实际上,虽然JAVA的代码确实没有纯C的运行快,但是JAVA平台实现要更快,也更稳定。

本地代码的应用场景

本地代码,比如C,对于跨平台需求,需要针对支持的平台提供单独的本地类库,而且使用C/C++编写的代码没有对通过使用无效指针所造成的内存腹泻提供任何包含,比如内存回收等,所以会容易破坏程序。

因此,只有在必要的时候才使用本地代码,如下三种场景:

  • 1.应用需要访问的系统特性和设备通过JAVA平台无法实现。
  • 2.已经有了大量的测试过和调试过的用另一种语言编写的代码,比如图像算法,并且知道如何将其导出到所有的目标平台上。
  • 3.通过基准测试,编写的JAVA代码比其他语言编写的等价代码要慢。

JNI

JNI是Java Native Interface的缩写,JNI是JAVA平台专门用于和本地C代码进行相互操作的API,称为JAVA本地接口。

JNI开发流程

  • 1.在JAVA中先声明一个native方法。
  • 2.通过javac -h或javah -jni命令导出JNI使用的C头头文件。
  • 3.使用C实现本地方法。
  • 4.将本地代码变异成动态库,windows下是.dll文件,linux下是.so文件。
  • 5.在JAVA程序中加载步骤4中生成的类库,执行JAVA程序,最终实现JAVA本地代码。

JNI头文件规则

在JAVA中,调用本地代码,需要实现本地代码,需要编写一个相应的C函数,而C函数需要按照JAVA虚拟机的规则来实现,其规则如下:

  • 1.使用完成的JAVA方法名,比如上例中,HelloNative.greeting,如果该类属于某个包,还需要在前面添加包名称,比如com.horstmann.HelloNative.greeting.
  • 2.用下划线’_‘替换掉1中的所有’.’,然后添加上Java_前缀,比如:Java_HelloNative_greeting或Java_com_horstmann_HelloNative_greeting
  • 3.如果类名中含有非ASCII字母或数字,如’_’,’$‘或大于’\u007F’的Unicode字符,用_0xxxx来替代,xxxx是该字符的Unicode值的4个十六进制数序列。

示例

本例中,通过简单的打印功能C函数来举例,在C中使用printf来实现某个打印函数,在JAVA中调用该功能函数。

1. 使用native创建一个本地方法

JAVA中使用关键字native表示本地方法,在JAVA类中声明一个方法,我们先创建一个HelloNative类,

代码如下:

public class HelloNative {
    public static native void greeting();
}

2. 使用javac -h生成头文件

使用 -h 标志运行javac (java 编译工具),提供头文件存放目录,实现对应头文件的生成。

命令代码如下:

javac -h ./ HelloNative.java

运行上述命令后,就会在./ (当前目录)下自动生成一个名为 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:    greeting
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloNative_greeting
  (JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

其中JNIEXPORT和JNICALL为宏定义,在头文件jni.h中定义,jni.h在JDK安装包中已经包含。

他们的作用是为自动装在库的导出函数表明了依赖于编译器的说明符。

3. 编写本地方法的C代码

我们需要根据头文件中本地方法的声明原型,使用C实现,

程序如下:

#include <stdio.h>
#include "HelloNative.h"
JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv *env, jclass cl)
{
    printf("this is hello native .c printf\n");
}

上述代码中,include了步骤2生成的头文件,另外补充了JNIEnv和jclass,默认的参数。

4. 在linux下,编写该C代码的动态库

需要说明的是,由于该C代码引用了jdk中的 jni.h头文件,所以生成动态库时,需要引用JDK的头文件位置,

命令码如下:

gcc -fPIC -I/opt/ctools/jdk1.8.0_301/include/ -I/opt/ctools/jdk1.8.0_301/include/linux/ -shared -o libHelloNative.so HelloNative.c

其中/opt/ctools/jdk1.8.0_301/include/ 和 /opt/ctools/jdk1.8.0_301/include/linux为依赖jdk的头文件目录。

编译生成了 libHelloNative.so 共享库。

5. 在JAVA程序中加载步骤5中生成的类库,执行

在JAVA程序中,通过System.loadLibrary方法调用动态库,我们再编写一个HelloNativeTest测试类,

代码如下:

public class HelloNativeTest {
	public static void main(String[] args)
	{
		HelloNative.greeting();
	}
	static
	{
		System.loadLibrary("HelloNative");
	}
}

使用javac 编译该测试类

javac HelloNativeTest.java

将步骤4中生成的动态库添加到库路径中,命令如下:

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

编译生成HelloNativeTest.class 字节码, 使用java命令执行

java HelloNativeTest

执行命令结果如下:

this is hello native .c printf

至此就实现了在JAVA中调用C本地代码的完整示例。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java三个环境变量配置简单教程

    java三个环境变量配置简单教程

    这篇文章主要为大家详细介绍了java三个环境变量配置简单教程,配置path变量、配置classpath变量、最后是配置JAVA_HOME变量,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • spring mvc @PathVariable绑定URI模板变量值方式

    spring mvc @PathVariable绑定URI模板变量值方式

    这篇文章主要介绍了spring mvc @PathVariable绑定URI模板变量值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • java中transient关键字的作用解析

    java中transient关键字的作用解析

    这篇文章主要介绍了java中transient关键字的作用解析,日常业务中,为了安全起见,有些敏感信息我们不希望在网络间被传输可以使用transient对字段进行修饰,不进行序列化,则返回获取到的字段为null,需要的朋友可以参考下
    2023-11-11
  • Java内部类的实现原理与可能的内存泄漏说明

    Java内部类的实现原理与可能的内存泄漏说明

    这篇文章主要介绍了Java内部类的实现原理与可能的内存泄漏说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 详细图解Java中字符串的初始化

    详细图解Java中字符串的初始化

    字符串广泛应用在Java编程中,在Java中字符串属于对象,Java提供了String类来创建和操作字符串,下面这篇文章主要给大家介绍了Java中字符串初始化的相关资料,需要的朋友可以参考下
    2021-08-08
  • java实现给出分数数组得到对应名次数组的方法

    java实现给出分数数组得到对应名次数组的方法

    这篇文章主要介绍了java实现给出分数数组得到对应名次数组的方法,涉及java针对数组的遍历、排序及运算的相关技巧,需要的朋友可以参考下
    2015-07-07
  • Java常用的一些多媒体文件基本操作方法简介

    Java常用的一些多媒体文件基本操作方法简介

    这篇文章主要介绍了Java常用的一些多媒体文件基本操作方法,包括对音频视频以及幻灯片的播放,需要的朋友可以参考下
    2015-10-10
  • spring boot集成jasypt 并实现自定义加解密的详细步骤

    spring boot集成jasypt 并实现自定义加解密的详细步骤

    由于项目中的配置文件 配置的地方过多,现将配置文件统一放到nacos上集中管理 且密码使用加密的方式放在配置文件中,配置文件的加密使用加密库jasypt,本文给大家介绍spring boot集成jasypt并实现自定义加解密,感兴趣的朋友一起看看吧
    2023-08-08
  • Intellij IDEA如何去掉@Autowired 注入警告的方法

    Intellij IDEA如何去掉@Autowired 注入警告的方法

    这篇文章主要介绍了Intellij IDEA如何去掉@Autowired 注入警告的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • Java中的logback标记日志过滤器MarkerFilter详解

    Java中的logback标记日志过滤器MarkerFilter详解

    这篇文章主要介绍了Java中的logback标记日志过滤器MarkerFilter详解,在logback-classic中存在一个全局过滤器TurboFilter,TurboFilter是与LoggerContext绑定,会在会在其它过滤器之前执行,需要的朋友可以参考下
    2023-11-11

最新评论