通过JNI,我们可以让Java调用C/C++的库。C/C++的库是平台相关的。要让依赖JNI动态链接库的Java开发包跨平台,需要把各个平台的库都封装到一个Jar包里。这篇文章分享下如何基于Dynamsoft Barcode Reader,用CMake为Windows,Linux和macOS快速构建JNI动态链接库,以及如何用Maven把.class,.dll,.dylib,.so文件打包到Jar包中。
创建Java类,生成.h头文件
用Eclipse创建一个Maven工程:
创建NativeBarcodeReader.java
:
public class NativeBarcodeReader {
private long nativePtr = 0;
static {
if (System.getProperty("java.vm.vendor").contains("Android")) {
System.loadLibrary("dbr");
} else {
try {
if (NativeLoader.load()) {
System.out.println("Successfully loaded Dynamsoft Barcode Reader.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public NativeBarcodeReader() {
nativePtr = nativeCreateInstance();
}
public void destroyInstance() {
if (nativePtr != 0)
nativeDestroyInstance(nativePtr);
}
public void setLicense(String license) {
nativeInitLicense(nativePtr, license);
}
public void decodeFile(String fileName) {
nativeDecodeFile(nativePtr, fileName);
}
private native int nativeInitLicense(long nativePtr, String license);
private native long nativeCreateInstance();
private native void nativeDestroyInstance(long nativePtr);
private native void nativeDecodeFile(long nativePtr, String fileName);
}
Eclipse会自动编译.class文件到target目录中。使用javah命令生成头文件:
mkdir jni
cd jni
javah -cp ..\target\classes -o NativeBarcodeReader.h com.dynamsoft.barcode.NativeBarcodeReader
创建对应的C/C++文件NativeBarcodeReader.cxx:
#include "NativeBarcodeReader.h"
#include "DynamsoftBarcodeReader.h"
#ifdef __cplusplus
extern "C"
{
#endif
/*
* Class: com_dynamsoft_barcode_NativeBarcodeReader
* Method: nativeInitLicense
* Signature: (JLjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_dynamsoft_barcode_NativeBarcodeReader_nativeInitLicense(JNIEnv *env, jobject, jlong hBarcode, jstring license)
{
const char *pszLicense = env->GetStringUTFChars(license, NULL);
if (hBarcode)
{
DBR_InitLicense((void *)hBarcode, pszLicense);
}
env->ReleaseStringUTFChars(license, pszLicense);
return 0;
}
/*
* Class: com_dynamsoft_barcode_NativeBarcodeReader
* Method: nativeCreateInstance
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_com_dynamsoft_barcode_NativeBarcodeReader_nativeCreateInstance(JNIEnv *, jobject)
{
return (jlong)DBR_CreateInstance();
}
/*
* Class: com_dynamsoft_barcode_NativeBarcodeReader
* Method: nativeDestroyInstance
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_com_dynamsoft_barcode_NativeBarcodeReader_nativeDestroyInstance(JNIEnv *, jobject, jlong hBarcode)
{
if (hBarcode)
{
DBR_DestroyInstance((void *)hBarcode);
}
}
/*
* Class: com_dynamsoft_barcode_NativeBarcodeReader
* Method: nativeDecodeFile
* Signature: (JLjava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_dynamsoft_barcode_NativeBarcodeReader_nativeDecodeFile(JNIEnv *env, jobject, jlong ptr, jstring fileName)
{
if (ptr)
{
void *hBarcode = (void *)ptr;
const char *pszFileName = env->GetStringUTFChars(fileName, NULL);
DBR_DecodeFile(hBarcode, pszFileName, "");
STextResultArray *paryResult = NULL;
DBR_GetAllTextResults(hBarcode, &paryResult);
int count = paryResult->nResultsCount;
int i = 0;
for (; i < count; i++)
{
printf("Index: %d, Type: %s, Value: %s\n", i, paryResult->ppResults[i]->pszBarcodeFormatString, paryResult->ppResults[i]->pszBarcodeText); // Add results to list
}
// Release memory
DBR_FreeTextResults(&paryResult);
env->ReleaseStringUTFChars(fileName, pszFileName);
}
}
#ifdef __cplusplus
}
#endif
SDK
下载 Dynamsoft Barcode Reader for Windows, Linux and macOS.
把动态链接库拷贝到对应的目录下即可.
-
jni/platforms/win
DBRx64.lib
DynamicPdfx64.dll
DynamsoftBarcodeReaderx64.dll
vcomp110.dll
-
jni/platforms/linux
libDynamicPdf.so
libDynamsoftBarcodeReader.so
-
jni/platforms/macos
libDynamsoftBarcodeReader.dylib
用CMake编译JNI动态链接库
创建CMakeLists.txt. 定义头文件和库的路径:
if (CMAKE_HOST_WIN32)
set(WINDOWS 1)
set(JAVA_INCLUDE "C:/Program Files/Java/jdk1.8.0_181/include")
set(JAVA_INCLUDE_OS "C:/Program Files/Java/jdk1.8.0_181/include/win32")
elseif(CMAKE_HOST_APPLE)
set(MACOS 1)
set(JAVA_INCLUDE "/System/Library/Frameworks/JavaVM.framework/Headers")
set(JAVA_INCLUDE_OS "")
elseif(CMAKE_HOST_UNIX)
set(LINUX 1)
set(JAVA_INCLUDE "/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/")
set(JAVA_INCLUDE_OS "/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux")
endif()
if(WINDOWS)
link_directories("${PROJECT_SOURCE_DIR}/platforms/win" "C:/Program Files/Java/jdk1.8.0_181/lib")
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}" "${JAVA_INCLUDE}" "${JAVA_INCLUDE_OS}")
elseif(LINUX)
link_directories("${PROJECT_SOURCE_DIR}/platforms/linux")
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}" "${JAVA_INCLUDE}" "${JAVA_INCLUDE_OS}")
elseif(MACOS)
link_directories("${PROJECT_SOURCE_DIR}/platforms/macos")
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}" "${JAVA_INCLUDE}")
endif()
指定动态链接库的名字以及依赖的库。针对macOS把dylib重命名成jnilib。
add_library(dbr SHARED NativeBarcodeReader.cxx)
if(MACOS)
set_target_properties(dbr PROPERTIES SUFFIX ".jnilib")
endif()
if(WINDOWS)
if(CMAKE_CL_64)
target_link_libraries (dbr "DBRx64")
else()
target_link_libraries (dbr "DBRx86")
endif()
else()
target_link_libraries (dbr "DynamsoftBarcodeReader")
endif()
把生成的库拷贝到指定目录中。这里针对Eclipse和Maven的需要拷贝了两次。最后生成结果里只有一份拷贝。
set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/../src/main/")
set(ECLIPSE_PATH "java/com/dynamsoft/barcode/native")
set(MAVEN_PATH "resources/com/dynamsoft/barcode/native")
if(WINDOWS)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/win" DESTINATION "${CMAKE_INSTALL_PREFIX}${ECLIPSE_PATH}")
install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/win" DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}")
install (TARGETS dbr DESTINATION "${CMAKE_INSTALL_PREFIX}${ECLIPSE_PATH}/win")
install (TARGETS dbr DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}/win")
elseif(LINUX)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/linux" DESTINATION "${CMAKE_INSTALL_PREFIX}${ECLIPSE_PATH}")
install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/linux" DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}")
install (TARGETS dbr DESTINATION "${CMAKE_INSTALL_PREFIX}${ECLIPSE_PATH}/linux")
install (TARGETS dbr DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}/linux")
elseif(MACOS)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/macos" DESTINATION "${CMAKE_INSTALL_PREFIX}${ECLIPSE_PATH}")
install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/macos" DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}")
install (TARGETS dbr DESTINATION "${CMAKE_INSTALL_PREFIX}${ECLIPSE_PATH}/macos")
install (TARGETS dbr DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}/macos")
endif()
编译JNI动态链接库。
Windows
E.g., Visual Studio 2017
mkdir build
cd build
cmake -G"Visual Studio 15 2017 Win64" ..
cmake --build . --config Release --target install
Linux & macOS
mkdir build
cd build
cmake ..
cmake --build . --config Release --target install
如何把动态链接库和class文件打包到Jar包中
编辑pom.xml指定动态链接库的路径:
<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>com.dynamsoft</groupId>
<artifactId>barcode</artifactId>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.md</exclude>
<exclude>**/*.h</exclude>
<exclude>**/*.lib</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
用Maven打包:
mvn package
也可以用Eclipse导出:
最后生成的Jar包结构:
如何加载Jar包中的动态链接库
动态链接库在Jar包中是不能直接加载的,需要先解压到临时目录中。再通过System.load()
指定绝对路径来加载。
所有JNI依赖的库都要加载:
String[] filenames = null;
if (Utils.isWindows()) {
filenames = new String[] {"vcomp110.dll", "DynamicPdfx64.dll", "DynamsoftBarcodeReaderx64.dll", "dbr.dll"};
}
else if (Utils.isLinux()) {
filenames = new String[] {"libDynamicPdf.so", "libDynamsoftBarcodeReader.so", "libdbr.so"};
}
else if (Utils.isMac()) {
filenames = new String[] {"libDynamsoftBarcodeReader.dylib", "libdbr.jnilib"};
}
boolean ret = true;
for (String file : filenames) {
ret &= extractAndLoadLibraryFile(dbrNativeLibraryPath, file, tempFolder);
}
把库解压到临时目录:
private static boolean extractAndLoadLibraryFile(String libFolderForCurrentOS, String libraryFileName,
String targetFolder) {
String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName;
String extractedLibFileName = libraryFileName;
File extractedLibFile = new File(targetFolder, extractedLibFileName);
try {
if (extractedLibFile.exists()) {
// test md5sum value
String md5sum1 = md5sum(NativeBarcodeReader.class.getResourceAsStream(nativeLibraryFilePath));
String md5sum2 = md5sum(new FileInputStream(extractedLibFile));
if (md5sum1.equals(md5sum2)) {
return loadNativeLibrary(targetFolder, extractedLibFileName);
} else {
// remove old native library file
boolean deletionSucceeded = extractedLibFile.delete();
if (!deletionSucceeded) {
throw new IOException(
"failed to remove existing native library file: " + extractedLibFile.getAbsolutePath());
}
}
}
// Extract file into the current directory
InputStream reader = NativeBarcodeReader.class.getResourceAsStream(nativeLibraryFilePath);
FileOutputStream writer = new FileOutputStream(extractedLibFile);
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = reader.read(buffer)) != -1) {
writer.write(buffer, 0, bytesRead);
}
writer.close();
reader.close();
if (!System.getProperty("os.name").contains("Windows")) {
try {
Runtime.getRuntime().exec(new String[] { "chmod", "755", extractedLibFile.getAbsolutePath() })
.waitFor();
} catch (Throwable e) {
}
}
return loadNativeLibrary(targetFolder, extractedLibFileName);
} catch (IOException e) {
System.err.println(e.getMessage());
return false;
}
}
加载动态链接库:
private static synchronized boolean loadNativeLibrary(String path, String name) {
File libPath = new File(path, name);
if (libPath.exists()) {
try {
System.load(new File(path, name).getAbsolutePath());
return true;
} catch (UnsatisfiedLinkError e) {
System.err.println(e);
return false;
}
} else
return false;
}
运行程序:
java -cp ./target/barcode-1.0.0.jar com.dynamsoft.barcode.Test
源码
https://github.com/dynamsoft-dbr/java-jni-barcode