如何把Windows,Linux和macOS的动态链接库封装到一个Java Jar包中

通过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

转载于:https://my.oschina.net/yushulx/blog/2209522

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值