通过JNI,我们可以让Java调用C/C++的库。C/C++的库是平台相关的。要让依赖JNI动态链接库的Java开发包跨平台,需要把各个平台的库都封装到一个Jar包里。这篇文章分享下如何基于,用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 jnicd jnijavah -cp ..\target\classes -o NativeBarcodeReader.h com.dynamsoft.barcode.NativeBarcodeReader
创建对应的C/C++文件NativeBarcodeReader.cxx:
#include "NativeBarcodeReader.h"#include "DynamsoftBarcodeReader.h" #ifdef __cplusplusextern "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
下载 .
把动态链接库拷贝到对应的目录下即可.
-
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 buildcd buildcmake -G"Visual Studio 15 2017 Win64" .. cmake --build . --config Release --target install
Linux & macOS
mkdir buildcd buildcmake .. cmake --build . --config Release --target install
如何把动态链接库和class文件打包到Jar包中
编辑pom.xml指定动态链接库的路径:
4.0.0 com.dynamsoft barcode 1.0.0 UTF-8 src/main/resources **/*.md **/*.h **/*.lib org.apache.maven.plugins maven-compiler-plugin 2.5.1
用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