JNA实战笔记汇总(一)—— JNA简介及demo环境创建

目录

1、简介

2、原理

3、配置环境,创建demo

3.1 搞清楚.dll/.so文件适用环境

3.2 创建一个普通的maven项目

3.2.1 将.dll/.so文件放在resources根路径下

3.2.2 pom.xml文件添加jna依赖

3.2.3 编写一个CLibrary接口,继承Library接口

3.2.4 编写一个测试类调用c++函数代码VixHz_InitSDK()

4、调用JNA过程遇到的问题及解决方法


1、简介

提到JNA 就不得不提一下JNI(Java Native Interface),有过不同语言间通信开发经历的一般都知道,它允许java和其他语言代码(尤其是C/C++)进行交互,只要遵守约定即可。首先看下JNA调用C/C++过程,注意写程序时自下而上,调用时自上而下:

JNA实战笔记汇总(一)—— JNA简介及demo环境创建_第1张图片

可见步骤之多,调用.dll/.so共享库之痛苦的过程。

若已有编译好的.dll/.so文件—>需先用是C语言另外写一个.dll/.so共享库,使用SUN规定的数据结构代替C语言的数据结构,调用已有的dll/so中公布的函数—>java中载入这个库—>java编写Native函数作为链接库中函数的代理

问题是很少有java程序员愿意编写调用.dll/.so库中原生函数的java程序,这也使java在客户端上乏善可言,是JNI的一大弱点!

但是JNA不能完全替代JNI,JNI不仅可以实现java访问C,也可实现C调用java。

而JNA只能实现Java访问C函数,作为一个Java框架,自然不能实现C语言调用Java代码。此时,你还是需要使用JNI技术。

那什么是JNA呢?

JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。

JNA框架就是为了解决上述JNI弱点而开发的,它提供一组java工具类用于在运行期间动态访问系统本地共享类库,java开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,而不需要编写任何Native/JNI代码,大大降低了Java调用本体共享库的开发难度。

之所以说它是JNI的替代者,是因为JNA大大简化了调用本地方法的过程,使用很方便,基本上不需要脱离Java环境就可以完成。JNA调用C/C++的过程大致如下:

JNA实战笔记汇总(一)—— JNA简介及demo环境创建_第2张图片

可以看到步骤减少了很多,最重要的是我们不需要重写我们的动态链接库文件,而是有直接调用的API,大大简化了我们的工作量。

2、原理

JNA使用一个小型的JNI库插桩程序来动态调用本地代码。开发者使用java接口描述目标本地库的功能和结构,这使得它很容易利用本机平台的功能,而不会产生多平台配置和生成JNI代码的高开销。这样的性能、准确性和易用性显然受到很大的重视。此外JNA包括一个已与许多本地函数映射的平台库,以及一组简化本地访问的共用接口。

注意:

JNA是建立在JNI技术基础之上的一个java类库,它提供了一个动态的C语言编写的转发器,可以自动实现java和C的数据类型映射,不需要再编写C动态链接库,可方便使用java直接访问动态链接库中的函数,JNA性能上有些微损失。

3、配置环境,创建demo

3.1 搞清楚.dll/.so文件适用环境

示例代码是根据不同的环境调用不同的动态链接库:

public class JnaUtil {

    public static Logger logger = Logger.getLogger(JnaUtil.class);
    static CLibrary INSTANCE;
    public static CLibrary LED_INSTANCE;

    static {
        // 获取jdk位数
        String bits = System.getProperty("sun.arch.data.model");
        // 获取os名称
        String ops = System.getProperty("os.name");
        logger.info("jdk bits=" + bits);
        logger.info("option sysetm=" + ops);
        if (ops.startsWith("win") || ops.startsWith("Win"))//windows
        {
            if ("32".equals(bits)) {
                logger.info("use CCR_SDKx32.dll");
                INSTANCE = (CLibrary) Native.loadLibrary("CCR_SDKx32.dll", CLibrary.class);
            }
            if ("64".equals(bits)) {
                logger.info("use CCR_SDKx64.dll and LEDControl_x64.dll");
                INSTANCE = (CLibrary) Native.loadLibrary("CCR_SDKx64.dll", CLibrary.class);
                LED_INSTANCE = Native.loadLibrary("LEDControl_x64.dll", CLibrary.class);
            }
        } else {
            if ("32".equals(bits)) {
                logger.info("use libCCR_SDKx64-x86_32.so");
                INSTANCE = (CLibrary) Native.loadLibrary("libCCR_SDKx64-x86_32.so", CLibrary.class);
            }
            if ("64".equals(bits)) {
                logger.info("use libCCR_SDKx64-x86_64.so and libLEDControl-x86_64.so");
                INSTANCE = (CLibrary) Native.loadLibrary("libCCR_SDKx64-x86_64.so", CLibrary.class);
                LED_INSTANCE = Native.loadLibrary("libLEDControl-x86_64.so", CLibrary.class);
            }
        }
    }
}

java环境的jdk位数要与.dll/.so位数相同,否则会报类似错误:

 java.lang.UnsatisfiedLinkError: %1 不是有效的 Win32 应用程序。

3.2 创建一个普通的maven项目

3.2.1 将.dll/.so文件放在resources根路径下

(若放在子包中,也是可以加载库文件的,不放心可在Native.loadLibrary()中制定下库文件路径)

JNA实战笔记汇总(一)—— JNA简介及demo环境创建_第3张图片

3.2.2 pom.xml文件添加jna依赖

建议jna包的版本不要太老旧:


    net.java.dev.jna
    jna
    4.5.1

3.2.3 编写一个CLibrary接口,继承Library接口

public interface CLibrary extends Library {
    CLibrary INSTANCE = Native.loadLibrary("iNetSDK.dll", CLibrary.class);

    /**
     * 初始化SDK 注意:调用SDK其他接口前必须先调用此接口!
     *
     * @return TRUE表示成功,FALSE表示失败
     */
    boolean VixHz_InitSDK();
}

3.2.3.1 在上面的代码中,我们定义了一个接口,继承自Library 或StdCallLibrary,默认的是继承Library ,
如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary,比如众所周知的kernel32库。

接口中使用的函数必须与链接库中的函数原型保持一致。

3.2.3.2 接口内部定义

接口内部需要一个公共静态常量:INSTANCE,通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。
该常量通过Native.loadLibrary()这个API函数获得,该函数有2个参数:

  • 第一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀(此处加了后缀,不同系统和环境已经适用了不同的库文件),这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。搜索动态链接库路径的顺序是:当前类当前文件夹,找不到——再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,找不到——到WINDOWS下面去搜索,再找不到——抛异常。

  • 第二个参数是本接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。

3.2.4 编写一个测试类调用c++函数代码VixHz_InitSDK()

@RunWith(SpringRunner.class)
@SpringBootTest
public class DeviceSdkApplicationTests {

    /**
     * 初始化SDK
     */
    @Test
    public void testVixHz_InitSDK() {
        boolean initSDKResult = CLibrary.INSTANCE.VixHz_InitSDK();
        System.out.println("initSDKResult = " + initSDKResult);
    }
}

运行结果,类型简单的JNA调用成功:

4、调用JNA过程遇到的问题及解决方法

4.1 调用JNA报的错java.lang.Error: Invalid memory access:很大可能是JNA调c++接口类型映射的问题;

类型复杂涉及到指针和结构体,接下来总结下JNA实战笔记汇总(二)——JNA和C / C ++的数据类型映射

 

你可能感兴趣的:(JNA,实际场景解决)