在Android平台使用SNPE库以前都没什么太大问题,最近因为要使用userBuffer,编译时报链接错误:

undefined reference to `zdl::DlSystem::TensorShape::TensorShape(std::vector<unsigned long, std::allocator<unsigned long> >)

因为是链接错误,而且查看了头文件,TensorShape有对应的重载构造函数,所以怀疑SNPE的库文件libSNPE.so不对。怀疑高通提供的库文件不对,好像这也不太可能,一时间陷入了困境。

开始从高通的客服那边没有得到太有用的信息,只能自己想办法。用objdump从库文件dump出符号表看看有没有对应的函数。但是高通release库肯定是把符号表去掉了的,只能通过objdump -T出来动态符号。从动态符号中筛选出和TensorShape相关的,然后找一些看着可能像的符号,用c++filt还原出函数名。

先在x86_64平台的libSNPE.so找到了

c++filt _ZN3zdl8DlSystem11TensorShapeC1ESt6vectorImSaImEE
zdl::DlSystem::TensorShape::TensorShape(std::vector<unsigned long, std::allocator<unsigned long> >)

这和我们需要的函数是一样的,也就是说x86_64平台的libSNPE.so肯定是包含这个重载的函数实现的。那android平台的不应该没有啊?继续在android平台的libSNPE.so里找,终于找到

c++filt _ZN3zdl8DlSystem11TensorShapeC2ENSt6__ndk16vectorImNS2_9allocatorImEEEE
zdl::DlSystem::TensorShape::TensorShape(std::__ndk1::vector<unsigned long, std::__ndk1::allocator<unsigned long> >)

看着像,但是和平常见到的是不太一样,vector的定义是std::__ndk1::vector。这说明SNPE链接的是LLVM的标准libc++库。这样原因清楚了。我们编译使用的是cmake,最开始在build.gradle文件中并没有指定ANDROID_STL变量,编译使用的应该不是libc++库,导致了链接错误。(按照NDK文档的说法,ANDROID_STL默认的是c++_static,这是libc++的静态库。按理是能够链接通过的,实际也试过了,ANDROID_STL指定c++_static是能够编译通过的。但是不指定,就会出现上面的undefined reference错误。我怀疑默认使用的是GNU的库)

因为还使用了opencv的库(用的是opencv3的版本),如果改用libc++,会出现链接opencv出错。查找opencv的文档,opencv4.0.0开始使用c++11库,因此改用opencv4。在build.gradle文件中加上ANDROID_STL定义,编译通过。

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments '-DANDROID_STL=c++_shared'
            }
        }
    }
}

按NDK的文档,从NDK r18开始,libc++成为NDK中唯一可用的STL。因为我们使用的NDK版本还比较低,所以开始并没有出错。随着NDK升级,不仅r18开始libc++是唯一的STL,而且gcc也不再支持,只能使用clang。还是慢慢转过去了,以免出现一些莫名其妙的问题。