一、编译环境准备
1.1 开发工具
1.2 SDK下载
下载编译第三方库的SDK有两种方式,第一种方式从官方渠道根据电脑系统选择对应的SDK版本,第二种方式通过DevEco-Studio下载SDK。本文只介绍通过DevEco-Studio下载SDK的方式。
- 安装SDK到本地
- 根据SDK安装位置获取SDK
- 将SDK复制到GMSSL的同级目录并更名为ohos-sdk
二、编译鸿蒙架构
第三方库编译鸿蒙架构有两种方式。一种是使用C/C++原生构建工具configure、makefile编译,一种是使用lycium框架快速交叉编译。本文只详细介绍通过configure、makefile编译的方式。
2.1 原生工具构建
-
编写编译脚本
#!/bin/bash # Define paths and environment variables # 获取脚本当前所在路径 CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # 获取GmSSL源码路径 GMSSL_DIR=$CURRENT_PATH/GmSSL2.5.4 # 取脚本执行的第一个参数 OHOS_TARGET_ABI=$1 # 获取鸿蒙SDK路径 export OHOS_SDK=$CURRENT_PATH/ohos-sdk # 编译64位架构 if [ "$OHOS_TARGET_ABI" == "arm64-v8a" ] then export AS=${OHOS_SDK}/native/llvm/bin/llvm-as export CC=${OHOS_SDK}/native/llvm/bin/aarch64-unknown-linux-ohos-clang export CXX=${OHOS_SDK}/native/llvm/bin/aarch64-unknown-linux-ohos-clang++ export LD=${OHOS_SDK}/native/llvm/bin/ld.lld export STRIP=${OHOS_SDK}/native/llvm/bin/llvm-strip export RANLIB=${OHOS_SDK}/native/llvm/bin/llvm-ranlib export OBJDUMP=${OHOS_SDK}/native/llvm/bin/llvm-objdump export OBJCOPY=${OHOS_SDK}/native/llvm/bin/llvm-objcopy export NM=${OHOS_SDK}/native/llvm/bin/llvm-nm export AR=${OHOS_SDK}/native/llvm/bin/llvm-ar export CFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1" export CXXFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1" outdir=arm64-v8a cd ${GMSSL_DIR} ./Configure linux-aarch64 elif [ "$OHOS_TARGET_ABI" == "armeabi-v7a" ] then #编译32位架构 export AS=${OHOS_SDK}/native/llvm/bin/llvm-as export CC=${OHOS_SDK}/native/llvm/bin/armv7-unknown-linux-ohos-clang export CXX=${OHOS_SDK}/native/llvm/bin/armv7-unknown-linux-ohos-clang++ export LD=${OHOS_SDK}/native/llvm/bin/ld.lld export STRIP=${OHOS_SDK}/native/llvm/bin/llvm-strip export RANLIB=${OHOS_SDK}/native/llvm/bin/llvm-ranlib export OBJDUMP=${OHOS_SDK}/native/llvm/bin/llvm-objdump export OBJCOPY=${OHOS_SDK}/native/llvm/bin/llvm-objcopy export NM=${OHOS_SDK}/native/llvm/bin/llvm-nm export AR=${OHOS_SDK}/native/llvm/bin/llvm-ar export CFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1" export CXXFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1" outdir=armeabi-v7a cd ${GMSSL_DIR} ./Configure linux-generic32 elif [ "$OHOS_TARGET_ABI" == "x86_64" ] then #编译32位架构 export AS=${OHOS_SDK}/native/llvm/bin/llvm-as export CC=${OHOS_SDK}/native/llvm/bin/x86_64-unknown-linux-ohos-clang export CXX=${OHOS_SDK}/native/llvm/bin/x86_64-unknown-linux-ohos-clang++ export LD=${OHOS_SDK}/native/llvm/bin/ld.lld export STRIP=${OHOS_SDK}/native/llvm/bin/llvm-strip export RANLIB=${OHOS_SDK}/native/llvm/bin/llvm-ranlib export OBJDUMP=${OHOS_SDK}/native/llvm/bin/llvm-objdump export OBJCOPY=${OHOS_SDK}/native/llvm/bin/llvm-objcopy export NM=${OHOS_SDK}/native/llvm/bin/llvm-nm export AR=${OHOS_SDK}/native/llvm/bin/llvm-ar export CFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1" export CXXFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1" outdir=x86_64 # Navigate to OpenSSL directory ./Configure linux-x86_64 else echo "Unsupported target ABI: $ANDROID_TARGET_ABI" exit 1 fi make # Copy the outputs OUTPUT_INCLUDE=$CURRENT_PATH/ohos_libs/include OUTPUT_LIB=$CURRENT_PATH/ohos_libs/lib/${outdir} mkdir -p $OUTPUT_INCLUDE mkdir -p $OUTPUT_LIB cp -RL include/openssl $OUTPUT_INCLUDE cp libcrypto.so.1.1 $OUTPUT_LIB cp libcrypto.a $OUTPUT_LIB cp libssl.so.1.1 $OUTPUT_LIB cp libssl.a $OUTPUT_LIB
将编写好的脚本文件命名为ohos.sh。文件之间的目录结构如下:
-
执行脚本
cd到HarmonyOS-SDK目录下,依次执行如下命令,分别编译64位、32位、x86_64的鸿蒙架构。
ohos.sh arm64-v8a ohos.sh armeabi-v7a ohos.sh x86_64
-
编译问题
解决方案:删除GMSSL2.5.4源码中对getcontext、makecontext、setcontext等函数的引用。
三、使用库文件
鸿蒙工程可以使用.a的静态库和.so的动态库,两种类型的库引用一种即可。
3.1 创建NAPI工程
NAPI是OpenHarmony系统中的原生模块扩展开发框架,提供JavaScript与C/C++模块之间相互调用的交互能力。
- 添加二进制文件到工程
如果该三方库二进制文件为so文件,还需要将so文件拷贝到工程目录的entry/libs/${OHOS_ARCH}/
目录下,如下图:
3.2 配置链接
添加二进制文件后需要在cpp目录的CMakeLists.txt文件中添加对应target_link_libraries
才能被工程引用。
-
配置静态库链接
target_link_libraries(product PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/gmssl/${OHOS_ARCH}/lib/libcrypto.a) target_link_libraries(product PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/gmssl/${OHOS_ARCH}/lib/libssl.a)
-
配置动态库链接
target_link_libraries(product PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/gmssl/${OHOS_ARCH}/lib/libcrypto.so) target_link_libraries(product PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/gmssl/${OHOS_ARCH}/lib/libssl.so)
-
配置头文件
target_include_directories(product PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/gmssl/${OHOS_ARCH}/include)
四、编写NAPI接口
配置完三方库的链接和头文件路径后,可以根据各自的业务逻辑调用三方库对应的接口完成NAPI接口的编写,NAPI接口开发可以参照以下文档学习。
4.1 NAPI编写示例
NAPI接口编写的基本思路是将从JavaScript层传入的参数转成C/C++数据类型,然后调用第三方库接口执行业务逻辑,最后将执行的结果转成JavaScript数据类型返回给JavaScript层。
-
编写接口
napi_init.cpp文件中编写NAPI接口。
第一步:获取JS层传入的参数转成C/C++数据类型并将转换的结果作为参数传入第三方库函数中。
static napi_value gm_post(napi_env env, napi_callback_info info){
size_t argc = 4;
napi_value args[4];
// 获取JS参数
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
char url[512] = {0},parameter[MAX_BUF] = {0}, filePath[1024] = {0};
size_t url_len,parameter_len,filePath_len;
double timeOut;
// 获取请求地址 napi(JS)数据类型转c/c++数据类型
napi_get_value_string_utf8(env, args[0], url, sizeof(url), &url_len);
// 获取请求参数 napi(JS)数据类型转c/c++数据类型
napi_get_value_string_utf8(env, args[1], parameter, sizeof(parameter), ¶meter_len);
// 获取超时时间
napi_get_value_double(env, args[2], &timeOut);
// 获取证书路径
napi_get_value_string_utf8(env, args[3], filePath, sizeof(filePath), &filePath_len);
// 调用gmssl库的post请求方法
resp_t *resp = infosecPost(url, parameter, timeOut, NULL, filePath);
// 格式转换后返回响应数据
return transformRespToNapi(env, resp);
}
第二步:将第三方库返回的结果转成JavaScript数据类型
static napi_value transformRespToNapi(napi_env env,resp_t *resp){
napi_value res_obj;
// 1.创建对象
napi_status status = napi_create_object(env, &res_obj);
if (status != napi_ok) {
napi_throw_error(env,"-1","创建响应数据对象异常");
return nullptr;
}
// 2.创建data属性值
napi_value res_data;
status = napi_create_string_utf8(env, resp->data, NAPI_AUTO_LENGTH, &res_data);
if (status != napi_ok) {
napi_throw_error(env,"-2","创建属性值异常");
return nullptr;
}
status = napi_set_named_property(env, res_obj, "data", res_data);
if (status != napi_ok) {
napi_throw_error(env,"-3","设置对象的属性异常");
return nullptr;
}
// 3.创建code属性
napi_value res_code;
status = napi_create_double(env, resp->code, &res_code);
if (status != napi_ok) {
napi_throw_error(env,"-2","创建属性值异常");
return nullptr;
}
status = napi_set_named_property(env, res_obj, "code", res_code);
if (status != napi_ok) {
napi_throw_error(env,"-3","设置对象的属性异常");
return nullptr;
}
resp_free(resp);
return res_obj;
}
第三步:ArkTS接口与C/C++接口绑定和映射
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
{ "send_gm_post", nullptr, gm_post, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
说明:send_gm_post是ArkTS的函数名,gm_post是C/C++函数名。
- 声明ArkTS侧的方法
Index.d.ts文件中声明ArkTS侧的方法。
export const send_gm_post: (url: string,parameters:string,timeOut:number,caPath:string) => object;