The article is not about how to create Java native methods invoking C/C++ APIs. It aims to help developers to build a jar package containing JNI shared library, made with Dynamsoft Barcode Reader, for Windows, Linux, and macOS from scratch. I will demonstrate how to create JNI shared libraries with CMake, as well as how to package class files and shared libraries into jar files with Eclipse and Maven.
How to Use Maven with Dynamsoft Barcode Reader
Dynamsoft has provided a full Java development package. You can use it with Maven as follows:
<repositories>
<repository>
<id>dbr</id>
<url>https://download.dynamsoft.com/maven/dbr/jar</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.dynamsoft</groupId>
<artifactId>dbr</artifactId>
<version>6.2</version>
</dependency>
</dependencies>
If you are not interested in how to build such a jar package step by step, you can ignore the following paragraphs.
How to Define Native Methods in Java
Create a new Maven project in Eclipse.
![Eclipse maven project]()
Create a Java class 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);
}
Note: If use System.load() method, you have to load all dependencies before loading the JNI shared library.
Eclipse will automatically compile the source code to a class file. Use javah to generates a C header NativeBarcodeReader.h from the class in jni folder.
mkdir jni
cd jni
javah -cp ..\target\classes -o NativeBarcodeReader.h com.dynamsoft.barcode.NativeBarcodeReader
Create the corresponding NativeBarcodeReader.cxx file:
#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
The code here is pretty simple. You can add more implementations if you like.
SDK
Download Dynamsoft Barcode Reader for Windows, Linux and macOS.
Copy OS-dependent shared libraries to jni/platforms
folder.
- jni/platforms/win
DBRx64.lib
DynamicPdfx64.dll
DynamsoftBarcodeReaderx64.dll
vcomp110.dll
- jni/platforms/linux
libDynamicPdf.so
libDynamsoftBarcodeReader.so
- jni/platforms/macos
libDynamsoftBarcodeReader.dylib
How to Use CMake to Build JNI Shared Library
Create a CMakeLists.txt file.
Define Java include and lib directories:
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()
Link Dynamsoft Barcode Reader to generate JNI shared libraries for different platforms. Rename the file suffix from dylib to jnilib for macOS:
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()
Install libraries:
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()
The ECLIPSE_PATH is used to export jar files by Eclipse, whereas the MAVEN_PATH is used to export jar files by Maven.
Build the JNI shared library.
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
How to Package Native Libraries into Jar File
Edit the pom.xml file to add the directory of shared libraries:
<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>
Generate the jar file in the target folder:
mvn package
![Maven JNI shared library]()
Alternatively, you can use Eclipse to export the jar file.
![Eclipse export JNI shared library]()
Here is the generated jar file.
![java jni jar]()
How to Load JNI Shared Libraries from a Jar File
List all dependent library files:
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);
}
Extract the shared libraries to the temporary folder:
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;
}
}
Load libraries with absolute path:
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;
}
Run the test:
java -cp ./target/barcode-1.0.0.jar com.dynamsoft.barcode.Test
References
Source Code
https://github.com/dynamsoft-dbr/java-jni-barcode
The post How to Package JNI Shared Library into Jar File appeared first on Code Pool.