客户端应用开发
=======

#### 场景介绍

[OH\_ContentEmbed](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-contentembed)内容嵌入模块提供对象编辑框架与技术，支持应用间文档嵌入与协同编辑。

OE客户端应用指嵌入其它文档的应用，通过调用[OE框架层接口](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-content-embed-proxy-h)实现嵌入外部文档、展示文档快照，以及按需启动OE服务端应用编辑文档的功能。

典型应用场景包括：

*   在CAD文档中嵌入Excel表格，通过点击嵌入对象启动Excel应用进行编辑。
*   在文档编辑器中嵌入图片、表格等多种格式的文档。
*   在笔记应用中嵌入其他应用的文档，实现跨应用协作。

#### 约束限制

在使用接口前，需先确认设备具备SystemCapability.ContentEmbed.ObjectEditor系统能力，判断方式请参阅[查询指定的系统能力是否被支持](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/init#caniuse)。并申请ohos.permission.CONNECT\_OBJECTEDITOR\_EXTENSION权限，配置方式请参阅[声明权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/declare-permissions)。

#### 接口说明

常用接口如下表所示。更多API说明请参考[OH\_ContentEmbed](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-contentembed)。

**表1** 客户端主要接口

| 接口名称 | 功能描述 |
| :-- | :-- |
| OH\_ContentEmbed\_CreateDocumentByFile | 通过被嵌入文档路径创建[OE文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/content-embed-kit-terminology#oe文档)。 |
| OH\_ContentEmbed\_CreateDocumentByOEId | 通过[OEID](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/content-embed-kit-terminology#oeid)创建OE文档。 |
| OH\_ContentEmbed\_LoadDocumentFromFile | 通过已存在的[OE格式文件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/content-embed-kit-terminology#oe格式文件)加载OE文档。 |
| OH\_ContentEmbed\_CreateExtensionProxy | 创建[客户端OE对象](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/content-embed-kit-terminology#客户端oe对象)。 |
| OH\_ContentEmbed\_DestroyExtensionProxy | 销毁客户端OE对象，释放相关资源。 |
| OH\_ContentEmbed\_Proxy\_RegisterOnUpdateFunc | 向客户端OE对象注册OE文档更新时的回调函数。 |
| OH\_ContentEmbed\_Proxy\_RegisterOnErrorFunc | 向客户端OE对象注册OE文档触发错误时的回调函数。 |
| OH\_ContentEmbed\_Proxy\_RegisterOnEditingFinishedFunc | 向客户端OE对象注册OE文档编辑完成时的回调函数。 |
| OH\_ContentEmbed\_Proxy\_RegisterOnExtensionStoppedFunc | 向客户端OE对象注册[OE Extension](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/content-embed-kit-terminology#oe-extension)停止时的回调函数。 |
| OH\_ContentEmbed\_Proxy\_StartWork | 客户端通过客户端OE对象与Object Editor服务跨进程通信，启动OE Extension组件，并创建服务端OE对象。 |
| OH\_ContentEmbed\_Proxy\_StopWork | 客户端通过客户端OE对象与Object Editor服务跨进程通信，销毁服务端OE对象，释放资源。 |
| OH\_ContentEmbed\_Proxy\_GetSnapshot | 从客户端OE对象获取当前OE文档的快照图像，用于预览或缩略图显示。 |
| OH\_ContentEmbed\_Proxy\_DoEdit | 客户端通过客户端OE对象与OE Extension组件跨进程通信，通知服务端启动UIAbility编辑OE文档。 |
| OH\_ContentEmbed\_Proxy\_GetDocument | 从客户端OE对象获取其关联的OE文档对象。 |
| OH\_ContentEmbed\_Document\_Read | 从OE文档中读取OE格式文件数据，存入buffer。 |
| OH\_ContentEmbed\_DestroyDocument | 销毁OE文档对象，释放资源。 |

#### 开发步骤

以下演示使用Native API开发OE客户端应用的完整流程。

#### \[h2\]添加动态链接库

CMakeLists.txt中添加以下lib。

\# content embed
libcontent\_embed\_ndk.so
# hilog
libhilog\_ndk.z.so
# ace
libace\_napi.z.so
# piexlmap
libpixelmap.so

#### \[h2\]引用头文件

#include <string>
#include <ContentEmbedKit/content\_embed/content\_embed\_common.h>
#include <ContentEmbedKit/content\_embed/content\_embed\_document.h>
#include <ContentEmbedKit/content\_embed/content\_embed\_proxy.h>
#include <hilog/log.h>
#include <multimedia/image\_framework/image/pixelmap\_native.h>

#### \[h2\]查询当前设备支持的可嵌入文档信息

查询当前设备在指定语言下支持的所有可嵌入文档的信息。

void QueryAllFormat(const std::string &locale)
{
    ContentEmbed\_Info\* contentEmbedInfo = nullptr;
    // 创建ContentEmbedInfo
    ContentEmbed\_ErrorCode errCode = OH\_ContentEmbed\_CreateContentEmbedInfo(&contentEmbedInfo);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_CreateContentEmbedInfo failed, errCode: %{public}d.", errCode);
        return;
    }
    // 获取locale语言的ContentEmbedInfo信息
    errCode = OH\_ContentEmbed\_GetContentEmbedInfo(locale.c\_str(), contentEmbedInfo);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetContentEmbedInfo failed, errCode: %{public}d.", errCode);
        // 查询失败销毁ContentEmbedInfo对象
        OH\_ContentEmbed\_DestroyContentEmbedInfo(contentEmbedInfo);
        return;
    }
    uint32\_t count = 0;
    // 获取ContentEmbedInfo对象Format对象数量
    errCode = OH\_ContentEmbed\_GetFormatCountFromInfo(contentEmbedInfo, &count);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetFormatCountFromInfo failed, errCode: %{public}d.", errCode);
        // 查询失败销毁ContentEmbedInfo对象
        OH\_ContentEmbed\_DestroyContentEmbedInfo(contentEmbedInfo);
        return;
    }
    for (int i = 0; i < count; i++) {
        ContentEmbed\_Format\* ceFormat = nullptr;
        // 从ContentEmbedInfo对象获取索引为i的ContentEmbed\_Format信息
        errCode = OH\_ContentEmbed\_GetFormatFromInfo(contentEmbedInfo, i, &ceFormat);
        if (errCode != CE\_ERR\_OK) {
            OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetFormatFromInfo failed, errCode: %{public}d.", errCode);
            continue;
        }
        char oeid\[MAX\_OEID\_LENGTH\];
        // 获取OEID
        errCode = OH\_ContentEmbed\_GetOEidFromFormat(ceFormat, oeid);
        if (errCode != CE\_ERR\_OK) {
            OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetOEidFromFormat failed, errCode: %{public}d.", errCode);
            continue;
        }
        char name\[MAX\_NAME\_LENGTH\];
        char description\[MAX\_DESCRIPTION\_LENGTH\];
        // 获取名称和描述
        errCode = OH\_ContentEmbed\_GetNameAndDescriptionFromFormat(ceFormat, name, description);
        if (errCode != CE\_ERR\_OK) {
            OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetNameAndDescriptionFromFormat failed, errCode: %{public}d.", errCode);
            continue;
        }
        OH\_PixelmapNative\* pixelMap = nullptr;
        // 获取图标
        errCode = OH\_ContentEmbed\_GetIconFromFormat(ceFormat, &pixelMap);
        if (errCode != CE\_ERR\_OK) {
            OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetIconFromFormat failed, errCode: %{public}d.", errCode);
            continue;
        }
        unsigned int count = 0;
        // 获取文件后缀
        char \*\*fileNameExtensions = OH\_ContentEmbed\_GetFileNameExtensionsFromFormat(ceFormat, &count);
        if (fileNameExtensions == nullptr) {
            OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetFileNameExtensionsFromFormat failed");
            continue;
        }
    }
    // 销毁ContentEmbedInfo对象
    OH\_ContentEmbed\_DestroyContentEmbedInfo(contentEmbedInfo);
}

#### \[h2\]查询OEID对应的格式信息

void QueryFormatByOEid(const std::string &oeid, const std::string &locale)
{
    ContentEmbed\_Format\* ceFormat = nullptr;
    // 创建ContentEmbed\_Format
    ContentEmbed\_ErrorCode errCode = OH\_ContentEmbed\_CreateContentEmbedFormat(&ceFormat);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_CreateContentEmbedFormat failed, errCode: %{public}d.", errCode);
        return;
    }
    errCode = OH\_ContentEmbed\_GetContentEmbedFormatByOEidAndLocale(oeid.c\_str(), locale.c\_str(), ceFormat);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetContentEmbedFormatByOEidAndLocale failed, errCode: %{public}d.", errCode);
        // 查询失败销毁ContentEmbed\_Format对象
        OH\_ContentEmbed\_DestroyContentEmbedFormat(ceFormat);
    }
    char name\[MAX\_NAME\_LENGTH\];
    char description\[MAX\_DESCRIPTION\_LENGTH\];
    // 获取名称和描述
    errCode = OH\_ContentEmbed\_GetNameAndDescriptionFromFormat(ceFormat, name, description);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetNameAndDescriptionFromFormat failed, errCode: %{public}d.", errCode);
        return;
    }
    OH\_PixelmapNative\* pixelMap = nullptr;
    // 获取图标
    errCode = OH\_ContentEmbed\_GetIconFromFormat(ceFormat, &pixelMap);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetIconFromFormat failed, errCode: %{public}d.", errCode);
        return;
    }
    unsigned int count = 0;
    // 获取文件后缀
    char \*\*fileNameExtensions = OH\_ContentEmbed\_GetFileNameExtensionsFromFormat(ceFormat, &count);
    if (fileNameExtensions == nullptr) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_GetFileNameExtensionsFromFormat failed");
        return;
    }
}

#### \[h2\]创建OE文档

1.  基于OEID创建OE文档
    
    void CreateDocumentByOEid(const std::string &oeid)
    {
        ContentEmbed\_Document\* ceDocument = nullptr;
        // 基于OEID创建OE文件
        ContentEmbed\_ErrorCode errCode = OH\_ContentEmbed\_CreateDocumentByOEid(oeid.c\_str(), &ceDocument);
    }
    
2.  基于文件创建OE文档
    
    void CreateDocumentByFile(std::string filePath)
    {
        // 基于文件创建OE文档
        ContentEmbed\_Document \*ceDocument = nullptr;
        // 是否以链接形式创建OE文档，当isLinking为true时，表示以链接方式创建OE文档；当isLinking为false时，表示以嵌入方式创建OE文档。
        bool isLinking = false;
        ContentEmbed\_ErrorCode ret = OH\_ContentEmbed\_CreateDocumentByFile(filePath.c\_str(), filePath.size(), isLinking, &ceDocument);
    }
    
3.  基于[OE格式文件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/content-embed-kit-terminology#oe格式文件)加载OE文档，当OE格式文件为数据流存储时需先落盘至本地再使用此接口。
    
    void LoadDocumentFromFile(std::string oeFormatFilePath)
    {
        ContentEmbed\_Document \*ceDocument = nullptr;
        // 基于OE格式文件
        ContentEmbed\_ErrorCode ret = OH\_ContentEmbed\_LoadDocumentFromFile(oeFormatFilePath.c\_str(), oeFormatFilePath.size(), &ceDocument);
    }
    

#### \[h2\]创建客户端OE对象

基于OE文档和当前上下文实例创建客户端OE对象，实现OE文档与客户端OE对象的关联，并用于与服务端通信。

void ClientCallBack\_OnUpdateFunc(ContentEmbed\_ExtensionProxy \*proxy)
{
    OH\_LOG\_INFO(LOG\_APP, "Enter ClientCallBack\_OnUpdateFunc");
    // 将OE文档数据写入客户端应用文档
    ContentEmbed\_Document \*document = nullptr;
    ContentEmbed\_ErrorCode errCode = OH\_ContentEmbed\_Proxy\_GetDocument(proxy, &document);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_GetDocument failed, errCode: %{public}d.", errCode);
        return;
    }
    bool isLinking = false;
    ContentEmbed\_ErrorCode ret = OH\_ContentEmbed\_Document\_IsLinking(document, &isLinking);
    OH\_LOG\_INFO(LOG\_APP, "ret: %{public}d, isLinking: %{public}d", ret, isLinking);
    if (ret == CE\_ERR\_OK && isLinking) {
        OH\_LOG\_INFO(LOG\_APP, "linking document, No need to save to the sandbox");
        return;
    }
    const size\_t CHUNK\_SIZE = 4096; // 分块大小
    uint8\_t \*buffer = new (std::nothrow) uint8\_t\[CHUNK\_SIZE + 1\];
    if (!buffer)
        return;
    size\_t offset = 0;
    size\_t actualRead = 0;
    do {
        // 读取OE文档内容
        ret = OH\_ContentEmbed\_Document\_Read(buffer, CHUNK\_SIZE, document, offset, &actualRead);

        if (ret != CE\_ERR\_OK || actualRead == 0)
            break;

        // 自行处理buffer数据流
        // ...
        offset += actualRead;
    } while (true);

    delete\[\] buffer;
}

void ClientCallBack\_OnErrorFunc(ContentEmbed\_ExtensionProxy \*proxy, ContentEmbed\_ErrorCode error)
{
    OH\_LOG\_INFO(LOG\_APP, "Enter ClientCallBack\_OnErrorFunc, error: %{public}d", error);
}

void ClientCallBack\_OnEditingFinishedFunc(ContentEmbed\_ExtensionProxy \*proxy, bool dataModified)
{
    OH\_LOG\_INFO(LOG\_APP, "Enter ClientCallBack\_OnEditingFinishedFunc, dataModified: %{public}d", dataModified);
    // 建议此时销毁OE文档和客户端OE对象，释放资源。
    ContentEmbed\_Document \*document = nullptr;
    ContentEmbed\_ErrorCode errCode = OH\_ContentEmbed\_Proxy\_GetDocument(proxy, &document);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_GetDocument failed, errCode: %{public}d.", errCode);
        return;
    }
    // 销毁OE文档
    OH\_ContentEmbed\_DestroyDocument(document);
    // 销毁客户端OE对象
    OH\_ContentEmbed\_DestroyExtensionProxy(proxy);
}

void ClientCallBack\_OnExtensionStoppedFunc(ContentEmbed\_ExtensionProxy \*proxy)
{
    OH\_LOG\_INFO(LOG\_APP, "Enter ClientCallBack\_OnExtensionStoppedFunc");
}

// contextPtr: 当前上下文实例
// oeDocument: OE文档
void CreateProxy(void\* contextPtr, ContentEmbed\_Document \*oeDocument)
{
    ContentEmbed\_ExtensionProxy\* proxy;
    // 创建客户端OE对象
    ContentEmbed\_ErrorCode errCode = OH\_ContentEmbed\_CreateExtensionProxy(oeDocument, &proxy, contextPtr);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_CreateExtensionProxy failed, errCode: %{public}d.", errCode);
        return;
    }
    // 注册回调：注册文档更新、错误、编辑结束等回调函数，回调函数注册顺序无要求，但不能遗漏。
    // 注册OE文档更新回调
    errCode = OH\_ContentEmbed\_Proxy\_RegisterOnUpdateFunc(proxy, ClientCallBack\_OnUpdateFunc);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_RegisterOnUpdateFunc failed, errCode: %{public}d.", errCode);
        return;
    }
    // 注册OE文档发生错误回调
    errCode = OH\_ContentEmbed\_Proxy\_RegisterOnErrorFunc(proxy, ClientCallBack\_OnErrorFunc);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_RegisterOnErrorFunc failed, errCode: %{public}d.", errCode);
        return;
    }
    // 注册OE文档编辑结束回调
    errCode = OH\_ContentEmbed\_Proxy\_RegisterOnEditingFinishedFunc(proxy, ClientCallBack\_OnEditingFinishedFunc);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_RegisterOnEditingFinishedFunc failed, errCode: %{public}d.", errCode);
        return;
    }
    // 注册Extension实例关闭回调
    errCode = OH\_ContentEmbed\_Proxy\_RegisterOnExtensionStoppedFunc(proxy, ClientCallBack\_OnExtensionStoppedFunc);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_RegisterOnExtensionStoppedFunc failed, errCode: %{public}d.", errCode);
        return;
    }
}

获取当前上下文实例。

// 获取UIAbility Context
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
// 调用Native接口，将Context传到C++侧处理
nativeModule.receiveContext(context);

static napi\_value ReceiveContext(napi\_env env, napi\_callback\_info info) {
    size\_t argc = 1;
    napi\_value argv\[2\] = {nullptr};
    napi\_get\_cb\_info(env, info, &argc, argv, nullptr, nullptr);

    // 获取ArkTS传递的UIAbilityContext对象
    napi\_value contextObj = argv\[0\];
    void\* contextPtr = nullptr;
    napi\_ref tmp = nullptr;
    // 创建强引用，引用计数+1，防止ArkTS垃圾回收把这个Context回收掉
    napi\_create\_reference(env, contextObj, 1, &tmp);
    // 把ArkTS对象解包为C++指针
    napi\_status status = napi\_unwrap(env, contextObj, &contextPtr);
  
    if (status != napi\_ok || contextPtr == nullptr) {
        // 错误处理
        napi\_throw\_error(env, nullptr, "Failed to unwrap context");
        return nullptr;
    }
    // 记录contextPtr，后续使用
    // ...
    return nullptr;
}

// napi注册
EXTERN\_C\_START
static napi\_value Init(napi\_env env, napi\_value exports) {
    napi\_property\_descriptor desc\[\] = {
        {"receiveContext", nullptr, ReceiveContext, nullptr, nullptr, nullptr, napi\_default, nullptr},
    };
    napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
    return exports;
}
EXTERN\_C\_END

#### \[h2\]实现客户端OE对象和OE Extension交互

1.  启动OE Extension：启动OE Extension组件。
2.  查询OE Extension组件能力。
3.  编辑文档：通知OE服务端启动UIAbility编辑文档。
4.  查询OE文档编辑状态。
5.  获取OE文档快照。

void HandleProxy(ContentEmbed\_ExtensionProxy\* proxy)
{
    ContentEmbed\_ErrorCode errCode = CE\_ERR\_OK;
    // 启动OE Extension组件
    errCode = OH\_ContentEmbed\_Proxy\_StartWork(proxy);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_StartWork failed, errCode: %{public}d.", errCode);
        return;
    }
    uint32\_t bitmask = 0;
    // 查询OE Extension组件能力
    errCode = OH\_ContentEmbed\_Proxy\_GetCapability(proxy, &bitmask);
    if (errCode != CE\_ERR\_OK) {
        OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_GetCapability failed, errCode: %{public}d.", errCode);
        return;
    }
    if ((bitmask & CE\_CAPABILITY\_SUPPORT\_DO\_EDIT) != 0) {
        // 服务端支持编辑能力时，通知OE服务端编辑OE文档
        errCode = OH\_ContentEmbed\_Proxy\_DoEdit(proxy);
        if (errCode != CE\_ERR\_OK) {
            OH\_LOG\_ERROR(LOG\_APP, "OH\_ContentEmbed\_Proxy\_DoEdit failed, errCode: %{public}d.", errCode);
            return;
        }
    }
    // 主动查询OE文档的编辑状态
    bool isEditing = false;
    bool isModified = false;
    // 查询被嵌入文档编辑状态
    errCode = OH\_ContentEmbed\_Proxy\_GetEditStatus(proxy, &isEditing, &isModified);
    if ((bitmask & CE\_CAPABILITY\_SUPPORT\_SNAPSHOT) != 0) {
        // 服务端支持获取OE快照能力时，从OE服务端获取OE文档快照
        OH\_PixelmapNative\* pixelMap;
        // 获取OE文档快照
        errCode = OH\_ContentEmbed\_Proxy\_GetSnapshot(proxy, &pixelMap);
    }
    // 当proxy不需要和服务端交互时
    OH\_ContentEmbed\_Proxy\_StopWork(proxy);
}