1. 目的
本文将描述在Java中如果通过JNA(Java Native Access)技术调用C++动态链接库中的方法,并支持Linux系统以及Windows系统。
2. 技术说明
1)JDK11
2)jna-platform:5.13.0
3)操作系统验证:Windows11、Ubuntu20
4)IDEA:CLion
3. Demo演示
3.1 构建C++动态链接库
3.1.1 创建一个CMakeLists项目
cmake_minimum_required(VERSION 3.22)
cmake_policy(SET CMP0074 NEW)
project(library_shared_demo)
set(CMAKE_CXX_STANDARD 17)
if(UNIX)
message("current platform: Linux")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fms-extensions")
add_definitions(-DisLinux=1)
elseif(CMAKE_CL_64)
message("current platform: Windows x64")
add_definitions(-DisWindows=1)
elseif(WIN32)
message("current platform: Windows x32")
add_definitions(-DisWindows=1)
else()
message("current platform: Unkonw")
endif()
add_library(LibrarySharedDemo SHARED src/library.cpp)
#add_executable(LibrarySharedDemo src/library.cpp src/main.cpp)
3.1.2 创建头文件src/library.h
#ifndef BASLER_PYLON_DEMO_LIBRARY_H
#define BASLER_PYLON_DEMO_LIBRARY_H
#include <iostream>
#include <string.h>
using namespace std;
#define C_API extern "C"
#ifdef isWindows
#define DLL_API __declspec(dllexport)
#else
#define DLL_API ""
#endif
//callback
typedef void (*CustomCallback)(char* param1, int param2);
struct Device {
char* deviceNo;
int exposureTime;
CustomCallback customCallback;
};
struct R {
int code;
char* message;
};
//test
C_API DLL_API void noParamTest();
C_API DLL_API void receiveJavaStringTest(char* val);
C_API DLL_API void returnStringTest(char* returnVal);
C_API DLL_API R* testWithStructParamAndCallback(Device* device);
#endif //BASLER_PYLON_DEMO_LIBRARY_H
3.1.3 创建源文件src/library.cpp
#include "library.h"
C_API DLL_API void noParamTest() {
cout << "[C++]exec method: noParamTest" << std::endl;
}
C_API DLL_API void receiveJavaStringTest(char* val) {
cout << "[C++]exec method: receiveJavaStringTest" << std::endl;
std::string text(val);
cout << "[C++]receive val: " << text.c_str() << std::endl;
}
C_API DLL_API void returnStringTest(char* returnVal) {
cout << "[C++]exec method: returnStringTest" << std::endl;
std::string val("returnValue123");
strcpy(returnVal, val.c_str());
}
C_API DLL_API R* testWithStructParamAndCallback(Device* device) {
cout << "[C++]exec method: testWithStructParamAndCallback" << std::endl;
cout << "[C++]deviceNo: " << device->deviceNo << "; exposureTime: " << device->exposureTime << std::endl;
//exec callback
device->customCallback("abc", 1);
R* r = new R();
r->code = 1;
r->message = "success";
return r;
}
3.1.3 构建
在对接的目录下就可以看到相应的动态链接库文件了,我是在windows平台上编译的,对应的文件为LibrarySharedDemo.dll
。
3.2 创建Maven项目调用
3.2.1 pom
文件配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>library-shared-jna-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>library-shared-jna-demo</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.13.0</version>
</dependency>
<!--<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform-jpms</artifactId>
<version>5.13.0</version>
</dependency>-->
<!--<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.13.0</version>
</dependency>-->
<!--<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-jpms</artifactId>
<version>5.13.0</version>
</dependency>-->
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
3.2.2 类文件
package com.panda;
import com.panda.LibrarySharedSDK.LibrarySharedLibrary.CustomCallback;
import com.panda.LibrarySharedSDK.LibrarySharedLibrary.Device;
import com.panda.LibrarySharedSDK.LibrarySharedLibrary.R;
import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder;
public class LibrarySharedSDK {
public interface LibrarySharedLibrary extends Library {
void noParamTest();
void receiveJavaStringTest(String val);
void returnStringTest(Pointer returnVal);
Pointer testWithStructParamAndCallback(Device device);
@FieldOrder({"deviceNo", "exposureTime", "customCallback"})
public static class Device extends Structure {
public String deviceNo;
public int exposureTime;
public CustomCallback customCallback;
public Device() {
super();
}
public Device(Pointer pointer) {
super(pointer);
useMemory(pointer);
read();
}
}
@FieldOrder({"code", "message"})
public static class R extends Structure {
public int code;
public String message;
public R() {
super();
}
public R(Pointer pointer) {
super(pointer);
useMemory(pointer);
read();
}
}
interface CustomCallback extends Callback {
public void onImageGrabbed(String param1, int param2);
}
}
static class CustomTestCallback implements CustomCallback {
@Override
public void onImageGrabbed(String param1, int param2) {
System.out.println("[java][callback]param1: " + param1);
System.out.println("[java][callback]param2: " + param2);
}
}
public static void test() {
String path = "D:\\CLionWorkspace\\library-shared-demo\\cmake-build-debug\\LibrarySharedDemo.dll";
// String path = "/home/zz/CLionProjects/library-shared-demo/cmake-build-debug/libLibrarySharedDemo.so";
LibrarySharedLibrary INSTANCE = Native.load(path, LibrarySharedLibrary.class);
try {
System.out.println("=====noParamTest=====");
INSTANCE.noParamTest();
System.out.println("");
System.out.println("=====receiveJavaStringTest=====");
INSTANCE.receiveJavaStringTest("abc");
System.out.println("");
System.out.println("=====returnStringTest=====");
Pointer returnVal = new Memory(10);
INSTANCE.returnStringTest(returnVal);
System.out.println("[java]returnCStringTest: " + returnVal.getString(0));
System.out.println("");
System.out.println("=====testWithStructParamAndCallback=====");
CustomCallback callback = new CustomTestCallback();
Device device = new Device();
device.deviceNo = "23448102";
device.exposureTime = 50000;
device.customCallback = callback;
Pointer pointer = INSTANCE.testWithStructParamAndCallback(device);
R r = new R(pointer);
System.out.println("[java]r.code: " + r.code);
System.out.println("[java]r.message: " + r.message);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
test();
}
}
3.2.3 运行java类
4. 其他问题说明
4.1 在Windows和Linux操作系统切换代码文件时报错:该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失
clion下:点击右下角UTF-8,然后点击Add BOM。
4.2 Windows平台下和Linux平台下动态链接库中的方法暴露方式不同
Linux平台下只需要如下配置:
#define C_API extern "C"
C_API void test(char* val) {
//do something
}
Windows平台下需要如下配置:
#define C_API extern "C"
#define DLL_API __declspec(dllexport)
C_API DLL_API void test(char* val) {
//do something
}
4.3 extern "C"
说明
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
4.4 依赖第三方库注意点
我在做打包容器的时候,对于依赖的第三方库,我一开始采取了直接复制的方式,因为我在windows平台和linux平台来回切换,导致第三方库中的一些软链接失效,在容器中一直执行失败,这个记录一下,给大家提个醒。