How to Package JNI Shared Library into Jar File

This article is not about creating Java native methods to invoke C/C++ APIs. Instead, it aims to help developers build a JAR package containing a JNI shared library, made with Dynamsoft Barcode Reader, for Windows, Linux, and macOS from scratch. In this article, I will demonstrate how to create JNI shared libraries with CMake and package class files and shared libraries into JAR files using Eclipse and Maven.

How to Use Maven with Dynamsoft Barcode Reader

Dynamsoft provides a comprehensive Java development package, which you can use with Maven as follows:

<repositories>
    <repository>
      <id>dbr</id>
      <url>https://download2.dynamsoft.com/maven/dbr/jar</url>
    </repository>
  </repositories>
<dependencies>
    <dependency>
      <groupId>com.dynamsoft</groupId>
      <artifactId>dbr</artifactId>
      <version>9.6.40.1</version>
    </dependency>
  </dependencies>

If you are not interested in building such a JAR package step by step, you can skip the following sections.

How to Define Native Methods in Java

First, create a new Maven project in Eclipse.

Eclipse maven project

Next, create a Java class named 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 you use the System.load() method, ensure all dependencies are loaded before loading the JNI shared library.

Eclipse will automatically compile the source code into a class file. Use javah to generate a C header file NativeBarcodeReader.h from the class in the 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

This code serves as a basic example. You can expand it with more functionalities as needed.

Dynamsoft C++ Barcode SDK

Download the Dynamsoft Barcode Reader for Windows, Linux and macOS.

Copy the OS-dependent shared libraries to the jni/platforms folder.

  • jni/platforms/win
    • DBRx64.lib
    • DynamicPdfx64.dll
    • DynamsoftBarcodeReaderx64.dll
    • vcomp110.dll
    • DynamsoftLicClientx64.dll
    • DynamsoftLicenseClientx64.dll
  • jni/platforms/linux
    • libDynamicPdf.so
    • libDynamsoftBarcodeReader.so
    • libDynamLicenseClient.so
    • libDynamsoftLicenseClient.so
  • jni/platforms/macos
    • libDynamsoftBarcodeReader.dylib
    • libDynamsoftLicenseClient.dylib
    • libDynamicPdf.dylib

How to Use CMake to Build a JNI Shared Library

Create a CMakeLists.txt file.

Define the Java include and library directories:


find_package(JNI REQUIRED)
include_directories(${JNI_INCLUDE_DIRS})

MESSAGE( STATUS "JAVA_INCLUDE: " ${JAVA_INCLUDE})

# Check platforms
if (CMAKE_HOST_WIN32)
    set(WINDOWS 1)
elseif(CMAKE_HOST_APPLE)
    set(MACOS 1)
elseif(CMAKE_HOST_UNIX)
    set(LINUX 1)
endif()

# Set RPATH
if(CMAKE_HOST_UNIX)
    if(CMAKE_HOST_APPLE)
        SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath,@loader_path")
        SET(CMAKE_INSTALL_RPATH "@loader_path")
    else()
        SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath=$ORIGIN")
        SET(CMAKE_INSTALL_RPATH "$ORIGIN")
    endif()
    SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()

if(WINDOWS)
    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        link_directories("${PROJECT_SOURCE_DIR}/platforms/win/bin/" ${JNI_LIBRARIES}) 
    else()
        link_directories("${PROJECT_SOURCE_DIR}/platforms/win/lib/" ${JNI_LIBRARIES}) 
    endif()
elseif(LINUX)
    if (CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
        MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/platforms/linux/" )
        link_directories("${PROJECT_SOURCE_DIR}/platforms/linux/" ${JNI_LIBRARIES})
    elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l OR ARM32_BUILD)
        MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/platforms/arm32/" )
        link_directories("${PROJECT_SOURCE_DIR}/platforms/arm32/" ${JNI_LIBRARIES}) 
    elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64) 
        MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/platforms/aarch64/" )
        link_directories("${PROJECT_SOURCE_DIR}/platforms/aarch64/" ${JNI_LIBRARIES}) 
    endif()
elseif(MACOS)
    MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/platforms/macos/" )
    link_directories("${PROJECT_SOURCE_DIR}/platforms/macos/" ${JNI_LIBRARIES}) 
endif()
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include/")

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_CXX_COMPILER_ID STREQUAL "GNU")
        target_link_libraries (${PROJECT_NAME} "DynamsoftBarcodeReaderx64")
    else()
        if(CMAKE_CL_64)
            target_link_libraries (${PROJECT_NAME} "DBRx64")
        else()
            target_link_libraries (${PROJECT_NAME} "DBRx86")
        endif()
    endif()
else()
    target_link_libraries (${PROJECT_NAME} "DynamsoftBarcodeReader" pthread)
endif()

if(WINDOWS)
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    "${PROJECT_SOURCE_DIR}/platforms/win/bin/"      
    $<TARGET_FILE_DIR:dbr>)
endif()

Install the 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/bin/" DESTINATION "${CMAKE_INSTALL_PREFIX}${ECLIPSE_PATH}/win")
    install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/win/bin/" DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}/win")
    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}/linux")
    install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/linux/" DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}/linux")
    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}/macos")
    install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/macos/" DESTINATION "${CMAKE_INSTALL_PREFIX}${MAVEN_PATH}/macos")
    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

mkdir build
cd build
cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..
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 a JAR File

Edit the pom.xml file to include 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:

private static boolean extractResourceFiles(String dbrNativeLibraryPath, String dbrNativeLibraryName,
			String tempFolder) throws IOException {
    String[] filenames = null;
    if (Utils.isWindows()) {
        filenames = new String[] {"vcomp110.dll", "DynamicPdfx64.dll", "DynamsoftBarcodeReaderx64.dll", "dbr.dll", "DynamsoftLicClientx64.dll", "DynamsoftLicenseClientx64.dll"};
    }
    else if (Utils.isLinux()) {
        filenames = new String[] {"libDynamicPdf.so", "libDynamsoftBarcodeReader.so", "libdbr.so", "libDynamLicenseClient.so", "libDynamsoftLicenseClient.so"};
    }
    else if (Utils.isMac()) {
        filenames = new String[] {"libDynamsoftBarcodeReader.dylib", "libdbr.jnilib", "libDynamsoftLicenseClient.dylib", "libDynamicPdf.dylib"};
    }
    
    boolean ret = true;
    
    for (String file : filenames) {
        ret &= extractAndLoadLibraryFile(dbrNativeLibraryPath, file, tempFolder);
    }
    
    return ret;
}

Extract the shared libraries to a 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 an 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 Test.java <image file>

Source Code

https://github.com/yushulx/java-jni-barcode-qrcode-reader