目录

What should I do if multiple SO libraries depend on each other?

Symptom

Module A has a.so, and module B has b.so. The a.so file calls the functions of the b.so file, and the b.so file also calls the functions of the a.so file. The compilation fails no matter which .so file is compiled first.

Solution

Use dlopen and dlsym to decouple the compilation dependencies between the SO libraries and convert implicit dependencies to explicit dependencies. The sample code is as follows:

  1. Modify the code and CMakeLists.txt file, and use dlopen() on the native side to generate liba.so and libb.so. The generated .so files are in the build/default/intermediates/cmake/default/obj directory.

    (Note that the function must be enclosed in extern "C" {}. Otherwise, the corresponding function cannot be identified, causing a compilation error.)

    // a.cpp
    extern "C" {     // Be sure to enclose it with extern 'C' {}
    #include "a.h"
    #include <dlfcn.h>
    #include "stdio.h"
    typedef int (*FUNC_SUB)(int, int);
    int add(int a, int b) { return a + b; }
    int getb(char *path, int a, int b) {       // Path:The sandbox path for passing So files from ArkTS side (note that the path should be passed from ArkTS side, otherwise it may not be found, and the specific code will be listed later)
        void *handle = dlopen(path, RTLD_LAZY);  // Open the dynamic link library with path as path
        if (!handle) {
            return 0;
        }
        FUNC_SUB sub_func = (FUNC_SUB)dlsym(handle, "sub"); // Get the function named sub
        int res = sub_func(a, b);                           // caller function
        dlclose(handle);                                    // Close dynamic link library
        return res;
    }
    }

    // a.h
    extern "C" {
    #ifndef DemoSO_a_H
    #define DemoSO_a_H
    int add(int a, int b);
    int getb(char *path, int a, int b);
    #endif // DemoSO_a_H
    }

    // b.cpp
    extern "C" {     // Be sure to enclose it with extern 'C' {}
    #include "b.h"
    #include <dlfcn.h>
    #include "stdio.h"
    typedef int (*FUNC_ADD)(int, int);
    int sub(int a, int b) { return a - b; }
    int geta(char *path, int a, int b) {    // Path: The sandbox path for passing So files from ArkTS side (note that the path should be passed from ArkTS side, otherwise it may not be found, and the specific code will be listed later)
        void *handle = dlopen(path, RTLD_LAZY);    // Open the dynamic link library with path as path
        if (!handle) {
            return 0;
        }
        FUNC_ADD add_func = (FUNC_ADD)dlsym(handle, "add");      // Get the function named sub
        int res = add_func(a, b);                                // caller function
        dlclose(handle);                                         // Close dynamic link library
        return res;
    }
    }

    // b.h
    extern "C" {
    #ifndef DemoSO_b_H
    #define DemoSO_b_H
    int sub(int a, int b);
    int geta(char *path, int a, int b);
    #endif // DemoSO_b_H
    }

    # CMakeLists.txt
    cmake_minimum_required(VERSION 3.4.1)
    project(liba)
    # Compile library liba.so
    add_library(a SHARED a.cpp)
    target_link_libraries(a PUBLIC libace_napi.z.so libhilog_ndk.z.so)
    project(libb)
    # Compile library libb.so
    add_library(b SHARED b.cpp)
    target_link_libraries(b PUBLIC libace_napi.z.so libhilog_ndk.z.so)
  2. Move the generated .so files (relative path: build/default/intermediates/cmake/default/obj) to the libs directory.

    The directory structure after the move is as follows:

  3. Modify the CMakeLists.txt file and import the generated .so files to the project for compilation.
    # CMakeLists.txt
    cmake_minimum_required(VERSION 3.4.1)
    project(DemoSO)
    set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
    include_directories(${NATIVERENDER_ROOT_PATH}
                        ${NATIVERENDER_ROOT_PATH}/include)
    # Add libdemoso. so file
    add_library(demoso SHARED hello.cpp)
    # Add dependency libraries liba.so and libb.so. Please note to include the path, otherwise the corresponding SO library cannot be found
    target_link_libraries(demoso PUBLIC libace_napi.z.so ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/liba.so ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/libb.so)

    // index.ets
    import testNapi from 'libdemoso.so';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    
    @Entry
    @Component
    struct Index {
      @State message: string = 'Hello World';
      private path: string = '';
    
      build() {
        Row() {
          Column() {
            Text(this.message)
              .fontSize(50)
              .fontWeight(FontWeight.Bold)
              .onClick(() => {
                this.path = this.getUIContext().getHostContext()!.bundleCodeDir;   // get path
                hilog.info(0x0000, 'testTag', 'Test NAPI 5 + 3 = %{public}d', testNapi.add(5, 3, this.path + '/libs/arm64/liba.so'));  // Call the native side function
                hilog.info(0x0000, 'testTag', 'Test NAPI 5 - 3 = %{public}d', testNapi.sub(5, 3, this.path + '/libs/arm64/libb.so'));
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }

    // index.d.ts
    export const add: (a: number, b: number, path: string) => number;
    export const sub: (a: number, b: number, path: string) => number;

    // hello.cpp
    #include "a.h"
    #include "b.h"
    #include "napi/native_api.h"
    
    static napi_value Add(napi_env env, napi_callback_info info) {
        size_t requireArgc = 3;
        size_t argc = 3;
        napi_value args[3] = {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);
        napi_valuetype valuetype2;
        napi_typeof(env, args[2], &valuetype2);
        int value0;
        napi_get_value_int32(env, args[0], &value0);
        int value1;
        napi_get_value_int32(env, args[1], &value1);
        char path[255];
        size_t size = 255;
        napi_get_value_string_utf8(env, args[2], path, 255, &size);
        int res = geta(path, value0, value1);                    // Call the function and pass the sandbox path
        napi_value sum;
        napi_create_int32(env, res, &sum);
        return sum;
    }
    static napi_value Sub(napi_env env, napi_callback_info info) {
        size_t requireArgc = 3;
        size_t argc = 3;
        napi_value args[3] = {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);
        napi_valuetype valuetype2;
        napi_typeof(env, args[2], &valuetype2);
        int value0;
        napi_get_value_int32(env, args[0], &value0);
        int value1;
        napi_get_value_int32(env, args[1], &value1);
        char path[255];
        size_t size = 255;
        napi_get_value_string_utf8(env, args[2], path, 255, &size);
        int res = getb(path, value0, value1);                 // Call the function and pass the sandbox path
        napi_value sum;
        napi_create_int32(env, res, &sum);
        return sum;
    }
    EXTERN_C_START
    static napi_value Init(napi_env env, napi_value exports) {
        napi_property_descriptor desc[] = {{"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},
                                           {"sub", nullptr, Sub, nullptr, nullptr, nullptr, napi_default, nullptr}};
        napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
        return exports;
    }
    EXTERN_C_END
    
    static napi_module demoModule = {
        .nm_version = 1,
        .nm_flags = 0,
        .nm_filename = nullptr,
        .nm_register_func = Init,
        .nm_modname = "demoso",
        .nm_priv = ((void *)0),
        .reserved = {0},
    };
    extern "C" __attribute__((constructor)) void RegisterDemosoModule(void) { napi_module_register(&demoModule); }