工具:WSL + Ubuntu24 + DevEco Studio

环境配置

安装工具

## 切换root用户
sudo -i

## 更新和安装包
apt update
apt install -y gcc g++ cmake make pkg-config autoconf automake git git-lfs ninja-build
sudo apt install -y unzip curl wget tree vim

## 退出root用户
exit

## 配置python虚拟环境来安装meson,以免污染原有python环境
apt install -y python3-venv python3-full
cd ~
python3 -m venv ohos_env
source ohos_env/bin/activate
pip install meson==1.5.1
## 查看meson版本
meson --version

下载 OHOS SDK 压缩包

## 创建目录并下载 SDK 压缩包
mkdir -p ~/ohpkg
cd ~/ohpkg
~/ohpkg$ wget https://repo.huaweicloud.com/openharmony/os/6.0-Release/ohos-sdk-windows_linux-public.tar.gz

## 解压 SDK
~/ohpkg$ tar -zxf ohos-sdk-windows_linux-public.tar.gz

## 解压 native 工具链
cd linux
unzip -q native-linux-x64-6.0.0.47-Beta1.zip
~/ohpkg/linux$ unzip -q toolchains-linux-x64-6.0.0.47-Beta1.zip

配置 OHOS_SDK 等环境变量

## SDK路径和hnpcli路径改为自己的,一般只要改用户名(如:imlsy)
echo 'export OHOS_SDK=/home/imlsy/ohpkg/linux' >> ~/.bashrc
echo 'export HNP_TOOL=/home/imlsy/ohpkg/linux/toolchains/hnpcli' >> ~/.bashrc
source ~/.bashrc

## 检查是不是自己刚刚设置的
## 期望得到
## /home/imlsy/ohpkg/linux
## /home/imlsy/ohpkg/linux/toolchains/hnpcli
echo $OHOS_SDK
echo $HNP_TOOL

克隆 lycium++

# 克隆 Lycium++ 工具仓库(用于编译 HarmonyOS 第三方库)
git clone https://gitcode.com/OpenHarmonyPCDeveloper/lycium_plusplus.git

# 进入 Lycium 工具的脚本目录
cd ~/lycium_plusplus/lycium/script

# 备份环境配置文件(以防修改出错)
cp envset.sh envset.sh.bak

# 检查 envset.sh 脚本语法是否正确(不执行,仅做语法检查)
bash -n ~/lycium_plusplus/lycium/script/envset.sh

# 进入 Lycium 主目录
cd ~/lycium_plusplus/lycium

# 激活 Python 虚拟环境(用于运行 Lycium 工具)
source ~/ohos_env/bin/activate

# 加载环境变量(设置交叉编译工具链路径等)
source script/envset.sh

# 验证交叉编译器的 C 预处理器是否配置正确
# 应该输出:/home/imlsy/ohpkg/linux/native/llvm/bin/aarch64-linux-ohos-clang -E
echo $CPP

# 进入 CUPS 第三方库的源码目录
cd ~/lycium_plusplus/thirdparty/cups

# 返回 Lycium 主目录
cd ~/lycium_plusplus/lycium

# 重新激活 Python 虚拟环境
source ~/ohos_env/bin/activate

# 重新加载环境变量
source script/envset.sh

如果后续编译构建失败,记得用 rm 清除旧的构建


交叉编译

拉取 OpenCV 代码

进入 thirdparty,创建 opencv 适配目录,用 Git 进行项目拉取

## 进入目录,创建适配文件夹
cd ~/lycium_plusplus/thirdparty
mkdir -p opencv
cd opencv

## 拉取代码
git clone -b 4.8.0 https://github.com/opencv/opencv.git opencv-4.8.0

创建并编辑 HPKBUILD 文件

HPKBUILD 鸿蒙三方库的“构建说明书”,可以把它理解成是为特定 CPU(如鸿蒙 PC 的 ARM 架构)准备的一份“定制化编译菜谱”,lycium 框架会读取这份“菜谱”,并按照其中的步骤自动完成从下载源码到生成 .so 文件的全过程。

创建和编辑 HPKBUILD 文件

因为这里是部分编译 OpenCV 的内容,所以如果需要编译全部或者自己想要的内容可以进行修改。

# 编辑 HPKBUILD
vim HPKBUILD
~/lycium_plusplus/thirdparty/opencv/HPKBUILD 
#!/bin/bash
# Copyright (c) 2023 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License

pkgname=opencv
pkgver=4.8.0
pkgrel=0
pkgdesc="OpenCV (core + imgproc + imgcodecs only)"
url="https://github.com/opencv/opencv"
archs=("arm64-v8a")
license=("Apache License 2.0")
depends=()
makedepends=()

source="https://gitee.com/lycium_pkg_mirror/$pkgname/repository/archive/$pkgver.zip"

autounpack=false
downloadpackage=false
patchflag=true

builddir="opencv-${pkgver}"
packagename="${pkgver}.zip"
test_data_name=opencv_extra
test_data_version=4.8.1
test_data=$test_data_name-$test_data_version
testresdownloadflag=true

prepare() {
    # 检查本地源码是否存在
    if [ ! -d "$builddir" ]; then
        echo "ERROR: Source directory $builddir not found!"
        echo "Please copy OpenCV source to: $(pwd)/$builddir"
        exit 1
    fi
    echo "Using local source: $(pwd)/$builddir"
    
    if [ $patchflag == true ];then
        cd $builddir
        patch -p1 < `pwd`/../opencv_oh_test.patch > $publicbuildlog 2>&1 || true
        patchflag=false
        cd $OLDPWD
    fi
    
    mkdir -p $builddir/$ARCH-build
}

build() {
    cd $builddir
    ${OHOS_SDK}/native/build-tools/cmake/bin/cmake "$@" \
        -DBUILD_LIST=core,imgproc,imgcodecs \
        -DBUILD_TESTS=OFF \
        -DBUILD_PERF_TESTS=OFF \
        -DCMAKE_C_FLAGS="-fvisibility=default" \
        -DCMAKE_CXX_FLAGS="-fvisibility=default" \
        -DENABLE_NEON=OFF \
        -B$ARCH-build -S./ > $buildlog 2>&1
    $MAKE -C $ARCH-build VERBOSE=1 >> $buildlog 2>&1
    ret=$?
    cd $OLDPWD
    return $ret
}

package() {
    cd $builddir
    $MAKE -C $ARCH-build install >> $buildlog 2>&1
    cd $OLDPWD
}

check() {
    if [ $ARCH == "arm64-v8a" ]; then
        cp ${OHOS_SDK}/native/llvm/lib/aarch64-linux-ohos/libc++_shared.so $LYCIUM_ROOT/usr/$pkgname/$ARCH/lib/ 2>/dev/null || true
    fi
    echo "OpenCV built successfully for $ARCH"
}

cleanbuild(){
    rm -rf ${PWD}/$builddir/$ARCH-build
}

如果更喜欢可视化编辑,可以下载MobaXterm进行文件编辑。

确认环境变量

export OHOS_SDK=/home/imlsy/ohpkg/linux
export LYCIUM_ROOT=/home/imlsy/lycium_plusplus/lycium

编译

cd ~/lycium_plusplus/lycium
./build.sh opencv

成功标志

编译出来的文件在 “~/lycium_plusplus/lycium/usr/opencv/arm64-v8a/”。

传送编译好的文件到 Windows

将编译好的三方库复制/发送到 Windows 电脑(即本机)。

采用的方法可以是通过 \\wsl$ 访问文件直接复制这些文件到 Windows 目录下,或者用 psftp/scp 从 WSL 传输到 Windows。

进行复制粘贴的路径是 “\\wsl.localhost\Ubuntu_24.04\home\imlsy\lycium_plusplus\lycium\output\arm64-v8a”。

可以随便找一个 Windows 目录放进去。后续会将解压出来的文件放进项目里面进行使用。

这里是在 WSL 用 cp 进行复制粘贴

# 1. 创建目标目录(如果不存在)
mkdir -p /mnt/d/harmonyos_libplacebo/temp

# 2. 复制头文件(整个 opencv4 目录)
cp -r ~/lycium_plusplus/lycium/usr/opencv/arm64-v8a/include/opencv4 /mnt/d/harmonyos_libplacebo/temp/

# 3. 复制 .so 文件(复制实际文件,重命名为不带版本号)
cp ~/lycium_plusplus/lycium/usr/opencv/arm64-v8a/lib/libopencv_core.so.4.8.0 /mnt/d/harmonyos_libplacebo/temp/libopencv_core.so
cp ~/lycium_plusplus/lycium/usr/opencv/arm64-v8a/lib/libopencv_imgproc.so.4.8.0 /mnt/d/harmonyos_libplacebo/temp/libopencv_imgproc.so
cp ~/lycium_plusplus/lycium/usr/opencv/arm64-v8a/lib/libopencv_imgcodecs.so.4.8.0 /mnt/d/harmonyos_libplacebo/temp/libopencv_imgcodecs.so

# 4. 确认文件已复制
ls -la /mnt/d/harmonyos_libplacebo/temp/


DevEco Studio 集成调用

创建项目

创建一个 Native C++ 的空项目。

把我们将复制出来的 3 个文件放到 “MyOpenCVProject/entry/libs/arm64-v8a/” 目录下:

  • libopencv_core.so
  • libopencv_imgproc.so
  • libopencv_imgcodecs.so

将 opencv4 文件夹放入 “MyOpenCVProject\entry\src\main\cpp\include” 目录下,如果没有include 文件夹,就手动创建一个。

编写四个文件

CMakeLists.txt

告诉编译系统如何编译 C++ 代码

cmake_minimum_required(VERSION 3.5.0)
project(car_companion)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

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

# ========== 声明 OpenCV 库(三个模块) ==========
set(LIBS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH})

# core
add_library(opencv_core SHARED IMPORTED)
set_target_properties(opencv_core PROPERTIES IMPORTED_LOCATION ${LIBS_DIR}/libopencv_core.so)

# imgproc
add_library(opencv_imgproc SHARED IMPORTED)
set_target_properties(opencv_imgproc PROPERTIES IMPORTED_LOCATION ${LIBS_DIR}/libopencv_imgproc.so)

# imgcodecs
add_library(opencv_imgcodecs SHARED IMPORTED)
set_target_properties(opencv_imgcodecs PROPERTIES IMPORTED_LOCATION ${LIBS_DIR}/libopencv_imgcodecs.so)

# ========== 头文件路径 ==========
# 直接包含 opencv4 目录,这样 #include <opencv2/xxx.hpp> 就能找到
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include
                    ${NATIVERENDER_ROOT_PATH}/include/opencv4)

# ========== NAPI 模块 ==========
add_library(entry SHARED napi_init.cpp)

# ========== 链接 ==========
target_link_libraries(entry PUBLIC
    libace_napi.z.so
    opencv_core
    opencv_imgproc
    opencv_imgcodecs
)

napi_init.cpp

C++ 代码的入口文件,实现具体的业务逻辑。可以提供给鸿蒙端使用。

#include "napi/native_api.h"
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <string>
#include <vector>

// ========== 工具函数 ==========
static std::string MatInfo(const cv::Mat& mat) {
    char buf[256];
    snprintf(buf, sizeof(buf), "%dx%d, %d channels, %s",
             mat.cols, mat.rows, mat.channels(),
             mat.type() == CV_8UC3 ? "BGR" : "other");
    return std::string(buf);
}

// ========== 1. 获取版本 ==========
static napi_value GetOpenCVVersion(napi_env env, napi_callback_info info) {
    std::string version = cv::getVersionString();
    napi_value result;
    napi_create_string_utf8(env, version.c_str(), version.size(), &result);
    return result;
}

// ========== 2. 生成测试图片 ==========
static napi_value GenerateTestImage(napi_env env, napi_callback_info info) {
    cv::Mat image(480, 640, CV_8UC3);
    for (int y = 0; y < image.rows; y++) {
        for (int x = 0; x < image.cols; x++) {
            image.at<cv::Vec3b>(y, x) = cv::Vec3b(
                (x * 255 / image.cols),
                (y * 255 / image.rows),
                128
            );
        }
    }
    
    std::string infoStr = MatInfo(image);      // ← 改名为 infoStr
    std::string resultStr = "测试图片已生成: " + infoStr;
    
    napi_value result;
    napi_create_string_utf8(env, resultStr.c_str(), resultStr.size(), &result);
    return result;
}

// ========== 3. 灰度化 ==========
static napi_value ConvertToGray(napi_env env, napi_callback_info info) {
    cv::Mat src(480, 640, CV_8UC3);
    for (int y = 0; y < src.rows; y++) {
        for (int x = 0; x < src.cols; x++) {
            src.at<cv::Vec3b>(y, x) = cv::Vec3b(
                (x * 255 / src.cols),
                (y * 255 / src.rows),
                128
            );
        }
    }
    
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    
    std::string resultStr = "灰度化成功: " + MatInfo(gray);
    
    napi_value result;
    napi_create_string_utf8(env, resultStr.c_str(), resultStr.size(), &result);
    return result;
}

// ========== 4. 高斯模糊 ==========
static napi_value ApplyGaussianBlur(napi_env env, napi_callback_info info) {
    cv::Mat src(480, 640, CV_8UC3);
    for (int y = 0; y < src.rows; y++) {
        for (int x = 0; x < src.cols; x++) {
            src.at<cv::Vec3b>(y, x) = cv::Vec3b(
                (x * 255 / src.cols),
                (y * 255 / src.rows),
                128
            );
        }
    }
    
    cv::Mat blurred;
    cv::GaussianBlur(src, blurred, cv::Size(15, 15), 0);
    
    std::string resultStr = "高斯模糊完成: " + MatInfo(blurred);
    
    napi_value result;
    napi_create_string_utf8(env, resultStr.c_str(), resultStr.size(), &result);
    return result;
}

// ========== 5. 边缘检测 ==========
static napi_value ApplyCanny(napi_env env, napi_callback_info info) {
    cv::Mat src(480, 640, CV_8UC3, cv::Scalar(128, 128, 128));
    cv::rectangle(src, cv::Rect(100, 100, 200, 200), cv::Scalar(255, 255, 255), -1);
    cv::circle(src, cv::Point(400, 300), 80, cv::Scalar(0, 0, 255), -1);
    
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    
    cv::Mat edges;
    cv::Canny(gray, edges, 50, 150);
    
    std::string resultStr = "Canny 边缘检测完成: " + MatInfo(edges);
    
    napi_value result;
    napi_create_string_utf8(env, resultStr.c_str(), resultStr.size(), &result);
    return result;
}

// ========== NAPI 模块注册 ==========
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"getOpenCVVersion", nullptr, GetOpenCVVersion, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"generateTestImage", nullptr, GenerateTestImage, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"convertToGray", nullptr, ConvertToGray, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"applyGaussianBlur", nullptr, ApplyGaussianBlur, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"applyCanny", nullptr, ApplyCanny, 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);
}

Index.d.ts

告诉 ArkTS 这个原生模块有什么函数和类型,相当于 C++ 代码的"说明书"。他可以让 ArkTS 代码有智能提示,提供类型检查,防止调用错误的函数。

即他是 C++ 与 ArkTS 之间的接口定义,让  ArkTS 调用 C++ 的函数。

export const getOpenCVVersion: () => string;
export const generateTestImage: () => string;
export const convertToGray: () => string;
export const applyGaussianBlur: () => string;
export const applyCanny: () => string;

Index.ets

在鸿蒙项目使用我们刚刚编译出来的库。

import { hilog } from '@kit.PerformanceAnalysisKit';
import cv from 'libentry.so';

const DOMAIN = 0x0000;
const TAG = 'OpenCV';

@Entry
@Component
struct Index {
  @State version: string = '';
  @State result: string = '';

  build() {
    Scroll() {
      Column() {
        Text('OpenCV 鸿蒙适配验证')
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 24, bottom: 24 })

        // 按钮1:获取版本
        Button('获取 OpenCV 版本')
          .width('90%')
          .height(48)
          .fontSize(16)
          .backgroundColor('#007DFF')
          .borderRadius(12)
          .onClick(() => {
            this.version = cv.getOpenCVVersion();
            hilog.info(DOMAIN, TAG, 'Version: ' + this.version);
          })

        if (this.version) {
          Text('版本: ' + this.version)
            .fontSize(14)
            .fontColor('#333')
            .margin({ top: 8 })
        }

        // 按钮2:生成测试图片
        Button('生成测试图片(渐变图)')
          .width('90%')
          .height(48)
          .fontSize(16)
          .backgroundColor('#52C41A')
          .borderRadius(12)
          .margin({ top: 16 })
          .onClick(() => {
            this.result = cv.generateTestImage();
            hilog.info(DOMAIN, TAG, this.result);
          })

        // 按钮3:灰度化
        Button('灰度化处理')
          .width('90%')
          .height(48)
          .fontSize(16)
          .backgroundColor('#FA8C16')
          .borderRadius(12)
          .margin({ top: 12 })
          .onClick(() => {
            this.result = cv.convertToGray();
            hilog.info(DOMAIN, TAG, this.result);
          })

        // 按钮4:高斯模糊
        Button('高斯模糊')
          .width('90%')
          .height(48)
          .fontSize(16)
          .backgroundColor('#722ED1')
          .borderRadius(12)
          .margin({ top: 12 })
          .onClick(() => {
            this.result = cv.applyGaussianBlur();
            hilog.info(DOMAIN, TAG, this.result);
          })

        // 按钮5:边缘检测
        Button('Canny 边缘检测')
          .width('90%')
          .height(48)
          .fontSize(16)
          .backgroundColor('#EB2F96')
          .borderRadius(12)
          .margin({ top: 12 })
          .onClick(() => {
            this.result = cv.applyCanny();
            hilog.info(DOMAIN, TAG, this.result);
          })

        if (this.result) {
          Text(this.result)
            .fontSize(14)
            .fontColor('#555')
            .margin({ top: 12 })
            .padding(12)
            .backgroundColor('#F5F5F5')
            .borderRadius(8)
        }

        Blank().height(40)
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

检查

因为可能会出现找不到库的情况,所以需要检查 .so 文件的 SONAME 名字是否能指向正确 .so 文件。

  • 先找到 sdk 的 里面的 llvm-readelf ,一般在sdk里面能找到,比如:“ E:\app\devecostudio-windows-6.0.2.642\6.0.2.642\DevEco Studio\sdk\default\openharmony\native\llvm\bin
  • (可选)配置他到环境变量里面,我这里没有配
  • 查看,一般看 CMake.txt 里面 set 设置得 .so 的名字。例如下面的三个 libopencv_xx.so 。

## 第三步配置了环境变量
llvm-readelf -d xxx.so | Select-String SONAME

## 没有配置环境变量
& "E:\app\devecostudio-windows-6.0.2.642\6.0.2.642\DevEco Studio\sdk\default\openharmony\native\llvm\bin\llvm-readelf.exe" -d xxx.so | Select-String SONAME

先进入 arm64-x8v 目录下,进行查看,可以看到下面三个文件的 SONAME 的名字都没有出现在 arm64-x8v 里面,所以可以给这三个文件改个名字,或改他们的 SONAME 名字。

## 复制一份改名字
## 这里只是创建一个满足加载器查找规则的文件名。
ELF 内部 SONAME 并没有发生变化。
cp libopencv_core.so libopencv_core.so.408
cp libopencv_imgproc.so libopencv_imgproc.so.408
cp libopencv_imgcodecs.so libopencv_imgcodecs.so.408
  • 查看 SONAME,看复制到 “MyOpenCVProject/entry/libs/arm64-v8a/” 的三个 .so 文件是否有 SONAME 名字一样的 

    理解

    理解文件的调用逻辑

    为什么我们需要交叉编译,本质是指令集的不同,所以我们需要转换指令集。

    为什么指令集会不同呢?因为如果直接使用 Windows 本机编译器(MSVC、MinGW)编译,则默认生成 x86/x64 指令;而交叉编译工具链指定目标为 aarch64-linux-ohos,因此最终生成 ARM 可执行文件。不然 x86/x64 指令烧录进 鸿蒙PC(ARM) 端,会无法运行,因为 arm 架构无法跑 x86 指令集。

    所以交叉编译一般出现在开发编译的CPU架构和真正运行程序的CPU不一致产生的。

    ArkTS 层 (Index.ets)
       ↓ 调用
    接口声明层 (Index.d.ts)
       ↓ 映射到
    NAPI 注册层 (napi_init.cpp)
       ↓ 调用
    C++ 实现层 (include文件夹里面)
       ↓ 使用
    第三方库层 (.so,存放从 x86 编译成 arm 的 arm 二进制指令集)
       ↓ 运行在
    ARM 硬件层 (鸿蒙 PC)

    理解 ELF 文件 —— .so

    ELF 规定了一个可执行程序应该如何组织。它里面有很多个 section ,比如代码机器指令(.text)、未初始化变量(.bss)等。

    在编译 C/C++ 库的时候最重要的是链接。这里讲一下动态链接。

    .dynamic:这个库依赖谁?主要是 SONAME 和 NEEDED。在程序运行时被使用。
    比如 :

    • libplacebo.so 里面的 .dynamic 有 NEEDED libc.so,就是运行 libplacebo.so 之前必须先加载 libc.so,否则函数找不到。
    • libplacebo.so 里面的 .dynamic 有 SONAME libplacebo.so.371,就是动态链接器依靠这个区分不同名的 .so 文件或同名不同版本的 .so 文件。

    编译阶段:ld 会根据文件名查找 .so 文件(严格同名的文件)进行链接,并在生成的主 ELF 的 .dynamic 中记录 NEEDED。

    运行阶段:动态链接器根据主 ELF 的 .dynamic 中的 NEEDED 加载 .so 文件,然后读取该 .so 文件里面的 SONAME 来识别库的真实身份,并继续加载该 .so 的 NEEDED 依赖。

    整个过程就是:
    编译时,ld 根据文件名查找并记录依赖;
    运行时,动态链接器根据 NEEDED 加载库,根据 SONAME 识别库身份,递归加载所有依赖。

    例子:

    编译阶段:
    ld 编译 libentry.so 时,根据 CMakeLists.txt 中的 target_link_libraries 找到 libplacebo.so
    在 libentry.so 的 .dynamic 中记录:NEEDED libplacebo.so

    运行阶段:
    1. 主程序启动,动态链接器开始工作
    2. 读取主程序的 .dynamic,发现 NEEDED libentry.so
    3. 找到并加载 libentry.so
    4. 读取 libentry.so 的 .dynamic,发现 NEEDED libplacebo.so
    5. 找到文件 libplacebo.so
    6. 读取 libplacebo.so 的 .dynamic,发现 SONAME libplacebo.so.371
    7. 将该库注册为 libplacebo.so.371
    8. 继续读取 libplacebo.so 的 NEEDED,发现 NEEDED libc.so
    9. 加载 libc.so
    10. 所有依赖加载完成,解析符号,程序开始执行

    为什么要注意这个呢,因为他会和我们后面更改文件名有关。不然他会找不到库。

    运行

    接入 鸿蒙PC 进行真机运行。

    记得给 PC 开通 开发者模式USB调试功能。

    开发者模式是点击序列号7下,可以搜一下。USB调试功能是开启开发者模式后的一个在设置里面的选项。

    结果展示:

    可能遇到的错误

    找不到库文件

    原因:这个的本质在 理解 ELF 文件 —— .so 有讲到,也就是 检查 有讲过详细步骤。简而言之就是:找不到库是因为动态链接器根据 SONAME 查找库时,对应的库文件名不存在。所以我们要更改 SONAME 或更改库文件名字。

    解决:这里是直接更改文件名。

    如果改SONAME需要问AI找一个保险的方法,不然可能会出现机器码没改对导致 .so 文件发生错误,无法使用。更改前可以进行备份,也可以去linux系统重新复制一份过来。

    可以两个方法都用一下,用之前进行备份即可。


    总结

    本文完成了 OpenCV 在 HarmonyOS PC 上的交叉编译,并成功集成到 DevEco Studio。过程中介绍了 Lycium++ 的使用方法、HPKBUILD 的编写方式、OpenCV 模块裁剪、NAPI 调用流程,以及 ELF、SONAME 和动态链接机制。掌握这些内容后,不仅可以移植 OpenCV,也可以采用相同的方法移植 FFmpeg、libplacebo、SDL2 等其他第三方 C/C++ 库。

    Logo

    讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

    更多推荐