三方动态链接库集成
=========

#### 概述

在实际项目中，业务功能可能由不同“团队/组织”提供，如：团队A开发功能编译生成so库，团队B引用so库进行后续开发。so库可以将项目的不同功能模块化，提升代码的复用性和工程的可维护性。团队开发过程中引用三方so库的场景可分为两种：

1.  在Native侧引用三方so库。
2.  在ArkTS侧引用三方so库。

下面针对这两种场景给出具体的实现方案。

#### 在Native侧引用三方so库

按照实际开发场景可分为两部分：编译生成so库和在Native侧引用so库。

第一部分：开发功能函数，编译生成so库。具体操作可参考：[使用命令行CMake构建NDK工程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/build-with-ndk-cmake)。

第二部分：在Native侧引用so库调用功能函数。可以采用如下两种方案：

*   方案一：通过编译动态链接库的方式引用。
*   方案二：通过调用dlopen的方式引用。

#### \[h2\]通过编译动态链接库的方式引用

**实现原理**

将so库加入到工程中，在Native侧使用CMake编译动态链接库，通过include引用头文件调用功能函数。

**开发步骤**

以引用一个加法计算so库为例，具体实现步骤如下：

1.  将第一部分生成的so库文件置于entry/libs对应的架构目录下，将其对应的头文件置于src/main/cpp目录下。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/b9/v3/VR-5vSyhSES8_G3XCEnKAg/zh-cn_image_0000002193852148.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=B6A71DB6B9FCAB24BB1CCC20EF885922C5FD285D3DEB16B8335D85D5E3D40E6E)
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/80/v3/LxNv284bRcKiC70Iq_iTDg/zh-cn_image_0000002193852140.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=A18F8BAAC07F0A515E0C9AB72FFCD47DD89505C65DA6A074B9D7636D336CC4E3)
    
2.  修改src/main/cpp目录下CMakeLists.txt文件配置，使用target\_link\_libraries命令将需要预加载的加法so库链接到项目中。
    
    \# Compile and link third-party SO libraries
    target\_link\_libraries(entry PUBLIC ${NATIVERENDER\_ROOT\_PATH}/../../../libs/${OHOS\_ARCH}/libnativeAdd.so)
    
3.  在Native侧通过头文件引用加法so库。
    
    static napi\_value NAPI\_Global\_nativeAdd(napi\_env env, napi\_callback\_info info) {
        size\_t argc = 2;
        napi\_value args\[2\] = {nullptr};
    
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
    
        napi\_valuetype valuetype0;
        napi\_typeof(env, args\[0\], &valuetype0);
    
        napi\_valuetype valuetype1;
        napi\_typeof(env, args\[1\], &valuetype1);
    
        double value0;
        napi\_get\_value\_double(env, args\[0\], &value0);
    
        double value1;
        napi\_get\_value\_double(env, args\[1\], &value1);
    
        napi\_value ret;
        napi\_create\_double(env, add(value0, value1), &ret);
    
        return ret;
    }
    
    // ...
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports) {
        napi\_property\_descriptor desc\[\] = {
            {"nativeAdd", nullptr, NAPI\_Global\_nativeAdd, nullptr, nullptr, nullptr, napi\_default, nullptr},
            // ...
        napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
        return exports;
    }
    EXTERN\_C\_END
    
4.  在index.d.ts文件中导出Native侧提供的接口，在ArkTS侧进行结果验证。
    
    // Export the addition calculation APIs provided on the Native side
    export const nativeAdd: (a: number, b: number) => number;
    
    在ArkTS侧调用Native侧提供的接口进行加法计算。
    
    // Integrate the SO library on the Native side directly to complete the addition operation
    let result = testNapi.nativeAdd(Number(this.paramX), Number(this.paramY));
    

**图1** 在Native侧通过编译动态链接库的方式引用so库完成加法运算效果展示  
![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/9f/v3/Kr3WHRIwTwCsaIGoHrCDsA/zh-cn_image_0000002194011732.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=0008787ED424A55B141DE0BAAE05F2B1875E3F3CBACFDB9C8FBFDB8412163600 "点击放大")

#### \[h2\]通过调用dlopen的方式引用

**实现原理**

将so库加入到工程中，在ArkTS侧将so库的沙箱路径传递至Native侧，在Native侧使用dlopen解析so库调用功能函数。但是需要注意，该方案只能引用C语言编译模式生成的so库，因此用于生成so库的.h头文件需要用extern "C" {}包裹。

**开发步骤**

以引用一个减法计算so库为例，具体实现步骤如下：

1.  将第一部分生成的so库文件置于entry/libs对应的架构目录下。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/fb/v3/aPqT-ccWT4mgI5wpwa0GRQ/zh-cn_image_0000002194011724.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=7EA15799F473ED88EA8DE817787DF3FE2CF4B5755701DF8E1D84C0127BD4BFEC)
    
2.  在ArkTS侧将so库的[沙箱路径](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-sandbox-directory)传递至Native侧。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/04/v3/u_xskRh3RSOlPCpVm3AjZA/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=1AD16B951CAE4FC51448F2DC66F648E81FADE04CDEB95128914E1C645E60D2D2)
    
    此处需要使用so库的沙箱路径，而不是其真实路径。
    
    为保障用户隐私安全，dlopen具有命名空间隔离能力，应用可以加载的动态库受到命名空间的限制。一般应用只能够加载应用安装包目录/data/storage/el1/bundle下的动态库，以及系统内置对外开放的动态库，若加载自定义路径动态库会报错：MUSL-LDSO bundlename E Open absolute\_path library: check ns accessible failed, pathname libxxx.so namespace moduleNs\_default。
    
    let projectPath = this.getUIContext().getHostContext()!.bundleCodeDir; // Get the project path
    let abiPath = deviceInfo.abiList === 'x86\_64' ? 'x86\_64' : 'arm64';
    let soLibPath = \`${projectPath}/libs/${abiPath}/libnativeSub.so\`;
    
3.  在Native侧引入dlfcn.h，通过调用dlopen解析so库实现减法计算。
    
    typedef double (\*Sub)(double, double);
    static napi\_value NAPI\_Global\_nativeSub(napi\_env env, napi\_callback\_info info) {
        size\_t argc = 3;
        napi\_value args\[3\] = {nullptr};
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
        double value0;
        napi\_get\_value\_double(env, args\[0\], &value0);
        double value1;
        napi\_get\_value\_double(env, args\[1\], &value1);
        size\_t length = 0;
        napi\_status status = napi\_get\_value\_string\_utf8(env, args\[2\], nullptr, 0, &length);
        if (status != napi\_ok) {
            return nullptr;
        }
        char \*path = new char\[length + 1\];
        std::memset(path, 0, length + 1);
        napi\_get\_value\_string\_utf8(env, args\[2\], path, length + 1, &length); // Get the SO library path information
        void \*handle = dlopen(path, RTLD\_LAZY);                              // Open a SO library and get the path
        napi\_value result = nullptr;
        Sub sub\_func = (Sub)dlsym(handle, "sub"); // Get the function named sub
        status = napi\_create\_double(env, sub\_func(value0, value1), &result);
        delete\[\] path;
        dlclose(handle); // Remember to close the SO library
        if (status != napi\_ok) {
            return nullptr;
        }
        return result;
    }
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports) {
        napi\_property\_descriptor desc\[\] = {
            // ...
            {"nativeSub", nullptr, NAPI\_Global\_nativeSub, nullptr, nullptr, nullptr, napi\_default, nullptr}};
        napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
        return exports;
    }
    EXTERN\_C\_END
    
4.  在index.d.ts文件中导出Native侧提供的接口，在ArkTS侧进行结果验证。
    
    // Export the subtraction calculation interface provided by the Native side
    export const nativeSub: (a: number, b: number, path: string) => number;
    
    在ArkTS侧调用Native侧提供的接口进行减法计算。
    
    // Integrate the SO library on the Native side by dlopen to complete the subtraction operation
    let result = testNapi.nativeSub(Number(this.paramX), Number(this.paramY), soLibPath);
    

**图2** 在Native侧通过调用dlopen引用三方so库完成减法运算效果展示  
![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/2d/v3/gG4-4ti-TiuXIkE9JiBkGA/zh-cn_image_0000002193852144.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=89F892054522DFE44B20DABE57DA30DB6159AE50C640AB8ADB9CEF2E7E58CBCF "点击放大")

#### 在ArkTS侧引用三方so库

按照实际开发场景可分为两部分：生成适配Native的so库和在ArkTS侧引用so库。

第一部分：开发功能函数，编译生成so库并适配Native。具体操作可参考：[使用命令行CMake构建NDK工程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/build-with-ndk-cmake)。

第二部分：在ArkTS侧通过配置模块动态依赖的方式引用so库。

#### \[h2\]通过配置模块动态依赖引用

**实现原理**

将so库和对应的Native侧接口文件加入到工程中，在工程中配置so库对应的模块动态依赖，在ArkTS侧通过import引入依赖接口调用so库。但是需要注意该方案只能引用适配Native的so库，因此在编译生成so库时需要实现功能函数并[向Napi注册其Native侧接口](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-process#native侧方法的实现)，提供对应的Native侧接口文件index.d.ts和配置文件oh-package.json5。

**开发步骤**

以引用一个乘法计算so库为例，具体实现步骤如下：

1.  将第一部分生成的so库文件置于entry/libs对应的架构目录下。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/c/v3/HR0OV6PTQaeiDo3Dqc5yhA/zh-cn_image_0000002193852156.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=96FCCABC9608204AD17D92BBD3A10806C78641B1B7C6BB53D6BD00D30C4D5D18)
    
2.  在src/main/cpp/types下新建目录并将so库模块src/main/cpp/types目录下的index.d.ts、oh-package.json5移动到该目录下。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/7d/v3/YgbBgm79RpGyNm0q1uZgag/zh-cn_image_0000002194011736.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=FCD0621033AA9C55D88842803D14C79DF4F2649D3436478BF1263E0425C2AD56)
    
3.  在模块级oh-package.json5中声明乘法so库根目录路径。
    
    {
      // ...
      "dependencies": {
        // ...
        // The declared dependency name should match the name of the referenced SO library
        "libmultiply.so": "file:./src/main/cpp/types/libmultiply"
      }
    }
    
4.  在ArkTS侧使用import引用oh-package.json5中声明的依赖并进行结果验证。
    
    // Integrate the SO library that has been adapted to Native On the ArkTS side to complete the multiplication operation
    let result = multiplyNapi.multiply(Number(this.paramX), Number(this.paramY));
    

**图3** 在ArkTS侧引用已经适配Native的三方so库完成乘法运算效果展示  
![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/f8/v3/4A5is61LSY-3JtDcJWWs6Q/zh-cn_image_0000002194011728.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=EDEDCA8618D51AAE8EAFE1D8660BF51FD65DFA24D918D9ECAC2AD4ED12B76DEA "点击放大")

#### 常见问题

#### \[h2\]如何在同一个工程中实现三方so库的编译和引用

可以在工程中创建两个Module，通过其中一个Module编译生成加法、减法和乘法运算so库，通过另外一个Module引用三方so库，进行结果验证。

**参考链接**

*   [使用命令行CMake构建NDK工程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/build-with-ndk-cmake)
*   [在NDK工程中使用预构建库](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/build-with-ndk-prebuilts)

#### \[h2\]在集成三方so库时，.so库文件和.h头文件一定要置于上述方法的路径下吗

不一定。原则上.so库文件和.h头文件可以置于需要引用so库的工程目录的任意位置，但是需要在工程的CMakeLists.txt文件中修改文件配置。如：将add.h头文件和libnativeAdd.so库文件放置在entry的根目录下，需要在CMakeLists.txt文件中通过include\_directories命令添加entry的根目录作为头文件路径，并修改target\_link\_libraries命令中需要预加载的加法so库的路径，才能保证so库链接成功。

**代码示例**

\# src/main/cpp/CMakeLists.txt
include\_directories(${NATIVERENDER\_ROOT\_PATH}/../../..)
target\_link\_libraries(entry PUBLIC ${NATIVERENDER\_ROOT\_PATH}/../../../libnativeAdd.so)

#### \[h2\]在纯ArkTS工程中如何引用三方so库

纯ArkTS工程可以通过配置模块动态依赖的方式引用so库。但是需要注意，在引用过程中除了将已经适配Native的xxx.so库文件置于entry/libs对应的架构目录下外，还需要将编译三方so库时配套产生的libc++\_xxx.so库文件置于该目录下。

**示例如下**

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/d8/v3/FtLquFh9QtqUamsFPysF6w/zh-cn_image_0000002193852152.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=8D1F7050AD1C59E1A5E1F535107533330A458FC91FA2CE44430C10193473D3F8)![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/34/v3/2eklPmupS4S4nwHsELGD3A/zh-cn_image_0000002194011740.png?HW-CC-KV=V1&HW-CC-Date=20260612T074607Z&HW-CC-Expire=86400&HW-CC-Sign=97D2BDC5BC6EC9497033C7FB229A1521C98BA984B4B2938DF209CC76DAB9ECE9)

#### 示例代码

*   [实现动态链接库（.so）的引用](https://gitcode.com/harmonyos_samples/NativeSoIntegration)