Java调用so文件

朱雀 2020-11-14 17:52 1225阅读 0赞

公司的硬件让我帮忙调用一个so文件,想着一直都没机会自己写一个jni,于是就答应了,在调用的过程中还踩了不少坑,特地写一篇博客记录一下。


一、使用技术

原本是想直接用java自带的jni,但是我们硬件只给了一个so文件,而且里面的函数命名等规则不符合java的jni调用标准,于是就打算使用框架jna来调用。

JNA就是建立在JNI之上,它简化了Java调用原生函数的过程。JNA提供了一个动态的C语言编写的转发器(实际上也是一个动态链接库)可以自动实现Java与C之间的数据类型映射。从性能上会比JNI技术调用动态链接库要低,但开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,大大降低了Java调用本体共享库的开发难度。

二、编码前准备

2.1 首先将so文件放在项目resource文件夹下:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pqcTg1MjUzMzQ0NQ_size_16_color_FFFFFF_t_70

之所以放在该目录下,是为了方便编码过程中能动态获得该文件的路径,其实so文件可以放在任意路径下,只需在加载该so文件的时候,传入的文件的路径给加载器即可。

2.2 引入jna的jar包

有两种引入方式:

一、直接在网上下载jna-3.0.9.jar,然后手动引入jar包

二、在pom.xml中引入[仅限maven项目]

  1. <dependency>
  2. <groupId>com.sun.jna</groupId>
  3. <artifactId>jna</artifactId>
  4. <version>3.0.9</version>
  5. </dependency>

ps: 有一个地方需要注意一下,如果你用的是idea,在jar包引入后可能还是会出现ClassNotFoundException,这个时候可以参考以下步骤:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pqcTg1MjUzMzQ0NQ_size_16_color_FFFFFF_t_70 1

完成上述步骤即可解决该异常


三、开始编码

3.1准备一个类,用于解析so文件

  1. package com.appcups.energy.chargingstation.server.chargingstationsysapi.jna;
  2. import com.sun.jna.Library;
  3. import com.sun.jna.Native;
  4. //继承Library,用于加载库文件
  5. public interface Clibrary extends Library {
  6. // [Native.synchronizedLibrary] 阻止多线程同时访问本地代码
  7. Clibrary INSTANTCE = (Clibrary) Native.synchronizedLibrary(
  8. (Clibrary) Native.loadLibrary(
  9. Clibrary.class.getResource("/secret_udp.so")
  10. .getPath()
  11. .substring(1)// substring(1)的原因是在Windows下获取到的路径前面会多一个斜杠,但在Linux下不会
  12. , Clibrary.class
  13. )
  14. );
  15. // 此方法为so文件中的c语言函数1 -> int test_return_C(void);
  16. // ##备注: 这里的void代表无参
  17. int test_return_C();
  18. // 此方法为so库中的c语言函数2 -> char* Decrpyt( char * input);
  19. // ## 备注: 这里的char* 是c语言中的指针,与java中的String相对应
  20. String Decrpyt(String input);
  21. }

注: 对于so文件中c类型与java类型的映射关系,可以参考下图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pqcTg1MjUzMzQ0NQ_size_16_color_FFFFFF_t_70 2

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pqcTg1MjUzMzQ0NQ_size_16_color_FFFFFF_t_70 3


3.2 编写测试类

  1. public class Demo {
  2. public static void main(String[] args) {
  3. Clibrary instance = Clibrary.INSTANTCE;
  4. // 方法一
  5. int result = instance.test_return_C();
  6. // 方法二
  7. String arr = instance.Decrpyt("方法二参数");
  8. }
  9. }

测试完毕,可以将函数调用返回值打印到控制台,查看调用结果

四、将项目部署到Linux

  1. 我将项目直接部署到Linux是无法正常运行的,需要修改 Clibrary.java 类,因为Linux下使用getResource()方法获取到的路径是正确的,只有在Windows上调用getResource()方法才会多出一条斜杠,所以在Linux下需要将类中的.subString(1)方法的调用删掉,为了让代码兼容WindowsLinux操作系统,我将Clibrary.java 类修改为以下代码:
  2. import com.sun.jna.Library;
  3. import com.sun.jna.Native;
  4. //继承Library,用于加载库文件
  5. public interface Clibrary extends Library {
  6. String os = System.getProperty("os.name"); // 获取当前操作系统的类型
  7. int beginIndex = os != null && os.startsWith("Windows") ? 1 : 0;// windows操作系统为1 否则为0
  8. Clibrary INSTANTCE = (Clibrary) Native.synchronizedLibrary(
  9. (Clibrary) Native.loadLibrary(
  10. Clibrary.class.getResource("/secret_udp.so")
  11. .getPath()
  12. .substring(beginIndex)
  13. , Clibrary.class
  14. )
  15. );
  16. // 此方法为so文件中的c语言函数1 -> int test_return_C(void);
  17. // ##备注: 这里的void代表无参
  18. int test_return_C();
  19. // 此方法为so库中的c语言函数2 -> char* Decrpyt( char * input);
  20. // ## 备注: 这里的char* 是c语言中的指针,与java中的String相对应
  21. String Decrpyt(String input);
  22. }

代码通过获取当前系统的操作类型来改变调用substring的作用域,实现了兼容性。

但在代码运行时,还是报错了:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pqcTg1MjUzMzQ0NQ_size_16_color_FFFFFF_t_70 4

发表评论

表情:
评论列表 (有 0 条评论,1225人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java调用so文件

    一、使用技术 原本是想直接用java自带的jni,但是我们硬件只给了一个so文件,而且里面的函数命名等规则不符合java的jni调用标准,于是就打算使用框架jna来调用。...