HarmonyOS NEXT开发:C代码打包so库,napi调用函数及回调

适用流程:已有一些C代码,不想把源码添加到HarmonyOS工程中,想要把已有的C代码编译成HarmonyOS平台可以使用的so库,在DevEco Studio工程的cpp模块加载so库并调用里面的函数,最终使用ArkTS/TS调用该函数(获取回调)。

C源码可以添加到HarmonyOS工程的,可以省略手动编译过程。

C代码打包.so动态库

获取OpenHarmony SDK

官网套件货架获取DevEco Studio安装包(也可以直接下载Command Line Tools)
解压后得到 免安装版 或 exe(需安装)
安装后,在安装目录下找到 sdk\HarmonyOS-NEXT-DB1\openharmony\native
不同版本的路径名称略有差异,大致相同,最终找到native目录

配置环境变量

新建以下环境变量(假设你的SDK放在XXX目录下)

环境变量名环境变量值
HDC_SERVER_PORT7035
OHOS_HDC_SERVER_PORT7036
OHOS_NDK_HOMEXXX\sdk\HarmonyOS-NEXT-DB1\openharmony\native

把以下路径加入Path环境变量
XXX\sdk\HarmonyOS-NEXT-DB1\openharmony\native\build-tools\cmake\bin

编写CMakeLists

在要编译的C代码根目录下添加CMakeLists.txt

cmake_minimum_required(VERSION 3.5.0)
project(net)

# SKIP REPATH SETTING. Next four lines are set for skipping repath.
# The fourth line works. Don't know if the third line works.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s -ftrapv -D_FORTIFY_SOURCE=2 -O2")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -ftrapv -D_FORTIFY_SOURCE=2 -O2")
set(CMAKE_SKIP_BUILD_RPATH_TRUE)
set(CMAKE_SKIP_RPATH TRUE)

# SKIP REPATH SETTING. Next two line is set for skipping repath.
add_compile_options(-fstack-protector-all)
# stack protector and Strip
add_link_options(-s)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}
                    ${CMAKE_CURRENT_SOURCE_DIR}/include)

# 链接静态库(例如需要链接OpenSSL)
link_libraries(${CMAKE_CURRENT_SOURCE_DIR}/lib/${OHOS_ARCH}/libcrypto.a)
link_libraries(${CMAKE_CURRENT_SOURCE_DIR}/lib/${OHOS_ARCH}/libssl.a)

# 将所有.c文件添加进来
file(GLOB_RECURSE SOURCES "./src/*.c")

# 编译为动态库,net是动态库的名称
# SHARED 动态库, STATIC 静态库
add_library(net
            SHARED
            ${SOURCES})

# 包含所有头文件的目录
target_include_directories(net PUBLIC
					"${CMAKE_CURRENT_SOURCE_DIR}/include"
					"${CMAKE_CURRENT_SOURCE_DIR}/include/openssl"
)

# 添加导入库的查找目录
target_link_directories(net PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/lib/${OHOS_ARCH}/)

# 链接导入库
target_link_libraries(net PUBLIC libssl.a libcrypto.a libace_napi.z.so libhilog_ndk.z.so libnet_connection.so)

编写脚本(Windows)

以编译脚本(.bat)与CMakeLists在同级目录为例

rd /s /Q build_cmake

mkdir build_cmake

cd build_cmake

cmake -DCMAKE_C_COMPILER:FILEPATH=%OHOS_NDK_HOME%\llvm\bin\clang.exe -DCMAKE_CXX_COMPILER:FILEPATH=%OHOS_NDK_HOME%\llvm\bin\clang++.exe -DCMAKE_TOOLCHAIN_FILE=%OHOS_NDK_HOME%\build\cmake\ohos.toolchain.cmake -DCMAKE_MAKE_PROGRAM=%OHOS_NDK_HOME%\build-tools\cmake\bin\ninja.exe -DOHOS_ARCH=arm64-v8a -DOHOS_STL=c++_shared .. -G Ninja

cmake --build .

cd /d %~dp0

编译

在命令行工具执行该脚本,即可打包成arm64-v8a的动态库,在build_cmake目录下获取libtest.so

使用NAPI调用动态库的函数

添加so

创建Native C++ Project

Create Project

把动态库放在module的libs目录下
add so

在napi.cpp中调用动态库的函数

假设C库中有一个函数是这样的:(需要ohos.permission.GET_NETWORK_INFO权限)

int get_client_ip(char*** client_ip, int* ip_size);

下面是napi.cpp中调用该函数

#include "aaa.h"//为了方便使用C库里面的一些结构体和宏定义,把头文件拷贝过来并引用
#include "dlfcn.h"

#undef LOG_TAG
#define LOG_TAG "NapiTest"
#define NATIVE_PATH "/data/storage/el1/bundle/libs/arm64/libnet.so"//这个路径可以在上层通过context获取,目前是固定的

void *native_lib = nullptr;

#define GET_NAPI_ERROR()                                                                                                    \
    const napi_extended_error_info *errorInfo;                                                                              \
    status = napi_get_last_error_info(gEnv, &errorInfo);                                                                    \
    if (status == napi_ok) {                                                                                                \
        OH_LOG_ERROR(LOG_APP, "last error info : %{public}s", errorInfo->error_message);                                    \
    } else {                                                                                                                \
        OH_LOG_ERROR(LOG_APP, "get last error error");                                                                      \
    }

typedef int (*GetClientIP)(char ***client_ip, int *ip_size);
static napi_value getClientIP(napi_env env, napi_callback_info info) {
    OH_LOG_INFO(LOG_APP, "getClientIP");
    char **client_ip = nullptr;
    int ip_size = 0;

    napi_value result = nullptr;
    if (native_lib == nullptr) {
        OH_LOG_INFO(LOG_APP, "arrayGetClientPublicIP native_lib is empty, dlopen");
        native_lib = dlopen(NATIVE_PATH, RTLD_LAZY | RTLD_GLOBAL);
        if (native_lib == nullptr) {
            OH_LOG_ERROR(LOG_APP, "cannot load library: %{public}s", dlerror());
            napi_get_undefined(env, &result);
            return result;
        }
    }
    dlerror();
    GetClientIP getIP = (GetClientIP)dlsym(native_lib, "get_client_ip");
    if (dlerror()) {
        OH_LOG_ERROR(LOG_APP, "load get_client_ip failed: %{public}s", dlerror());
        napi_get_undefined(env, &result);
        return result;
    }
    int value = getIP(&client_ip, &ip_size);
    if (value == ERR_SUCCESS) {
        OH_LOG_INFO(LOG_APP, "get client ip success, ip_size=%{public}d", ip_size);
        napi_value ip_array;
        napi_create_array_with_length(env, ip_size, &ip_array);

        for (int i = 0; i < ip_size; ++i) {
            OH_LOG_INFO(LOG_APP, "client ip[%{public}d]=%{public}s", i, client_ip[i]);
            napi_value ip_string;
            napi_create_string_utf8(env, client_ip[i], NAPI_AUTO_LENGTH, &ip_string);
            napi_set_element(env, ip_array, i, ip_string);
        }

        result = ip_array;
    } else {
        OH_LOG_INFO(LOG_APP, "get client ip failed, ret=%{public}d", value);
        napi_get_undefined(env, &result);
    }

    for (int i = 0; i < ip_size; ++i) {
        free(client_ip[i]);
    }
    free(client_ip);

    return result;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "getClientIP", nullptr, getClientIP, 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 = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

CMakeLists.txt

把napi.cpp和头文件、库添加进来

cmake_minimum_required(VERSION 3.5.0)
project(NativeDemo)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

if(DEFINED PACKAGE_FIND_FILE)
    include(${PACKAGE_FIND_FILE})
endif()

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

link_libraries(${NATIVERENDER_ROOT_PATH}/../../../libs/${OHOS_ARCH}/libnet.so)

add_library(entry SHARED napi.cpp)
target_link_libraries(extry PUBLIC libace_napi.z.so libhilog_ndk.z.so libnet.so libnet_connection.so)

桥接TS与C++

index.d.ts
在Index.d.ts中添加该接口的TS定义

export const getClientIP: () => Array<string>

TS/ArkTS调用

最终,在TS代码中添加库的引用,即可调用该native接口

import testNapi from 'libentry.so';

@Entry
@Component
struct Index {
  @State message: string = ''

  build() {
    Row() {
      Column() {
        Button('Get Client IP')
          .fontSize(20)
          .onClick(() => {
            this.message = testNapi.getClientIP().toString()
          })
        Text(this.message)
          .fontSize(20)
          .margin({ top: 20 })
      }
      .width('100%')
    }
    .height('100%')
  }
}

签名,运行,大功告成!

Gitee代码库

https://gitee.com/huyuhy/harmony-os

先写这么多,回调下次再写。。。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值