Invoke
jna.library.path
jna.boot.library.path
jna.platform.library.path
System.out.println("动态库so路径: " + soPath);
System.setProperty("jna.noclasspath", "true");
System.setProperty("jna.nounpack", "true");
System.setProperty("jna.library.path", soPath);
System.setProperty("jna.boot.library.path", soPath);
System.setProperty("jna.platform.library.path", soPath);
...
System.out.println("tmpdir java: " + System.getProperty("java.io.tmpdir"));
System.out.println("tmpdir jna : " + System.getProperty("jna.tmpdir"));
System.out.println("JNA Loaded: " + System.getProperty("jna.loaded"));
https://docs.gradle.org/current/userguide/cpp_library_plugin.html
gradle init
不支持交叉编译
https://github.com/gradle/gradle-native/issues/989
webx
可以尝试将 cpp-library, java-library 都发布到 maven
Dynamic Library
https://www.baeldung.com/java-jna-dynamic-libraries
https://github.com/ly3too/java-jna-jni
cmake_minimum_required(VERSION 3.19)
project(h1)
set(CMAKE_CXX_STANDARD 14)
add_library(h1 SHARED library.cpp library.h)
------------ 构建 ------------
mkdir build && cd build
cmake ..
make
header
#ifndef H1_LIBRARY_H
#define H1_LIBRARY_H
#ifdef _WIN32
#define JNA_EXPORT __declspec(dllexport)
#else
#define JNA_EXPORT
#endif
#ifdef __cplusplus
extern "C" {
#endif
JNA_EXPORT int add(int a, int b);
#ifdef __cplusplus
}
#endif
#endif //H1_LIBRARY_H
被 extern "C" 修饰的变量和函数是按照 C 语言方式编译和链接的
__declspec(dllexport)用于Windows中的动态库中,声明导出函数、类、对象等供外面调用,省略给出.def文件。即将函数、类等声明为导出函数,供其它程序调用,作为动态库的对外接口函数、类等。
extern "C"
{
__declspec(dllexport) int add(int a, int b);
}
Dynamic Library (Rust)
https://gist.github.com/CoolOppo/67b452c125bb0db3212a9fbc44c84245
1. 创建lib项目,修改Cargo.toml
[lib]
crate-type = ["cdylib"]
2. 修改lib.rs
#[no_mangle]
pub extern fn add(a:i32, b:i32) -> i32 {
a + b
}
3.构建
cargo build [--release]
cargo clean
Example
https://www.baeldung.com/java-jna-dynamic-libraries
https://github.com/ly3too/java-jna-jni
https://www.eshayne.com/jnaex/index.html
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit-pioneer/junit-pioneer -->
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>1.4.2</version>
<scope>test</scope>
</dependency>
在接口中定义 methods 和 types,由 jna 创建实例,进行映射
-------------- LibC.java --------------
public interface LibC extends Library {
LibC INSTANCE = (LibC)
Native.load((Platform.isWindows() ? "msvcrt" : "c"),
LibC.class);
void printf(String format, Object... args);
}
-------------- LibH.java --------------
public interface LibH extends Library {
LibH INSTANCE = (LibH) Native.load("h1", LibH.class);
int add(int a, int b);
}
测试调用
System.setProperty("jna.library.path", "C:\\jna");
System.setProperty("jna.debug_load", "true");
LibH.INSTANCE.add(1, 2)
junit测试
@SetSystemProperty(key = "jna.debug_load", value = "true")
//@SetSystemProperty(key = "jna.library.path", value = "C:\\jna")
public class AppTest {
@Test
@DisplayName("10+20=30")
public void test() {
Assertions.assertEquals(30, 10 + 20);
}
@Test
@DisplayName("call cosh(0)=1.0")
public void testLibC() {
LibC lib = Native.load(Platform.isWindows() ? "msvcrt" : "c", LibC.class);
Assertions.assertEquals(1.0, lib.cosh(0));
}
}
Java native方法
https://github.com/astonbitecode/j4rs-java-call-rust
-Djna.nosys=true
https://github.com/java-native-access/jna/issues/384
加载顺序
System.setProperty("jna.library.path", "C:\\jna");
System.setProperty("jna.debug_load", "true");
--- 首先加载jnidispatch
com/sun/jna/win32-x86-64/jnidispatch.dll
--- LibH测试:加载h1.dll
寻找jna.library.path:h1.dll
寻找system path:h1.dll
寻找lib- prefix:libh1.dll
寻找classpath:kbase-lib/target/classes/win32-x86-64/h1.dll
--- LibC测试:加载msvcrt.dll
寻找jna.library.path
寻找system path: C:\Windows\System32\msvcrt.dll
总结
- jni 是 java 和 cpp 混合开发,需要编译,代码复杂
- jna 进行了解耦,代码简单
- jni 比 jna 效率高
JNA-so路径
jna.library.path
jna.boot.library.path
jna.platform.library.path
GUI框架:javafx
依赖管理工具:gradle
openjdk11 + kotlin + junit5
Example (Kotlin)
https://www.hicode.club/articles/2020/01/19/1579404425222.html
https://discuss.kotlinlang.org/t/what-is-the-kotlin-equivalent-of-this-jna-code/13697/2
依赖
// https://mvnrepository.com/artifact/net.java.dev.jna/jna
implementation("net.java.dev.jna:jna:5.9.0")
// https://mvnrepository.com/artifact/org.junit-pioneer/junit-pioneer
testImplementation("org.junit-pioneer:junit-pioneer:1.4.2")
interface LibC : Library {
fun cosh(value: Double): Double
}
object DyLib {
val C by lazy {
Native.load("c", LibC::class.java) as LibC
}
}
interface LibC: Library {
companion object {
val INSTANCE by lazy { Native.load("c", LibC::class.java) }
}
// ...
}
// Native.load(if(Platform.isWindows()) "msvcrt" else "c", LibC::class.java) as LibC
声明 Struct
@Structure.FieldOrder("field1", "field2", "field3")
class FooType : Structure() {
@JvmField var field1: Int = 0
@JvmField var field2: Int = 0
@JvmField var field3: String = ""
}
@Structure.FieldOrder("field1", "field2", "field3")
data class FooType(
@JvmField var field1: Int=0,
@JvmField var field2: Int=0,
@JvmField var field3: String=""
) : Structure()
https://kotlinlang.org/docs/native-c-interop.html
HFS实例
// https://mvnrepository.com/artifact/net.java.dev.jna/jna
implementation("net.java.dev.jna:jna:5.9.0")
// https://mvnrepository.com/artifact/org.junit-pioneer/junit-pioneer
testImplementation("org.junit-pioneer:junit-pioneer:1.4.2")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation("org.jetbrains.kotlin:kotlin-reflect:1.5.30")
结构体
class HFS_FILE_INFO: Structure() {
@JvmField var tsDirName = ByteArray(MAX_FILE_ID)
@JvmField var tsTableName = ByteArray(MAX_TABLE_LENGTH)
@JvmField var tsFileName = ByteArray(MAX_FILE_ID)
@JvmField var tsFileType = ByteArray(MAX_FILE_EXT)
@JvmField var uiFileSize = 0L
@JvmField var tsCreateDate = ByteArray(20)
@JvmField var tsModifyDate = ByteArray(20)
@JvmField var MD5 = ByteArray(33)
@JvmField var btFlag = Byte.MAX_VALUE
@JvmField var bIsDir = Byte.MAX_VALUE
@JvmField var NodeID = 0
@JvmField var ulPos = 0L
@JvmField var ulSize = 0L
@JvmField var tsVirtualName = ByteArray(MAX_FILE_ID + MAX_FILE_EXT)
@JvmField var uiFileCount = 0L
@JvmField var uiDirCount = 0L
@JvmField var pData = ByteArray(NET_DATA_SIZE_T)
companion object {
const val MAX_FILE_PATH = 260
const val MAX_FILE_ID = 255
const val MAX_FILE_EXT = 32
const val MAX_TABLE_LENGTH = 256
const val NET_DATA_SIZE_T = 8
}
}
---- 字段顺序1
@FieldOrder(
"tsDirName", "tsTableName", "tsFileName", "tsFileType",
"uiFileSize", "tsCreateDate", "tsModifyDate", "MD5",
"btFlag", "bIsDir", "NodeID", "ulPos", "ulSize",
"tsVirtualName", "uiFileCount", "uiDirCount", "pData",
)
---- 字段顺序2
override fun getFieldOrder(): MutableList<String> {
val props = HFS_FILE_INFO::class.declaredMemberProperties.map { it.name }.toSet()
return HFS_FILE_INFO::class.java.declaredFields.map { it.name }.filter { props.contains(it) }.toMutableList()
}
结构体2
----struct/Constants.kt
const val MAX_FILE_PATH = 260
const val MAX_FILE_ID = 255
const val MAX_FILE_EXT = 32
const val MAX_TABLE_LENGTH = 256
const val NET_DATA_SIZE_T = 8
----struct/HFS_FILE_INFO.kt
class HFS_FILE_INFO: Structure() {
@JvmField var tsDirName = ByteArray(MAX_FILE_ID)
@JvmField var tsTableName = ByteArray(MAX_TABLE_LENGTH)
@JvmField var tsFileName = ByteArray(MAX_FILE_ID)
@JvmField var tsFileType = ByteArray(MAX_FILE_EXT)
@JvmField var uiFileSize = 0L
@JvmField var tsCreateDate = ByteArray(20)
@JvmField var tsModifyDate = ByteArray(20)
@JvmField var MD5 = ByteArray(33)
@JvmField var btFlag = Byte.MAX_VALUE
@JvmField var bIsDir = Byte.MAX_VALUE
@JvmField var NodeID = 0
@JvmField var ulPos = 0L
@JvmField var ulSize = 0L
@JvmField var tsVirtualName = ByteArray(MAX_FILE_ID + MAX_FILE_EXT)
@JvmField var uiFileCount = 0L
@JvmField var uiDirCount = 0L
@JvmField var pData = ByteArray(NET_DATA_SIZE_T)
override fun getFieldOrder(): MutableList<String> =
HFS_FILE_INFO::class.java.declaredFields.map { it.name }.toMutableList()
}
接口
interface LibHFS : Library {
companion object {
val INSTANCE by lazy {
Native.load("hfsclient", LibHFS::class.java) as LibHFS
}
}
fun CheckNetWork(ip: String, port: Int = 8810): Boolean
fun InitApplication(
ip: String,
port: Int = 8810,
appId: Int = 1024,
appName: String = "HFMS",
appKey: String = "f4b871d85cd746021b451487849e3cdf"
): Boolean
fun OpenStream(filename: String, mode: String): Pointer
fun CloseStream(fp: Pointer): Long
fun ReadStream(fp: Pointer, buf: ByteArray, size: NativeLong): Long
fun EofStream(fp: Pointer): NativeLong
fun GetStreamInfo(fp: Pointer, info: HFS_FILE_INFO): Boolean
}
测试下载
LibHFS.INSTANCE.InitApplication(ip)
val fp = LibHFS.INSTANCE.OpenStream(filename, "rb")
val bos = FileOutputStream("demo.caj").buffered()
val buf = ByteArray(1024 * 8)
while (LibHFS.INSTANCE.EofStream(fp).toInt() != -1) {
val size = LibHFS.INSTANCE.ReadStream(fp, buf, NativeLong(1024 * 8))
bos.write(buf, 0, size.toInt())
}
bos.close()
LibHFS.INSTANCE.CloseStream(fp)
测试获取文件信息
LibHFS.INSTANCE.InitApplication(ip)
val fp = LibHFS.INSTANCE.OpenStream(filename, "rb")
val info = HFS_FILE_INFO()
LibHFS.INSTANCE.GetStreamInfo(fp, info)
println(info)
LibHFS.INSTANCE.CloseStream(fp)
ByteArray 转 String 问题
ByteArray(32)大小固定,导致这种结果[99, 97, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
直接使用 String(bytes) 结果length=32,结尾有乱码
应该用 String(bytes, 0, bytes.indexOf(0)) 结果length=3
fun ByteArray.trimString() = String(this, 0, this.indexOf(0))