2019独角兽企业重金招聘Python工程师标准>>>
通过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 blocke.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
}
#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指定动态链接库的路径:
用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 valueString 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 fileboolean deletionSucceeded = extractedLibFile.delete();if (!deletionSucceeded) {throw new IOException("failed to remove existing native library file: " + extractedLibFile.getAbsolutePath());}}}// Extract file into the current directoryInputStream 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;}} elsereturn false;}
运行程序:
java -cp ./target/barcode-1.0.0.jar com.dynamsoft.barcode.Test
源码
https://github.com/dynamsoft-dbr/java-jni-barcode