当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 Type | Size | Java Type | Common Windows Types |
char | 8-bit integer | byte | BYTE, TCHAR |
short | 16-bit integer | short | WORD |
wchar_t | 16/32-bit character | char | TCHAR |
int | 32-bit integer | int | DWORD |
int | boolean value | boolean | BOOL |
long | 32/64-bit integer | NativeLong | LONG |
long long | 64-bit integer | long | __int64 |
float | 32-bit FP | float | |
double | 64-bit FP | double | |
char* | C string | String | LPTCSTR |
void* | pointer | Pointer | LPVOID, 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