最近接到领导的一项任务,要求把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 )
创建一个名为<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
导入目标的磁盘主文件的完整路径。对于非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库
前提条件:
- Linux环境。如使用VirtualBox搭建Ubuntu虚拟机
- 安装Java环境;
- 安装gcc(如果是c++代码,需要安装g++)
操作步骤:
- 将JNI部分代码和CMakeList.txt一起拷贝到linux环境下。
- 将依赖的第三方so库libalgorithm.so,libcrypto.so拷贝到/usr/lib目录下(libcrypto.so为libalgorithm.so所额外依赖的md5加密库)
- 执行编译命令:
/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)
参考: