Java使用JNA调用C/C++DLL库
1.0 使用Visual Studio 2019编写并生成动态链接库
1.1 创建新项目
1.2 配置编译平台
根据各自需求选择x64或x86(32位),要与JDK版本匹配才行,不然Java那边编译会报错。
1.3 编写测试代码
新建项目自带framework.h,pch.h,dllmain.cpp,pch.cpp,我这边只编写了pch.h,pch.cpp这两个文件生成测试接口。
IMPORT_DLL这个宏是声明函数输入,输出。函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。__declspec(dllexport)在C调用约定、C编译情况下可以去掉输出函数名的下划线前缀。extern "C"使得在C++中使用C编译方式进行兼容。在"C++"下定义"C"函数,需要加extern "C"关键词。用extern "C"来指明该函数使用C编译方式。输出的"C"函数可以从"C"代码里调用。
这边主要测试函数调用,直接对结构体赋值,获取。
pch.h
#ifndef PCH_H
#define PCH_H
#include "framework.h"
#include <string.h>
#include <stddef.h>
#ifdef IMPORT_DLL
#else
#define IMPORT_DLL extern "C" _declspec(dllimport)
#endif
typedef struct {
int a;
int b;
int c;
} CardStruct;
IMPORT_DLL void add();
IMPORT_DLL void set_struct(CardStruct *cs);
IMPORT_DLL CardStruct getCardStruct();
#endif
pch.cpp
#include "pch.h"
#include <iostream>
CardStruct mCs;
void add()
{
mCs.c = mCs.a + mCs.b;
std::cout << "C++ c: " << mCs.c << "\n";
}
CardStruct getCardStruct()
{
return mCs;
}
void set_struct(CardStruct *cs)
{
mCs.a = cs->a;
mCs.b = cs->b;
mCs.c = cs->c;
}
1.4 生成解决方案DLL提取位置
选择生成->生成解决方案
2.0 Java使用JNA进行DLL库调用
2.1 导入JNA jar包
创建一个Java程序,右键Build Path->Configure Build Path
我这边只导入了jna-4.0.0.jar,jna-platform-4.0.0.jar,版本差异可忽略。自行下载,能用就行。
2.2 导入DLL动态链接库
我这边直接放在项目根目录
2.3 Java端验证代码
public class Test {
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary) Native.loadLibrary("xxxDll", CLibrary.class);
int add();
int test();
public static class ExtU_DEMO_T extends Structure {
public static class ByReference extends ExtU_DEMO_T implements Structure.ByReference {
}
public static class ByValue extends ExtU_DEMO_T implements Structure.ByValue {
}
public int a;
public int b;
public int c;
@Override
protected List getFieldOrder() {
List<String> Field = new ArrayList<String>();
Field.add("a");
Field.add("b");
Field.add("c");
return Field;
}
}
void set_struct(ExtU_DEMO_T.ByReference csbr);
ExtU_DEMO_T.ByValue getCardStruct();
}
public static void main(String[] args) {
Test.CLibrary.ExtU_DEMO_T.ByReference tccb = new Test.CLibrary.ExtU_DEMO_T.ByReference();
tccb.a = 12;
tccb.b = 22;
CLibrary.INSTANCE.set_struct(tccb);// 设值给DLL
CLibrary.INSTANCE.add();// 进行运算
System.out.println("java c: " + tccb.c);
Test.CLibrary.ExtU_DEMO_T.ByValue tcet = CLibrary.INSTANCE.getCardStruct();
System.out.println("getCardStruct a: " + tcet.a);
System.out.println("getCardStruct b: " + tcet.b);
System.out.println("getCardStruct c: " + tcet.c);
}
}
运行结果:
java c: 0
getCardStruct a: 12
getCardStruct b: 22
getCardStruct c: 34
C++ c: 34
2.4 Java端指针方式操作DLL结构体
public static class AAA extends Structure {
public AAA() {
super();
}
public AAA(Pointer p) {
super(p);
}
public static class ByReference extends AAA implements Structure.ByReference {
}
public static class ByValue extends AAA implements Structure.ByValue {
}
public int a;
public int b;
public int c;
@Override
protected List getFieldOrder() {
List<String> Field = new ArrayList<String>();
Field.add("a");
Field.add("b");
Field.add("c");
return Field;
}
}
// 获取指针地址
Pointer cs = lib.getGlobalVariableAddress("CardStruct");
// 传入指针
AAA a1 = new AAA(cs);
a1.read();
// 给结构体直接赋值
a1.a = 1;
a1.write();
a1.b = 2;
a1.write();
a1.c = 3;
a1.write();
2.5 另一种方式执行函数
这种方式就无需在Java端重复声明int add();
NativeLibrary lib = NativeLibrary.getInstance("xxx.dll");
Function initialize = lib.getFunction("add");
initialize.invoke(new Object[] {});
2.6 使用过程中需要注意事项
- JDK版本要与DLL生成环境匹配,Java --version可查看jdk环境。
- loadLibrary路径可调整,并非只能放在工程根目录。
- extends Library后声明函数,变量要与DLL顺序要对上,据说顺序乱会出问题。
- 经实际验证,只有通过指针方式才能直接对DLL立面的结构体进行赋值。不然只能改DLL,声明set函数才能赋值。
3.0 JNI与JNA的差异
编写JNI需要集成NDK环境进行编译,生成函数库的头文件,一般提供so/dll都会提供头文件,生成一个native环境下的头文件才能给Java端进行调用。JNI有许多JNI定义的变量类型,操作相对JNA要复杂很多。JNA(Java Native Access)是一个开源的Java框架,是Sun公司推出的一种调用本地方法的技术,是建立在经典的JNI基础之上的一个框架。之所以说它是JNI的替 代者,是因为JNA大大简化了调用本地方法的过程,使用很方便,基本上不需要脱离Java环境就可以完成。
之前写过的一篇关于JNI的博客(Linux环境下编写的demo):https://blog.csdn.net/u012169524/article/details/50830426