1. Android NDK
NDK开发过程中常用的库定义在android-ndk-r25c/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android
如libc++_shared.so
libc++_static.a
libstdc++.a
库
ndk工具链下载:./bin/sdkmanager --install "ndk;25.0.8775105"
2. 链接问题
问题背景
给定第三方依赖库头文件和实现库,现在需要集成到自己的工程中。
第三方库ThirdLib.h
如下:
#ifndef _THIRD_LIB_H_
#define _THIRD_LIB_H_
#include <string>
#include <unistd.h>
std::string makeRandomString(std::string prefix);
#endif
实现库libthirdparty.so
给外部调用的符号表如下:
$ nm -D -g -C --defined-only libthirdparty.so
0000000000001014 T makeRandomString(std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >)
集成代码如下:
// main.cpp
#include <iostream>
#include <ThirdLib.h>
using namespace std;
int main(int argc, char const *argv[])
{
std::string result_cstring = makeRandomString(std::string("Hello"));
return 0;
}
对应的Android.bp
cc_binary {
name: "client",
srcs: [
"main.cpp",
],
shared_libs: [
"libthirdparty",
],
cflags: [
"-O2",
"-Wno-unused-parameter",
],
}
编译时会遇到如下错误:
ld.lld: error: undefined symbol: makeRandomString(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)
>>> referenced by main.cpp:7
问题分析
在编译main.cpp时出现符号找不到,需要链接的符号如下:
makeRandomString(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)
函数名为makeRandomString
,正是libthirdparty.so
提供的符号,但是libthirdparty.so
提供的符号为:
makeRandomString(std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >)
可以发现符号确实不一样,一个是std::__1
前缀,另一个是std::__ndk1
前缀。
第三方库的实现库没法改变,那么只能在集成端生成std::__ndk1
前缀的库进行调用,根据提示,std::__1
符号找不到,是否意味着本地生成的默认就是std::__1
前缀的符号呢?编写如下代码进行分析:
// Normal.h
#ifndef _NORMAL_H_
#define _NORMAL_H_
#include <string>
#include <unistd.h>
std::string makeNormalRandomString(std::string prefix);
#endif
// Normal.cpp
#include "Normal.h"
std::string makeNormalRandomString(std::string prefix) {
int uid = getuid();
int pid = getpid();
int tid = gettid();
int max = 256;
char buffer[max];
snprintf(buffer, max, "%s: normal! uid %d, pid %d, tid %d", prefix.c_str(), uid, pid, tid);
return std::string(buffer);
}
// Android.bp
cc_library_shared {
name: "libnormal",
srcs: [
"Normal.cpp",
],
export_include_dirs: [
".",
],
cflags: [
"-O2",
"-Wno-unused-parameter",
],
}
查看生成的符号表:
$ nm -D -g -C --defined-only ./libnormal.so
0000000000001014 T makeNormalRandomString(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)
本地生成的符号确实就是std::__1
前缀,接下来可以借助IDE的力量查看根据什么条件决定这个前缀,前提是需要使用vscode + clangd配置好代码跳转环境,可以参考我的另一篇文章:Android配置C++开发环境 根据提示,我们输入以下符号:
std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > temp;
点击__1
进行跳转,以下代码使用的是Android12代码环境:
// external/libcxx/include/string 533行
_LIBCPP_BEGIN_NAMESPACE_STD
// 继续跳转
// external/libcxx/include/__config 802行
#define _LIBCPP_BEGIN_NAMESPACE_STD namespace std { inline namespace _LIBCPP_ABI_NAMESPACE {
// 继续跳转 _LIBCPP_ABI_NAMESPACE
// external/libcxx/include/__config 124行
#ifndef _LIBCPP_ABI_NAMESPACE
# define _LIBCPP_ABI_NAMESPACE _LIBCPP_CONCAT(__,_LIBCPP_ABI_VERSION)
#endif
// 继续跳转 _LIBCPP_ABI_VERSION
// external/libcxx/include/__config 38行
#ifndef _LIBCPP_ABI_VERSION
# define _LIBCPP_ABI_VERSION 1
#endif
跟踪代码发现,std::__1
其中的1
是通过_LIBCPP_ABI_VERSION
宏定义的,默认值就是1
,那么只要提前将_LIBCPP_ABI_VERSION
定义为ndk1
就可以按照std::__ndk1
进行编译。
但是需要找到提供这种符号实现的库,根据ndk字眼猜测是NDK工具链中提供的,在android_sdk/ndk/25.0.8775105/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib
目录下使用android_find_symbols.sh
搜索std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >
,具体搜索可以参考我的另一篇文章:Android定位需要引用的动态库 得到如下结果:
Summary, you should add these libs:
libc++_shared
接下来我们就可以写一个中间层,向下对接第三方库,提供std::__ndk1
符号;向上对接本地代码,因为中间层代码实现时不可能同时提供std::__1
和std::__ndk1
符号,所以向上对接时需要替换为char*
。
编写中间层代码,用于实现char*
和std::__ndk1
之间的转换:
// NDKHelper.h
#ifndef _NDK_HELPER_H_
#define _NDK_HELPER_H_
#include <string>
std::string makeNDKString(const char* str);
const char* makeCString(const std::string& ndkString);
#endif
// NDKHelper.cpp
#define _LIBCPP_ABI_VERSION ndk1
#include "NDKHelper.h"
#include <string.h>
#include <string>
std::string makeNDKString(const char* str) {
return std::string(str);
}
const char* makeCString(const std::string& ndkString) {
char* cString = new char[ndkString.length() + 1];
memset(cString, 0, ndkString.length() + 1);
strcpy(cString, ndkString.c_str());
return cString;
}
// Android.bp
cc_library_shared {
name: "libNDKHelper",
srcs: [
"NDKHelper.cpp",
],
export_include_dirs: [
".",
],
shared_libs: [
"libc++_shared",
],
cflags: [
"-O2",
"-Wno-unused-parameter",
],
}
// nm -D -g -C --defined-only ./libNDKHelper.so
// 00000000000010a8 T makeCString(std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> > const&)
// 0000000000001014 T makeNDKString(char const*)
最后需要提供一个包装库可以给本地代码直接调用:
// ThirdLibWrapper.h
#ifndef _THIRD_LIB_WRAPPER_H_
#define _THIRD_LIB_WRAPPER_H_
const char* makeRandomStringWrapper(const char* prefix);
#endif
// ThirdLibWrapper.cpp
#define _LIBCPP_ABI_VERSION ndk1
#include "ThirdLibWrapper.h"
#include "NDKHelper.h"
#include <ThirdLib.h>
const char* makeRandomStringWrapper(const char* prefix) {
return ::makeCString( ::makeRandomString( ::makeNDKString(prefix) ) );
}
// Android.bp
cc_library_shared {
name: "libthirdparty_wrapper",
srcs: [
"ThirdLibWrapper.cpp",
],
export_include_dirs: [
".",
],
shared_libs: [
"libthirdparty",
"libNDKHelper",
],
cflags: [
"-O2",
"-Wno-unused-parameter",
],
}
最后,本地集成的代码:
// main.cpp
#include <iostream>
#include <ThirdLibWrapper.h>
using namespace std;
int main(int argc, char const *argv[])
{
const char* result = makeRandomStringWrapper("Hello");
printf("result is %s\n", result);
delete[] result;
result = nullptr;
return 0;
}
// Android.bp
cc_binary {
name: "client",
srcs: [
"main.cpp",
],
shared_libs: [
"libthirdparty_wrapper",
],
cflags: [
"-O2",
"-Wno-unused-parameter",
],
}
完整的demo可以参考:ndk_string_test.zip
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。