关于从JNI到JNA最后到JNR的踩坑之路

当java需要调用dll库时,第一时间想到了JNI,也确实钻进JNI这条崎岖的道路中。

关于JNI:

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。

使用JNI:

使用JNI的准备:

因为使用JNI大概流程是,创建一个类,这个类里有一个native修饰的方法,之后需要根据这个类生成一个头文件,将根据头文件制作一个cpp文件,再对cpp使用gcc进行编译成 .o文件,根据.o文件生成dll文件,这样,就可以加载这个dll库文件,并调用里面的方法了。

生成这个头文件就需要用到MinGW了。https://github.com/cpluspluscom/ChessPlusPlus/wiki/MinGW-Build-Tutorial这里有安装教程。注意:32位jdk就装32位gcc,64就装64,因为不同位系统编译的dll文件,运行会提示32.bit的文件不能再64位平台运行,但据说可以使用m32来修改编译的dll为32位的。

首先,需要创建一个类,然后生成他的class文件。

public class HelloJNI {
    public native void sayHello();

    static {
        System.loadLibrary("hello");
    }

    public static void main(String[] args) {
        new HelloJNI().sayHello();
    }
}

安装好MinGW后,使用命令,javah -jni -classpath (搜寻类目录) -d (输出目录) (类名)

例如:D:\jdk32/bin/javah -jni -classpath D:\IntelliJIDEA2018.1.4\workspace\JNIStudy\target\classes -d ./jni JNIDemo.HelloJNI

我是从target\classe里找编译好的类,并将生成的头文件(.h文件)输出到当前项目的jni目录下,JNIDemo.HelloJNI则生成

JNIDemo_HelloJNI开头的.h文件。

然后就写一个简单的cpp文件:

#include<jni.h>
#include <stdio.h>
#include "JNIDemo_HelloJNI.h"

JNIEXPORT void JNICALL Java_JNIDemo_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
   printf("Hello World!\n");
   return;
}

里面将我们生成的头文件include了进去。之后对这个cpp文件进行编译。

还是使用gcc的命令

gcc -c -I"D:\JAVA\JDK1.8\include" -I"D:\JAVA\JDK1.8\include\win32" jni/HelloJNI.cpp

其中-I"D:\JAVA\JDK1.8\include" -I"D:\JAVA\JDK1.8\include\win32"是为了引入jni.h和stdio.h,不然找不到。

这样就生成了.o结尾的文件,再对这个文件进行编译,就生成了dll文件。

gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o

自己可以命名dll文件名字。然后运行最开始我们写的代码就可以使用了。

是不是很兴奋!!!!

但是!!!这只是能运行我们自己生成的dll文件,没法运行第三方库的dll,JNI命名是这样的Java_JNIDemo_HelloJNI_sayHello,这样我们才能在JNIDemo包下的HelloJNI类中调这个方法。但第三方的方法命名肯定不可能像我们的JNI一样的命名。所以想要使用JNI调用,可以,你在cpp文件中写加载别的dll文件库,然后调用文件库中的方法。但写Cpp就需要一定的c/c+基础了。。。。

所以只能找JNI的升级框架,JNA和JNR。这就方便多了,JNA只要去引入jar包就行,

<dependency>
  <groupId>net.java.dev.jna</groupId>
  <artifactId>jna</artifactId>
  <version>5.2.0</version>
</dependency>

然后一个简单的demo

public class callHello {
    public interface HelloJNA extends Library {
        HelloJNI Instance = (HelloJNI) Native.load("XXX", HelloJNA.class);
        long XXXFUN(NativeLongByReference a, String b, String c , NativeLongByReference d);
    }

    public static void main(String[] args) {
        NativeLongByReference long1=new NativeLongByReference();
        NativeLong nativeLong1=new NativeLong();
        nativeLong1.setValue(1);
        long1.setValue(nativeLong1);

        NativeLongByReference long2=new NativeLongByReference();
        NativeLong nativeLong2=new NativeLong();
        nativeLong2.setValue(1);
        long2.setValue(nativeLong2);

        String b="1";
        String c="5";
        long ret=HelloJNA.Instance.XXXFUN(long1, ComAdr,Baud, long2);

    }
}

下面是参数类型映射对照。但我总觉得用的怪怪的,参数对照试了半天,我决定long类型用上面的NativeLongByReference,

其实也可以用LongByReference来对应long类型。

Native TypeSizeJava TypeCommon Windows Types
char8-bit integerbyteBYTE, TCHAR
short16-bit integershortWORD
wchar_t16/32-bit charactercharTCHAR
int32-bit integerintDWORD
intboolean valuebooleanBOOL
long32/64-bit integerNativeLongLONG
long long64-bit integerlong__int64
float32-bit FPfloat 
double64-bit FPdouble 
char*C stringStringLPTCSTR
void*pointerPointerLPVOID, HANDLE, LPXXX

 

但是当我用上面的JNA时,不知道是什么原因,获取到的long类型的返回值,总是一个随机数,但同事用JNR调用的则没有,于是我改用JNR了。

JNR就更简单了:

先引入jar包,

<dependency>
  <groupId>com.github.jnr</groupId>
  <artifactId>jnr-ffi</artifactId>
  <version>2.1.9</version>
</dependency>
public class JNRTest {
    public static interface JNRDemo {
        long xxxFUN(NativeLongByReference a, String b, String c , NativeLongByReference d);
    }

    public static void main(String[] args) {
        JNRDemo jnrDemo=LibraryLoader.create(JNRDemo.class).load("XXX");
        jnrDemo.xxxFUN(new NativeLongByReference(1),"1","1",new NativeLongByReference(2));
    }
}

使用JNR后,确实调用成功了,获取到了硬件的信息码。不再是JNA获取到的随机数字。

至此,简单的使用终于完成。

本文主要用于记录。

主要参考:https://blog.csdn.net/huachao1001/article/details/53906237

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值