博客 / 詳情

返回

Android ndk string處理

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

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.