最近接到领导的一项任务,要求把C++的录音处理算法包一层JNI,提供Java接口给到客户的Java工程师在Linux服务器上去调用。在初步接到任务时,以为使用Android的NDK将算法源码编译x86_64平台的so库就可以搞定,没想到开搞后事情并没有想象中的顺利。。

       本来以为拿到的会是源码,结果只有一个C++接口头文件,加两个so库(一个是算法具体实现,另一个是算法依赖的加密类so库),拿到算法文件后就蒙了,之前还没搞过用NDK编译so库时依赖链接另一个so库。。在这里对编译实践过程做一个总结,加深印象的同时,希望也能帮助有需要的同学。

一、AS环境下配置Cmake链接第三方动态库进行编译       

#需添加
#设置so库路径,设置my_lib_path为相对CMakeLists.txt路径下的libs目录
set(my_lib_path ${CMAKE_SOURCE_DIR}/libs)

#file (SO_RECURSE DIR_SRCS "${PROJECT_SOURCE_DIR}/jniLibs/libs/x86/*.so")
#将第三方库作为动态库引用,algorithm为算法具体实现so库,全名为libalgorithm.so
add_library( algorithm
             SHARED
             IMPORTED )
#需添加
#指名第三方库的绝对路径属性,ANDROID_ABI为build.gradle中配置要编译的平台
set_target_properties( algorithm
        PROPERTIES IMPORTED_LOCATION
        ${my_lib_path}/${ANDROID_ABI}/libalgorithm.so )

target_link_libraries( # Specifies the target library.
                       athena-call-lib
                       # 链接第三方库libalgorithm.so
                       algorithm

                       # Links the target library to the log library
                       # included in the NDK.
                       # 由于要在Linux下运行,故注释掉链接Android log库
#                       ${log-lib}
        )

 以上参考自:,感谢该博主。

接下来对引入第三方库相关配置做进一步做了解

1. add_library( algorithm   SHARED   IMPORTED )

soem 编译 编译so库_java

创建一个名为<name>的导入库目标。没有生成任何规则来构建它,并且IMPORTED目标属性为True。目标名称在创建它的目录及其下面具有作用域,但是GLOBAL选项扩展了可见性。它可以像项目中构建的任何目标一样被引用。导入的库对于从target_link_libraries()这样的命令中方便地引用是有用的。 (来自:https://cmake.org/cmake/help/latest/command/add_library.html#imported-libraries

2. set_target_properties

( algorithm  PROPERTIES IMPORTED_LOCATION
        ${my_lib_path}/${ANDROID_ABI}/libalgorithm.so )

1)set_target_properties

设置目标的属性。该命令的语法是列出您想要更改的所有目标,然后提供接下来想要设置的值。

来自:https://cmake.org/cmake/help/latest/command/set_target_properties.html

2)IMPORTED_LOCATION

soem 编译 编译so库_ndk_02

 导入目标的磁盘主文件的完整路径。对于非dll平台上的共享库,配置的是共享库的位置。

Cmake的所有配置方法:https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html

经过上面的配置编译后,发现编译出来的so库在PC Linux环境下并不能顺利运行,会出现一些关联引用的第三方so库出现不兼容的情况,比如;

Exception in thread "main" java.lang.UnsatisfiedLinkError: /usr/lib/libalgorithm-call-lib.so: /usr/lib/x86_64-linux-gnu/libdl.so: version `LIBC' not found (required by /usr/lib/libalgorithm-call-lib.so)
	at java.lang.ClassLoader$NativeLibrary.load(Native Method)
	at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1857)
	at java.lang.Runtime.loadLibrary0(Runtime.java:870)
	at java.lang.System.loadLibrary(System.java:1122)
Exception in thread "main" java.lang.UnsatisfiedLinkError: /usr/lib/libalgorithm-call-lib.so: /usr/lib/x86_64-linux-gnu/libm.so: invalid ELF header
	at java.lang.ClassLoader$NativeLibrary.load(Native Method)
	at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1857)
	at java.lang.Runtime.loadLibrary0(Runtime.java:870)
	at java.lang.System.loadLibrary(System.java:1122)

以上问题没有查到解决办法,只能尝试下面的第二种。

二、直接在Linux环境下使用gcc/g++ 编译生成so库

前提条件:

  1. Linux环境。如使用VirtualBox搭建Ubuntu虚拟机
  2. 安装Java环境;
  3. 安装gcc(如果是c++代码,需要安装g++)

操作步骤:

  1. 将JNI部分代码和CMakeList.txt一起拷贝到linux环境下。
  2. 将依赖的第三方so库libalgorithm.so,libcrypto.so拷贝到/usr/lib目录下(libcrypto.so为libalgorithm.so所额外依赖的md5加密库)
  3. 执行编译命令:
/media/sf_Ubuntu-18.04.1/workspace/AlgorithmCallJni/app/src/main/cpp$
sudo g++ -L "/usr/lib" -I "/usr/lib/jvm/java-8-openjdk-amd64/include" -I "/usr/lib/jvm/java-8-openjdk-amd64/include/linux" -o libalgorithm-call-lib.so algorithm-call-lib.cpp -shared -fPIC -lalgorithm -lcrypto -lstdc++

指令相关说明:

  • sudo:由于第三方库是拷贝到/usr/lib目录下,需要有sudo权限才能访问到,否则编译会找不到第三方so库。
  • g++:由于使用.cpp格式编写的JNI部分代码,故使用g++
  • -L "/usr/lib"(重要) : 指明第三方库所在的位置,执行程序时会去该位置找。尽管不配置编译不会报错,但执行时会提示找不到第三方库。。
  • -I xxx -I xxx:增加寻找头文件的目录,主要配置JNI头文件相关目录;(大写的i)
  • -o libalgorithm-call-lib.so:在当前目录生成目标so库;
  • -shared: 调用动态库
  • -fPIC:告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址
  • -lalgorithm -lcrypto: 链接libalgorithm.so和libcrypto.so(-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名)(小写的l)

参考: