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::__1std::__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


马师傅777
1 声望0 粉丝