首页 > 编程语言 >关于如何使用JNI将C语言接口打包成可供java环境调用的so库文件

关于如何使用JNI将C语言接口打包成可供java环境调用的so库文件

时间:2024-05-23 15:30:38浏览次数:28  
标签:java 成可供 int unsigned C语言 char so env

一、环境检查

在linux下打包.so文件,首先需要确认是否有安装java环境,可通过在终端中输入指令java的方式来进行查看。如下图所示,则为已安装java环境。

  

若当前未安装java环境,则可通过在终端中输入如下指令进行安装,我这里使用的java环境为1.8.0版本。

sudo apt-get install openjdk-8-jdk

如果需要安装不同版本则,修改openjdk-8-jdk即可,如:需要安装openJDK11,则使用入下指令:

sudo apt-get install openjdk-11-jdk

安装完成后可输入如下指令查看java版本。

Java -version

我这里查询出来java版本为1.8.0,说明java已安装成功。

二、Java环境变量配置

在进行环境变量配置之前,首先需要找到java的安装目录,通常情况下默认安装路径为:/usr/lib/jvm/

打开文件/etc/profile

sudo vim /etc/profile

添加如下内容:

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64

export PATH=$PATH:$JAVA_HOME/bin 

 添加完成后重启系统完成配置,可在终端中输入如下命令来查询JAVA_HOME是否配置成功。

echo $JAVA_HOME

三、新建.java类

新建一个名为helloword.java的类。

使用System.loadLibrary来导入库,并将需要生成头文件的C语言接口通过public native进行声明。

 四、生成C语言头文件

使用如下指令生成.class文件。

javac helloworld.java

使用如下指令生成.h文件

javah helloworld

打开头文件可以看到生成的C程序接口声明。

 

五、新建.c文件并实现它

新建helloworld.c文件,并实现其内容。

 六、生成.so文件

在终端中输入如下命令

gcc -shared -fPIC -o libhelloworld.so helloworld.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux

 其中${JAVA_HOME}/include和${JAVA_HOME}/include/linux分别为jni.h和jni_md.h所在路径,完成命令输入后即可在文件目录下看到名为libhelloworld.so的库文件。

 七、调用运行

在终端输入指令

java -Djava.library.path=. helloworld

其中” . ”表示的是.so文件所在路径,如果存放在其他位置,则此处也应做出对应修改。完成指令输入后可看到输出结果。

 八、注意事项

8.1 .so文件打包

在实际的工作中,我们所导出的.so库会需要导入到如idea这样的集成IDE中使用,而上述流程中的所打包出来的库在一定情况下可能不能满足实际要求而导致无法调用。

在我的工作中就遇到了这样的一个案例:

开发环境使用的是集成IDE idea,java版本为1.8.0,整个系统的执行代码被存放在名为“com.sunward.nettyTcp.iot”的软件包中,通过JNI打包的.so文件是通过在函数名前添加包名的方式以保证外部能够对函数名进行顺利访问,如下所示

而在使用上述7个步骤进行打包时,我发现无论是通过将文件添加到文件目录“com/sunward/nettyTcp/iot”的方式还是在javah指令中输入指定路径的方式都无法成功实现在.h的函数名前添加包名。后来我发现了一个方法去解决它,即在.java类名前手动添加上我们需要的包名,如下:

 通过指令生成.h文件,打开.h文件后发现了一个问题,即在包名中会存在一个“_1”。

 这个“_1”经过查询后可知是用于替换包名中的“ . ”的,查询的原文描述如下:

 但是这里经过实际测试,带有“_1”的.so库,无法在idea中顺利调用,猜测原因是因为idea的文件管理所导致的,因此在导出成供idea调用的库时,需要将.h文件中的“ _1 ”修改为“ _ ”,并在.c文件中对其函数进行代码实现。

 导出为.so库后,复制到idea工作目录下,并对JVM进行路径配置,方法如下:

完成如上操作后即可顺利调用.so库内容。

8.2 .c文件实现

在进行C代码实现时需要注意java与C之间的数据类型转换,如以下示例:

新建iotSystemDedicatedFunctionForJava.c文件,并将需要进行调用的C语言函数添加至此文件中。

 这里C语言接口FunSea48ByteEncode的函数实现如下:

void FunSea48ByteEncode(unsigned char PlainText[], unsigned char CipherText[])

{

    unsigned int i = 0;

 

    for (i = 0; i < 48; i++)

    {

        CipherText[i] = PlainText[i];

    }

}

将JNIEXPORT void JNICALL Java_iotSystemDedicatedFunctionForJava_FunSea48ByteEncode  (JNIEnv *, jobject, jintArray, jintArray)声明复制到.c文件中进行实现,如下:

JNIEXPORT void JNICALL Java_iotSystemDedicatedFunctionForJava_FunSea48ByteEncode

  (JNIEnv *env, jobject obj, jintArray plainText, jintArray cipherText)

   {

         // 获取数组长度和指针

         jsize plainTextLength = (*env)->GetArrayLength(env, plainText);

         jsize cipherTextLength = (*env)->GetArrayLength(env, cipherText);

 

         // 检查数组长度是否为48

         if (plainTextLength < 48 || cipherTextLength < 48) {

             // 处理错误情况,例如抛出Java异常

             return;

         }

 

         unsigned int *cPlainText = (unsigned int *)(*env)->GetIntArrayElements(env, plainText, NULL);

         unsigned int *cCipherText = (unsigned int *)(*env)->GetIntArrayElements(env, cipherText, NULL);

 

         if (cPlainText == NULL || cCipherText == NULL) {

             // 处理无法获取数组元素的情况

             return;

         }

 

         unsigned char tPlainText[48];

         unsigned char tCipherText[48];

 

         for (int i = 0; i < 48; i++)

         {

             tPlainText[i] = cPlainText[i] & 0xFF;

         }

         // 调用C函数

         FunSea48ByteEncode(tPlainText, tCipherText);

 

         for (int i = 0; i < 48; i++)

         {

             cCipherText[i] = tCipherText[i];

         }

         // 释放资源并更新Java数组

         (*env)->ReleaseIntArrayElements(env, plainText, (jint *)cPlainText, 0);

         (*env)->ReleaseIntArrayElements(env, cipherText, (jint *)cCipherText, 0);

   }

 

这里值得注意的是,C语言接口的参数传参是unsigned char类型,而在java中并没有提供与之相同的数据类型,也并未提供unsigned关键字,因此在java中只能通过大的数据类型来代替unsigned char防止数据溢出,这里使用int来代替unsigned char。

在实际的使用当中发现,在java中将int转换为C语言的char不同于C语言中的隐式转换,在java中如果将int强转成C语言的char,会将int拆分为4个char元素,如下图所示。

因此上述代码如果直接将48大小的int类型数组强转为unsigned char,则会得到一个有192字节的数组。

 

在调用C语言函数之前通过如下操作将int类型的数组转换成unsigned char类型数组,确保转换后每一个int元素对应1个unsigned char,即每一个int丢弃掉前三个字节。

         for (int i = 0; i < 48; i++)

         {

             tPlainText[i] = cPlainText[i] & 0xFF;

         }

并在C语言接口FunSea48ByteEncode(tPlainText, tCipherText);调用完成后,通过如下方式将unsigned char转换回int,即补全unsigned char每个元素前缺少的三个字节。

for (int i = 0; i < 48; i++)

         {

             cCipherText[i] = tCipherText[i];

         }

标签:java,成可供,int,unsigned,C语言,char,so,env
From: https://www.cnblogs.com/jiayezi/p/18208577

相关文章

  • 使用-HTML5-和-JavaScript-开发-Windows-商店应用-全-
    使用HTML5和JavaScript开发Windows商店应用(全)原文:zh.annas-archive.org/md5/8F13EC8AC7BDB8535E7218C5DDB48475译者:飞龙协议:CCBY-NC-SA4.0序言使用HTML5和JavaScript开发WindowsStore应用是一本实践性强的指南,涵盖了WindowsStore应用的基本重要特性以及......
  • 精通-JavaScript-高性能-全-
    精通JavaScript高性能(全)原文:zh.annas-archive.org/md5/582AFDEF15013377BB79AB8CEA3B2B47译者:飞龙协议:CCBY-NC-SA4.0序言欢迎来到精通JavaScript高性能。在这本书中,我们已经以帮助任何JavaScript开发者,无论他们是新手上路还是经验丰富的老手的方式,覆盖了JavaScrip......
  • Java的深浅拷贝认识
    目录浅拷贝深拷贝分辨代码里的深浅拷贝在Java中,深拷贝和浅拷贝是对象复制的两种方式,主要区别在于对对象内部的引用类型的处理上。浅拷贝定义:浅拷贝是指创建一个新的对象,但这个新对象的属性(包括引用类型的属性)仍然指向原来对象的属性。换言之,如果原对象中的属性是一个引用类型......
  • #Java集合的组内平均值的计算方法
    要计算Java集合(例如List或Set中的Integer、Double或其他数值类型的对象)的组内平均值,我们需要遍历这个集合,累加所有的元素值,然后除以集合的大小(即元素的数量)。以下是一个详细的步骤说明和完整的代码示例。1.步骤说明(1)创建集合:首先,我们需要一个包含数值的集合。在这个例子中,我们将......
  • Java静态变量在静态方法内部无法改变值
    一、如何解决“Java静态变量在静态方法内部无法改变值”的问题在Java中,静态变量(也称为类变量)属于类本身,而不是类的任何特定实例。它们可以在没有创建类的实例的情况下访问和修改。如果我们发现在静态方法内部无法改变静态变量的值,这通常是因为我们的代码中有一些逻辑错误或误解。......
  • PureBasic是一种基于BASIC语言的编程语言,它提供了一个简单易用的开发环境,旨在帮助开发
    PureBasic是一种基于BASIC语言的编程语言,它提供了一个简单易用的开发环境,旨在帮助开发人员快速创建跨平台的应用程序。PureBasic的特点如下:简单易学:PureBasic的语法类似于传统的BASIC语言,非常容易学习和理解,适合初学者入门。跨平台支持:PureBasic可以在多个操作系统上运行,包括W......
  • JAVA面试题
    第一章-Java基础篇1、你是怎样理解OOP面向对象难度系数:⭐2、重载与重写区别难度系数:⭐3、接口与抽象类的区别难度系数:⭐4、深拷贝与浅拷贝的理解难度系数:⭐5、sleep和wait区别难度系数:⭐6、什么是自动拆装箱int和Integer有什么区别难度系数:⭐7、......
  • java注解
    一、前言最近在看B站颜群老师的课程,Javaweb>spring>springMVC>mybatis>spring高级,一路走来,跌跌撞撞,发现spring也不过尔尔,说白了,spring就是想尽办法将new做的更简单,更完美,更可配置。Spring的一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean是如何加载到容器的,可以使用Spring......
  • Java如何显示不同字体的文字?
    Java如何显示不同字体的文字?在Java的GUI编程中,如何显示不同字体的文字?以下示例演示如何使用Font类的setFont()方法显示不同字体的文本。packagecom.yiibai;importjava.awt.*;importjava.awt.event.*;importjavax.swing.*;publicclassDisplayTextFontextendsJPane......
  • 浅谈一下C#和java的线程不同点
    C#和Java在线程处理方面有一些显著的区别,这些区别主要体现在线程的创建、管理和生命周期控制上。以下是一些主要的区别:线程的创建和管理Java:Java中线程的创建通常是通过继承Thread类或实现Runnable接口来实现的。Java提供了线程组(ThreadGroup)的概念,允许将线程组织在一起......