# HnpProcesses **Repository Path**: scenario-samples/hnp-processes ## Basic Information - **Project Name**: HnpProcesses - **Description**: 本项目为HarmonyOS PC中使用Qt实现集成hnp包。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-12-18 - **Last Updated**: 2026-04-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HarmonyOS平台Qt项目与hnp子进程交互示例 ## 场景介绍 “[HarmonyOS PC中使用Qt实现集成hnp包](https://gitee.com/scenario-samples/hnp-processes)”是HarmonyOS PC的Qt项目高频使用场景之一,如实现进程间通信等。 本示例主要基于Native软件包的集成与使用实现Qt主进程与hnp子进程通信功能。 ## 效果预览 - 主进程运行显示: - 子进程运行显示: - 日志文件: ## 实现思路 **一、构建hnp软件包工程。** 1. 编译Native可执行二进制。 打开Native C++工程,并在cpp文件下实现自定义功能。例如,本样例中实现Qt主进程与hnp子进程通信功能。主进程从1开始每隔2s发送数字,子进程收到主进程的数字后进行平方运算,并将结果返回给主进程。 - main.cpp代码如下: ```C++ #include #include #include #include #include #include int main(int argc, char *argv[]) { std::cout << "=== HNP子进程开始执行 ===" << std::endl; if (argc != 3) { std::cerr << "错误:需要两个管道文件描述符作为参数" << std::endl; return 1; } // 获取管道文件描述符 int pipe_read_fd = std::atoi(argv[1]); // 从主进程读取数字 int pipe_write_fd = std::atoi(argv[2]); // 向主进程写入结果 std::cout << "读管道fd: " << pipe_read_fd << std::endl; std::cout << "写管道fd: " << pipe_write_fd << std::endl; if (pipe_read_fd <= 0 || pipe_write_fd <= 0) { std::cerr << "错误:无效的管道文件描述符" << std::endl; return 1; } std::cout << "开始处理数据..." << std::endl; while (true) { int number = 0; ssize_t bytes_read = read(pipe_read_fd, &number, sizeof(number)); if (bytes_read == sizeof(number)) { std::cout << "收到数字: " << number << std::endl; int result = number * number; std::cout << "计算平方: " << number << " -> " << result << std::endl; // 将结果写回主进程 ssize_t bytes_written = write(pipe_write_fd, &result, sizeof(result)); if (bytes_written == sizeof(result)) { std::cout << "成功发送平方结果: " << result << std::endl; } else { std::cerr << "发送结果失败,写入字节数: " << bytes_written << std::endl; } } else if (bytes_read == 0) { std::cout << "读管道关闭,退出子进程" << std::endl; break; } else if (bytes_read < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { usleep(100000); continue; } else { std::cerr << "读取管道错误: " << strerror(errno) << std::endl; break; } } else { std::cerr << "读取数据大小不正确: " << bytes_read << std::endl; break; } } ::close(pipe_read_fd); ::close(pipe_write_fd); return 0; } ``` - CMakeLists.txt代码如下: ```cmake set(SOURCES main.cpp ) # 生成可执行文件(而非共享库) add_executable(MyQt ${SOURCES}) # 链接必要的库(根据实际需求调整) target_link_libraries(MyQt PUBLIC libace_napi.z.so) ``` - 点击Build->Build Hap(s)进行编译。 2. 将可执行二进制放入工程,软件包目录通常包含以下内容: ```shell 根目录 ├─bin:可执行二进制存放路径; ├─cfg:配置文件存放路径,可选; ├─lib:依赖库存放路径,可选; └─hnp.json:hnp配置文件。 ``` - hnp.json为hnp打包配置文件,需要放在软件包的外层目录,只能命名为hnp.json,内容要满足json文件的格式要求,该文件支持对可执行二进制进行软链接配置,具体的配置格式如下: ```json { "type":"hnp-config", #固定标识符“hnp-config” "name":"xxx", #Native软件名 "version":"1.1", #版本号 "install":{ "links":[ #软链接配置信息 { "source":"xxxxxx", "target":"xxxxxx" } ] } } ``` - 本样例中,hnpNew软件包目录如下: - 其中,配置文件hnp.json内容如下: ```json { "type":"hnp-config", "name":"MyQt", "version":"1.1", "install":{ "links":[ { "source":"/bin/MyQt", "target":"MyQt" } ] } } ``` **二、Native软件包打包。** 1. OpenHarmony SDK中提供了hnpcli工具,用户通过hnpcli命令进行打包。hnpcli打包命令有两种使用方式。 - 一种是软件包里有hnp.json的打包命令: ```shell hnpcli pack -i [待打包路径] < -o [输出路径] > 注: 1. 如果没有指定-o,则输出路径为当前目录。 2. 打包路径下有hnp.json配置文件,使用配置文件名里的软件名和版本号作为打包参数。 ``` - 另一种是软件包里没有hnp.json的打包方式: ```shell hnpcli pack -i [待打包路径] < -o [输出路径] > -n [软件名] -v [版本号] 注: 打包路径下没有hnp.json配置文件,则需要用户传入-n和-v参数,否则打包失败(打包软件会根据入参在压缩文件根目录中主动生成hnp.json)。 ``` - 本样例中,对MyApp软件进行打包,由于hnpNew目录下存在hnp.json文件,不需要指定软件名和版本号。因此命令如下: ```shell hnpcli pack -i ./hnpNew -o . ``` 2. 打包成功后会在输出路径下生成"[Native软件名].hnp"的文件。运行结果如下: 生成的产物如下: **三、hnp的集成。** 1. 打开需要使用hnp的工程。例如,本样例中实现Qt主进程从1开始每隔2s给子进程发送数字,子进程收到后进行平方运算,并将结果返回给主进程。 - mainwindow.cpp代码如下: ```C++ namespace { // HNP路径配置 std::string hnp_path("/data/service/hnp/MyQt.org/MyQt_1.1/bin/MyQt"); // 日志文件目录 std::string write_path("/data/storage/el2/base/haps/entry/files/"); } MainWindow::MainWindow(QWidget *parent) : QWidget(parent), numberInput(nullptr), resultDisplay(nullptr), timer(nullptr), counter(1), child_pid(-1) { // 初始化管道 pipe_to_child[0] = pipe_to_child[1] = -1; pipe_from_child[0] = pipe_from_child[1] = -1; setupUI(); setupConnections(); // 确保日志目录存在 QDir dir(QString::fromStdString(write_path)); if (!dir.exists()) { dir.mkpath("."); } } MainWindow::~MainWindow() { stopProcess(); } void MainWindow::setupUI() { QPushButton *startBtn = new QPushButton("开始", this); QPushButton *stopBtn = new QPushButton("结束", this); numberInput = new QLineEdit("1", this); resultDisplay = new QTextEdit(this); numberInput->setReadOnly(true); resultDisplay->setReadOnly(true); // 布局 QHBoxLayout *buttonLayout = new QHBoxLayout(); buttonLayout->addWidget(startBtn); buttonLayout->addWidget(stopBtn); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(new QLabel("当前数字:", this)); mainLayout->addWidget(numberInput); mainLayout->addLayout(buttonLayout); mainLayout->addWidget(new QLabel("计算结果:", this)); mainLayout->addWidget(resultDisplay); setLayout(mainLayout); setWindowTitle("Qt项目与hnp子进程交互示例"); resize(600, 800); connect(startBtn, &QPushButton::clicked, this, &MainWindow::startProcess); connect(stopBtn, &QPushButton::clicked, this, &MainWindow::stopProcess); } void MainWindow::setupConnections() { timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MainWindow::sendNumber); // 设置定时器检查管道数据 QTimer *pipeCheckTimer = new QTimer(this); connect(pipeCheckTimer, &QTimer::timeout, this, &MainWindow::checkPipeData); pipeCheckTimer->start(100); } void MainWindow::startProcess() { if (child_pid != -1) { resultDisplay->append("子进程已在运行,请先停止"); return; } // 重置计数器为1 counter = 1; numberInput->setText("1"); resultDisplay->clear(); if (startHNPProcess()) { timer->start(2000); resultDisplay->append("HNP子进程已启动,开始发送数据..."); } else { resultDisplay->append("启动HNP子进程失败"); } } void MainWindow::stopProcess() { if (timer) { timer->stop(); } if (child_pid != -1) { // 发送终止信号给子进程 kill(child_pid, SIGTERM); // 关闭管道 if (pipe_to_child[0] != -1) ::close(pipe_to_child[0]); if (pipe_to_child[1] != -1) ::close(pipe_to_child[1]); if (pipe_from_child[0] != -1) ::close(pipe_from_child[0]); if (pipe_from_child[1] != -1) ::close(pipe_from_child[1]); pipe_to_child[0] = pipe_to_child[1] = -1; pipe_from_child[0] = pipe_from_child[1] = -1; // 等待子进程退出 int status; waitpid(child_pid, &status, 0); child_pid = -1; resultDisplay->append("HNP子进程已停止"); // 重置计数器为1,确保下次从1开始 counter = 1; numberInput->setText("1"); } else { counter = 1; numberInput->setText("1"); resultDisplay->append("已重置计数器"); } } void MainWindow::sendNumber() { if (child_pid == -1 || pipe_to_child[1] == -1) { timer->stop(); return; } int number = counter++; numberInput->setText(QString::number(number)); // 通过管道发送数字给子进程 if (write(pipe_to_child[1], &number, sizeof(number)) == sizeof(number)) { resultDisplay->append("“主进程”发送数字: " + QString::number(number)); qDebug() << "[Main] 发送数字:" << number; } else { resultDisplay->append("发送数字失败"); qDebug() << "[Main] 发送数字失败:" << strerror(errno); } } void MainWindow::checkPipeData() { if (child_pid == -1 || pipe_from_child[0] == -1) { return; } // 检查管道是否有数据可读 int result = -1; ssize_t bytes_read = read(pipe_from_child[0], &result, sizeof(result)); if (bytes_read == sizeof(result)) { resultDisplay->append("收到“子进程”计算平方结果:" + QString::number(result)); qDebug() << "[Main] 收到平方结果:" << result; } else if (bytes_read == 0) { qDebug() << "[Main] 管道关闭,子进程可能已退出"; stopProcess(); } else if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { qDebug() << "[Main] 读取管道错误:" << strerror(errno); } } bool MainWindow::startHNPProcess() { qDebug() << "[Main] ========= 启动HNP子进程 ========="; if (pipe(pipe_to_child) == -1 || pipe(pipe_from_child) == -1) { qDebug() << "[Main] 创建管道失败:" << strerror(errno); return false; } // 设置管道非阻塞 fcntl(pipe_to_child[0], F_SETFL, O_NONBLOCK); fcntl(pipe_to_child[1], F_SETFL, O_NONBLOCK); fcntl(pipe_from_child[0], F_SETFL, O_NONBLOCK); fcntl(pipe_from_child[1], F_SETFL, O_NONBLOCK); // 先检查HNP文件是否存在 if (!QFile::exists(QString(hnp_path.c_str()))) { qDebug() << "[Main] HNP文件不存在:" << QString(hnp_path.c_str()); ::close(pipe_to_child[0]); ::close(pipe_to_child[1]); ::close(pipe_from_child[0]); ::close(pipe_from_child[1]); return false; } pid_t pid = fork(); qDebug() << "[Main] fork returned" << pid << "errno=" << errno; if (pid == 0) { ::close(pipe_to_child[1]); // 关闭主进程的写端 ::close(pipe_from_child[0]); // 关闭主进程的读端 // 设置文件权限 ::chmod(hnp_path.c_str(), 0777); // 检查执行权限 if (access(hnp_path.c_str(), X_OK) != 0) { int log_fd = open((write_path + "child_process.log").c_str(), O_CREAT | O_WRONLY | O_APPEND, 0644); if (log_fd != -1) { std::string error_msg = "[Child] 没有执行权限: " + std::string(strerror(errno)) + "\n"; write(log_fd, error_msg.c_str(), error_msg.length()); ::close(log_fd); } ::close(pipe_to_child[0]); ::close(pipe_from_child[1]); _exit(126); } std::string log_file_path = write_path + "child_process.log"; int log_fd = open(log_file_path.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0644); if (log_fd != -1) { dup2(log_fd, STDOUT_FILENO); dup2(log_fd, STDERR_FILENO); ::close(log_fd); } std::string pipe_read_arg = std::to_string(pipe_to_child[0]); // 子进程读 std::string pipe_write_arg = std::to_string(pipe_from_child[1]); // 子进程写 execl(hnp_path.c_str(), "MyQt", pipe_read_arg.c_str(), pipe_write_arg.c_str(), nullptr); ::close(pipe_to_child[0]); ::close(pipe_from_child[1]); _exit(127); } else if (pid > 0) { child_pid = pid; // 关闭不需要的管道端 ::close(pipe_to_child[0]); // 关闭子进程的读端 ::close(pipe_from_child[1]); // 关闭子进程的写端 qDebug() << "[Main] 子进程PID:" << pid; qDebug() << "[Main] 父进程写管道:" << pipe_to_child[1] << "父进程读管道:" << pipe_from_child[0]; return true; } else { qDebug() << "[Main] fork失败:" << strerror(errno); ::close(pipe_to_child[0]); ::close(pipe_to_child[1]); ::close(pipe_from_child[0]); ::close(pipe_from_child[1]); return false; } } int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow window; window.show(); return app.exec(); } ``` 2. 在使用hnp的工程根目录下新增hnp目录,hnp目录下根据设备的操作系统ABI名称(例如arm64-v8a)创建文件夹,文件目录格式如下: ```txt 工程根目录 └─hnp #HAP包中新增hnp根目录 └─arm64-v8a #(ABI) └─MyQt.hnp ``` 3. 修改hap工程根目录下entry/src/main/module.json5文件,配置hnp包信息,需要在"module"字段下增加以下字段。"package"字段指定hnp包在ABI文件夹下相对路径,"type"指定hnp包类型,包含公有("public")和私有("private")类型。 ```json "hnpPackages":[ { "package": "MyQt.hnp", "type": "public" // 或"private" } ] ``` 4. 打包方式。 DevEco Studio项目中配置了hnp包相关内容,生成的hap包中也没有hnp包,这时需要对编译器的脚本做微调。 修改步骤: - 右键点击桌面DevEco图标,打开IDE安装路径。 - 编辑packing-tool-options.js文件。 路径:DevEco Studio\tools\hvigor\hvigor-ohos-plugin\src\builder\inner-java-command-builder\packing-tool-options.js。 在文件最下面,添加以下代码: ```js addHnpPath(t) { return this.addFieldAndPath("--hnp-path", t); } ``` - 编辑base-pack-hap-task.js文件。 路径:DevEco Studio\tools\hvigor\hvigor-ohos-plugin\src\tasks\base\base-pack-hap-task.js。 在generateCommand函数中,添加以下代码: ```js let hnpPath = path_1.default.resolve(process.cwd(),'hnp'); if (fse.existsSync(hnpPath)) { a.addHnpPath(hnpPath); } ``` - 两个文件修改后,重启DevEco Studio。再使用DevEco Studio的Build->Rebuild Project功能,即可编译带hnp的hap包: - 打开entry/build/default/outputs/default/entry-default-signed.hap文件,检查hnp包是否正确打进hap中: **四、hnp的使用。** 1. 使用DevEco Studio的运行/调试功能运行程序。 - 打印字符串功能进行显示。 ```C #include int main() { printf("Hello, Qt\n"); return 0; } ``` - public模式下的hnp包运行结果如下: 2. 进程间通信项目运行结果: 见上方效果预览。 ## 说明 使用该示例需要将`libs\arm64-v8a`下的`libplugins_platforms_qopenharmony.so`文件替换为本地Qt安装路径`\plugins\platforms`目录下的`libplugins_platforms_qopenharmony.so`。 将开源版Qt安装路径配置到`QT_PREFIX`中,可以选择下面两个方案中的一个: - 修改`entry\src\main\cpp\qtModule\CMakeLists.txt`中的`set(QT_PREFIX "D:\\QtArea\\Code\\1105\\qt5.15.12_arm64_release")`为编译出的Qt产物目录: ```txt # 检查环境变量是否存在 if(DEFINED ENV{WORKSPACE}) set(QT_PREFIX $ENV{WORKSPACE}/code/SDK/Qt/QtOpenSDK) message(STATUS "get QT_PREFIX PATH: ${QT_PREFIX}") else() set(QT_PREFIX "D:\\QtArea\\Code\\1105\\qt5.15.12_arm64_release") message(STATUS "WORKSPACE not defined, using default path: ${QT_PREFIX}") endif() ``` - 或者删除`entry\src\main\cpp\qtModule\CMakeLists.txt`中的`QT_PREFIX`定义,在`entry\build-profile.json5`中设置QT_PREFIX: ```json "buildOption": { "externalNativeOptions": { "path": "./src/main/cpp/CMakeLists.txt", "arguments": "-DQT_PREFIX=D:\QtArea\Code\1105\qt5.15.12_arm64_release", "cppFlags": "", "abiFilters": ["arm64-v8a"] } }, ``` ## 约束与限制 * 本示例支持API Version 20 Release及以上版本。 * 本示例支持HarmonyOS 6.0.0 Release SDK及以上版本。 * 本示例需要使用DevEco Studio 6.0.0 Release及以上版本进行编译运行。 ## 工程目录 ``` nativeChild ├─entry │ └─src │ ├─main │ │ ├─cpp │ │ │ ├─CMakeLists.txt // Native项目CMakeList文件 │ │ │ └─main.cpp // 定义Native包功能 │ │ ├─ets │ │ │ ├─entryability // 应用入口 │ │ │ │ └─EntryAbility.ets │ │ │ ├─entrybackupability │ │ │ │ └─EntryBackupAbility.ets │ │ │ └─pages │ │ │ └─Index.ets // 主页面实现 │ │ ├─resources // 应用资源目录 │ │ └─module.json5 // 模块级核心配置文件 │ ├─build-profile.json5 // 工程结构/构建/定制化配置信息 │ └─oh-package.json5 // 描述全局配置 └─hvigor // 构建环境配置 mainProcess ├─entry │ ├─libs/arm64-v8a │ │ └─libplugins_platforms_qopenharmony.so // Qt安装目录'plugins\platforms'HarmonyOS平台插件so │ └─src │ ├─main │ │ ├─cpp │ │ │ ├─CMakeLists.txt // Qt项目CMakeList文件 │ │ │ ├─mainwindow.h // Qt主窗口头文件 │ │ │ └─mainwindow.cpp // Qt主程序功能 │ │ ├─ets │ │ │ ├─abilitystage │ │ │ │ └─MyAbilityStage.ets │ │ │ ├─entryability │ │ │ │ └─EntryAbility.ets // 应用入口 │ │ │ │ └─EntryEmbeddedAbility.ets │ │ │ └─pages │ │ │ └─Index.ets // 主页面实现 │ │ ├─resources // 应用资源目录 │ │ └─module.json5 // 模块级核心配置文件 │ ├─build-profile.json5 // 工程结构/构建/定制化配置信息 │ └─oh-package.json5 // 描述全局配置 ├─hnp │ └─arm64-v8a │ └─MyQt.hnp // hnp包 └─hvigor // 构建环境配置 ``` ## 模块依赖 [开源版Qt](https://gitcode.com/qtforohos/Build) [QTimer](https://doc.qt.io/archives/qt-5.15/qtimer.html) [QFile](https://doc.qt.io/archives/qt-5.15/qfile.html) ## 参考文档 [Native软件包开发](https://gitee.com/openharmony/startup_appspawn/tree/master/service/hnp) [Native软件打hnp包开发](https://gitee.com/openharmony/startup_appspawn/blob/master/service/hnp/pack/README_zh.md)