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