文件打印（C/C++）
===========

#### HarmonyOS提供的两种打印方式

[方式一](#步骤4-通过接口拉起系统打印预览界面下发任务)：应用通过接口拉起系统打印预览界面。适合没有实现打印预览能力的应用。

[方式二](#步骤5-通过打印接口直接下发打印任务)：应用通过接口指定打印文件和选项直接下发打印任务，适合已经实现打印预览能力的应用。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/65/v3/tPmIN3plT0yocZ2bQgMebg/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T132445Z&HW-CC-Expire=86400&HW-CC-Sign=613E10D54A460D7D52B00C0022CF0FAB80BB3FAEF879D1FC0FFF01792E6010F4)

使用打印服务，需[声明权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/declare-permissions)：ohos.permission.PRINT。

当不再使用打印服务时，调用OH\_Print\_Release()释放打印客户端资源并取消事件订阅。

c++接口需要在NDK工程中使用，请参考[NDK开发导读](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ndk-development-overview)。

#### \[h2\]步骤1. 引用NDK头文件

初始路径为entry/src/main/cpp/types/napi\_init.cpp # C++ 源码目录 NAPI 初始化入口（桥接 ArkTS 与 C++）。

#include "hilog/log.h"
#include "napi/native\_api.h"
#include "BasicServicesKit/ohprint.h"

#undef LOG\_TAG
#define LOG\_TAG "print c/c++"
#define LOGE(...) OH\_LOG\_ERROR(LOG\_APP, ##\_\_VA\_ARGS\_\_)
#define LOGI(...) OH\_LOG\_INFO(LOG\_APP, ##\_\_VA\_ARGS\_\_)

初始路径为entry/src/main/ets/pages/Index.ets # ArkTS 源码目录主页面。

import { hilog } from '@kit.PerformanceAnalysisKit';
import { testNapi } from 'libentry.so';
import { Context } from '@kit.AbilityKit';

class HiLog {
  static info(...args:string\[\]): void {
    hilog.info(0x0, "print c/c++ ", '%{public}s', \`${args.join(' ')}\`);
  }
}

#### \[h2\]步骤2. 在CMake脚本中添加动态链接库

初始路径为entry/src/main/cpp/types/CMakeLists.txt # C++ 源码目录 CMake 构建配置。

target\_link\_libraries(entry PUBLIC
    libace\_napi.z.so
    libhilog\_ndk.z.so
    libohprint.so
)

#### \[h2\]步骤3. 绑定页面和打印服务生命周期

建议将打印服务初始化和释放与使用系统打印能力的页面的生命周期绑定。

封装c++接口。

// napi\_init.cpp

static void PrinterDiscoveryCallback(Print\_DiscoveryEvent event, const Print\_PrinterInfo \*printerInfo)
{
    // 发现打印设备事件，以设备Id作为唯一标识符
    if (printerInfo == nullptr) {
        LOGE("printerInfo is nullptr");
        return;
    }
    // 开发者需根据具体事件实现相关逻辑
    switch (event) {
        // 探测到一台打印设备，可能会重复上报
        case PRINTER\_DISCOVERED:
            LOGI("do something, printer\[%{public}s\] discovered", printerInfo->printerId);
            break;
        // 打印设备从发现列表移除，仅上报一次
        case PRINTER\_LOST:
            LOGI("do something, printer\[%{public}s\] lost", printerInfo->printerId);
            break;
        // 打印设备开始连接，由OH\_Print\_ConnectPrinter触发
        case PRINTER\_CONNECTING:
            LOGI("do something, printer\[%{public}s\] on connecting", printerInfo->printerId);
            break;
        // 打印设备成功连接，由OH\_Print\_ConnectPrinter触发
        case PRINTER\_CONNECTED:
            LOGI("do something, printer\[%{public}s\] connected", printerInfo->printerId);
            break;
        default:
            break;
    }
}

static void PrinterChangeCallback(Print\_PrinterEvent event, const Print\_PrinterInfo \*printerInfo)
{
    // 以设备Id作为唯一标识符
    if (printerInfo == nullptr) {
        LOGE("printerInfo is nullptr");
        return;
    }
    // 开发者需根据具体事件实现相关逻辑
    switch (event) {
        // 打印设备新增到已添加设备列表
        case PRINTER\_ADDED:
            LOGI("do something, printer\[%{public}s\] added", printerInfo->printerId);
            break;
        // 打印设备从已添加设备列表移除
        case PRINTER\_DELETED:
            LOGI("do something, printer\[%{public}s\] deleted", printerInfo->printerId);
            break;
        // 打印设备状态变更
        case PRINTER\_STATE\_CHANGED:
            LOGI("do something, printer\[%{public}s\] state change to %{public}d",
                 printerInfo->printerId, printerInfo->printerState);
            break;
        // 打印设备基础属性变更
        case PRINTER\_INFO\_CHANGED:
            LOGI("do something, printer\[%{public}s\] info changed", printerInfo->printerId);
            break;
        default:
            break;
    }
}

static napi\_value NativeInit(napi\_env env, napi\_callback\_info info)
{
    // 初始化打印服务
    Print\_ErrorCode ret = OH\_Print\_Init();
    LOGI("nativeInit, ret = %{public}d", ret);
    napi\_value n\_ret = nullptr;
    napi\_get\_boolean(env, !ret, &n\_ret);
    if (ret == 0) {
        // 订阅已添加设备状态变更事件
        Print\_ErrorCode error = OH\_Print\_RegisterPrinterChangeListener(PrinterChangeCallback);
        LOGI("OH\_Print\_RegisterPrinterChangeListener, ret = %{public}d", error);
        // 订阅设备发现相关事件
        error = OH\_Print\_StartPrinterDiscovery(PrinterDiscoveryCallback);
        LOGI("OH\_Print\_StartPrinterDiscovery, ret = %{public}d", error);
    }
    return n\_ret;
}

static napi\_value NativeRelease(napi\_env env, napi\_callback\_info info)
{
    // 取消订阅已添加设备状态变更事件
    OH\_Print\_UnregisterPrinterChangeListener();
    // 取消订阅设备发现相关事件
    OH\_Print\_StopPrinterDiscovery();
    // 释放打印服务
    Print\_ErrorCode ret = OH\_Print\_Release();
    LOGI("nativeInit, ret = %{public}d", ret);
    napi\_value n\_ret = nullptr;
    napi\_get\_boolean(env, !ret, &n\_ret);
    return n\_ret;
}

// 添加napi接口声明
EXTERN\_C\_START
static napi\_value Init(napi\_env env, napi\_value exports)
{
    napi\_property\_descriptor desc\[\] = {
        { "nativeInit", nullptr, NativeInit, nullptr, nullptr, nullptr, napi\_default, nullptr },
        { "nativeRelease", nullptr, NativeRelease, nullptr, nullptr, nullptr, napi\_default, nullptr },
    };
    napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
    return exports;
}
EXTERN\_C\_END

应用侧在页面被拉起的生命周期初始化，在页面关掉时释放。

// Index.ets

@Entry
@Component
struct Index {
  // 页面展示到屏幕时，初始化打印服务
  aboutToAppear(): void {
    testNapi.nativeInit();
  }
  // 页面离开到屏幕时
  aboutToDisappear(): void {
    testNapi.nativeRelease();
  }
}

#### \[h2\]步骤4. 通过接口拉起系统打印预览界面下发任务

封装c++接口。

// napi\_init.cpp

// WriteFile 由开发者实现，示例仅为简单的文件拷贝。根据当前用户修改后的打印参数，若需要更新打印文件可重新写入系统提供的fd中
static uint32\_t WriteFile(uint32\_t fd, const Print\_PrintAttributes \*oldAttrs, const Print\_PrintAttributes \*newAttrs){
    // 沙箱内合法路径
    const char\* filePath = "/data/storage/el2/base/files/test.pdf";
    int32\_t fileFd = open(filePath, O\_RDONLY);
    if (fd == -1) {
        LOGE("open failed, errno=%{public}d", errno);
        return 1;
    }

    char buffer\[4096\];
    ssize\_t bytesRead = -1;;
    while ((bytesRead = read(fileFd, buffer, sizeof(buffer))) > 0) {
        if (write(fd, buffer, bytesRead) < bytesRead) {
            close(fileFd);
            return 1;
        }
    }
    close(fileFd);
    return 0;
}

// 系统打印预览界面回调，首次拉起或用户修改打印参数时的延迟文件写入回调。可以根据新参数适当修改打印文件
static void OnStartLayoutWriteCb(const char \*jobId,
                                uint32\_t fd,
                                const Print\_PrintAttributes \*oldAttrs,
                                const Print\_PrintAttributes \*newAttrs,
                                Print\_WriteResultCallback writeCallback)
{
    // 将数据写入系统提供的fd中，每次回调的fd不一定相同，请不要保存此fd
    uint32\_t retCode = WriteFile(fd, oldAttrs, newAttrs);
    // 通知打印系统文件写入完成，若需要异步写入数据，请保存好jobId
    // retCode取值：0-写入成功，1-写入异常，2-无需重新写入
    writeCallback(jobId, retCode);
}

// 打印文件写入完成后，系统打印预览界面会进行预览，此时用户可以点击“开始打印”下发任务
// 任务ID对应的打印状态变化的回调函数
static void OnJobStateChangedCb(const char \*jobId, uint32\_t state)
{
    // jobState取值：0-任务准备中，1-任务排队中， 2-任务打印中， 3-任务异常暂停， 4-任务结束， 100-任务未知异常
    LOGI("dosomething with OnJobStateChangedCb, jobId: %{public}s, jobState: %{public}u", jobId, state);
}

// 下发打印任务
static napi\_value NativeStartPrintByNative(napi\_env env, napi\_callback\_info info) {
    napi\_value n\_ret = nullptr;
    void \*context = nullptr;
    size\_t argc = 1;
    napi\_value argv\[1\] = {nullptr};
    // 假设 napi\_get\_cb\_info 和 napi\_unwrap 均正常返回
    napi\_get\_cb\_info(env, info, &argc, argv, nullptr, nullptr);
    napi\_unwrap(env, argv\[0\], &context);

    // 调用打印接口以拉起系统打印预览界面
    std::string printJobName = "test";
    Print\_PrintDocCallback printDocCallback = { OnStartLayoutWriteCb, OnJobStateChangedCb };
    Print\_ErrorCode ret = OH\_Print\_StartPrintByNative(printJobName.c\_str(), printDocCallback, context);
    napi\_get\_boolean(env, !ret, &n\_ret);
    return n\_ret;
}

// 添加napi接口声明
EXTERN\_C\_START
static napi\_value Init(napi\_env env, napi\_value exports)
{
    napi\_property\_descriptor desc\[\] = {
        { "nativeStartPrintByNative", nullptr, NativeStartPrintByNative, nullptr, nullptr, nullptr, napi\_default, nullptr },
    };
    napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
    return exports;
}
EXTERN\_C\_END

主页上新增一个按钮，单击调用c++的nativeStartPrintByNative接口拉起打印预览界面。

// Index.ets

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button('OH\_Print\_StartPrintByNative')
          .onClick(() => {
            HiLog.info("OH\_Print\_StartPrintByNative onClick");
            let ctx: Context | undefined = this.getUIContext().getHostContext();
            let ret: boolean= testNapi.nativeStartPrintByNative(ctx);
            HiLog.info(\`nativeStartPrintByNative ret: ${JSON.stringify(ret)}\`);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

#### \[h2\]步骤5. 通过打印接口直接下发打印任务

封装c++接口，示例仅演示从已添加打印设备列表获取信息并下发任务。

// napi\_init.cpp

// 下发打印任务
static napi\_value NativeStartPrintJob(napi\_env env, napi\_callback\_info info) {
    napi\_value n\_ret = nullptr;
    napi\_get\_boolean(env, false, &n\_ret);
    Print\_ErrorCode ret = PRINT\_ERROR\_INVALID\_PARAMETER;

    // 获取已添加打印机的列表
    Print\_StringList pList = { 0 };
    ret = OH\_Print\_QueryPrinterList(&pList);
    LOGI("OH\_Print\_QueryPrinterList ret = %{public}d", ret);
    if (ret != PRINT\_ERROR\_NONE) {
        return n\_ret;
    }
    LOGI("pList->count: %{public}d", pList.count);
    if (pList.count <= 0 || (!pList.list)) {
        return n\_ret;
    }
    // 打印列表中所有的打印机Id
    for (uint32\_t index = 0; index < pList.count; index++) {
        LOGI("pList->list\[%{public}d\]： %{public}s", index, pList.list\[index\]);
    }

    // 获取列表中第一台打印机属性
    const char \*printerId = pList.list\[0\];
    Print\_PrinterInfo \*printerInfo = nullptr;
    ret = OH\_Print\_QueryPrinterInfo(printerId, &printerInfo);
    if (ret != PRINT\_ERROR\_NONE) {
        return n\_ret;
    }
    // 打开要打印的文件，可以有多个，沙箱内合法路径
    const char\* filePath = "/data/storage/el2/base/files/test.pdf";
    int32\_t fd = open(filePath, O\_RDONLY);
    if (fd == -1) {
        LOGE("open failed, errno=%{public}d", errno);
        ret = PRINT\_ERROR\_INVALID\_PARAMETER;
        return n\_ret;
    }
    std::vector<uint32\_t> fdList = { static\_cast<uint32\_t>(fd) };
    // 本例子使用首选项 printerInfo->defaultValue 作为打印任务参数来下发任务
    Print\_PrintJob\* printJob = new Print\_PrintJob{ "jobName",
                                                   fdList.data(),
                                                   static\_cast<uint32\_t>(fdList.size()),
                                                   printerInfo->printerId,
                                                   1, // 打印份数
                                                   printerInfo->defaultValue.defaultPaperSource,
                                                   printerInfo->defaultValue.defaultMediaType,
                                                   printerInfo->defaultValue.defaultPageSizeId,
                                                   printerInfo->defaultValue.defaultColorMode,
                                                   printerInfo->defaultValue.defaultDuplexMode,
                                                   printerInfo->defaultValue.defaultResolution,
                                                   printerInfo->defaultValue.defaultMargin,
                                                   true,
                                                   printerInfo->defaultValue.defaultOrientation,
                                                   printerInfo->defaultValue.defaultPrintQuality,
                                                   DOCUMENT\_FORMAT\_PDF,
                                                   printerInfo->defaultValue.otherDefaultValues, };
    ret = OH\_Print\_StartPrintJob(printJob);
    close(fd);
    // 使用完打印机属性和添加列表后需要及时释放
    OH\_Print\_ReleasePrinterInfo(printerInfo);
    OH\_Print\_ReleasePrinterList(&pList);

    napi\_get\_boolean(env, !ret, &n\_ret);
    return n\_ret;
}

// 添加napi接口声明
EXTERN\_C\_START
static napi\_value Init(napi\_env env, napi\_value exports)
{
    napi\_property\_descriptor desc\[\] = {
        { "nativeStartPrintJob", nullptr, NativeStartPrintJob, nullptr, nullptr, nullptr, napi\_default, nullptr },
    };
    napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
    return exports;
}
EXTERN\_C\_END

主页上新增一个按钮，单击调用c++的nativeStartPrintJob直接发送任务。

// Index.ets

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button('OH\_Print\_StartPrintJob')
          .onClick(() => {
            HiLog.info("OH\_Print\_StartPrintJob onClick");
            let ret: boolean = testNapi.nativeStartPrintJob();
            HiLog.info(\`OH\_Print\_StartPrintJob ret: ${JSON.stringify(ret)}\`);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}