文件最后提交记录最后更新时间
细粒度页面身份获取: postMessage Signed-off-by: liyongzhuang <liyongzhuang1@huawei.com> 1 个月前
bugfix Signed-off-by: goukun <wangkunshi@huawei.com> 21 天前
蓝黄同步websnapshotV2代码 IssueNo:#5647 Description: Sig: SIG_ApplicationFramework Feature or Bugfix: Feature Binary Source: No Signed-off-by: wuliubao <liubao11@huawei.com> 8 个月前
https://gitcode.com/openharmony/web_webview/issues/5979 docs: 添加 ArkWeb 项目完整文档体系 本次提交为 ArkWeb 项目添加了完整的文档体系,涵盖架构设计、开发工作流、API 接口、配置管理、测试等方面。 ## 新增文档 ### 项目主文档 - AGENT.md (英文版) - 项目主指南,便于 AI 工具理解 - AGENT_ZH.md (中文版) - 完整的中文项目指南 - CLAUDE.md - AI 辅助开发指南 - HOW_TO_ADD_BUILD_FEATURE.md - 编译特性开关添加指南 ### 核心模块文档 - ohos_nweb/README.md - 核心引擎层详细文档 - ohos_adapter/README.md - 系统适配层文档 (40+ 适配器) - ohos_interface/include/README.md - 胶水层接口文档 (双向解耦设计) - ohos_wrapper/README.md - 封装层文档 - arkweb_utils/README.md - 工具库文档 - ohos_nweb/README.md - 核心引擎层详细文档 - ohos_adapter/README.md - 系统适配层文档 (40+ 适配器) - ohos_interface/include/README.md - 胶水层接口文档 (双向解耦设计) - ohos_wrapper/README.md - 封装层文档 - arkweb_utils/README.md - 工具库文档 ### 配置管理文档 - ohos_nweb/HOW_TO_ADD_PARAM_CONFIG.md - PARAM 参数配置指南 (推荐方式) - ohos_nweb/HOW_TO_ADD_XML_CONFIG.md - XML 配置指南 ### 接口层文档 - interfaces/kits/napi/README.md - NAPI (ArkTS/JavaScript) 接口文档 - interfaces/kits/nativecommon/README.md - nativecommon 通用组件文档 - interfaces/kits/ani/README.md - ANI (高性能 ArkTS) 接口文档 - interfaces/kits/cj/README.md - CJ FFI (Rust/Python) 接口文档 - interfaces/native/README.md - Native (C++ NDK) 接口文档 ### 系统服务文档 - sa/app_fwk_update/README.md - 应用框架更新服务文档 - sa/web_native_messaging/README.md - Web 消息服务文档 ### 测试文档 - test/README.md - 测试代码文档 (单元测试 + 模糊测试) ## 文档特点 1. **全面性** - 覆盖所有核心模块、接口层、配置管理 2. **实用性** - 包含大量代码示例和实际使用场景 3. **结构清晰** - 分层架构说明,6 个常见开发场景 4. **双语支持** - 提供中英文版本,便于不同开发者使用 5. **AI 友好** - 英文版本便于 AI 工具理解和处理 ## 核心内容 ### 架构设计 - 8 层分层架构 (应用层 → 接口层 → 核心引擎 → 胶水层 → ArkWebCore → 胶水层 → 适配层 → 系统服务) - 胶水层双向解耦创新设计 (ohos_nweb: 53+ 接口, ohos_adapter: 66+ 接口) - 适配器模式 (40+ 适配器) ### 开发工作流 (6 大场景) 1. 添加新的 Public API (应用层接口) 2. 添加胶水层 API (ohos_adapter 方向) 3. 添加配置项 (PARAM 参数 + XML 配置) 4. 添加 nativecommon 通用组件 5. 添加测试用例 (单元测试 + 模糊测试) ### 配置管理 - PARAM 参数 (运行时配置,推荐) - XML 配置 (编译时配置) - 条件编译开关 (16 个功能开关) Signed-off-by: handyohos <zhangxiaotian@huawei.com> 3 个月前
README.md

interfaces/kits/cj - ArkWeb CJ FFI 接口

本目录提供 ArkWeb 的 CJ (Cangjie) FFI (Foreign Function Interface) 接口实现,通过 C FFI 为跨语言调用提供标准化的绑定机制,支持 Rust、Python 等语言调用 WebView 功能。

概述

核心功能

CJ FFI 接口提供了以下能力:

  • C FFI 绑定 - 标准的 C 外部函数接口
  • 跨语言支持 - 支持 Rust、Python、Node.js 等多种语言
  • 类型兼容 - 提供与 FFI 兼容的数据类型
  • 轻量级 - 最小化运行时开销
  • ABI 稳定 - 保证跨语言调用的二进制兼容性

适用场景

  • ✅ Rust 应用集成 WebView
  • ✅ Python 脚本调用 WebView 功能
  • ✅ 跨语言混合开发
  • ✅ 需要标准 FFI 接口的场景
  • ✅ 多语言统一接口需求

CJ vs 其他接口

特性 CJ FFI NAPI ANI Native
主要语言 Rust, Python, C++ ArkTS ArkTS C/C++
绑定方式 C FFI NAPI ANI C API
性能 高 (接近 Native) 最高
跨语言 优秀 (多语言) 仅 ArkTS 仅 ArkTS 仅 C/C++
复杂度
类型安全 编译时 运行时 编译时 编译时
API 稳定性 ABI 稳定 API 稳定 API 稳定 ABI 稳定

架构设计

整体架构

Rust/Python/C++ 应用
  ↓ FFI 调用
CJ FFI 接口层 (interfaces/kits/cj/)
  ↓ C 函数调用
ohos_nweb/ 核心引擎层
  ↓ ohos_interface/include/ohos_nweb/
ArkWebCore.hap

架构说明:

  • CJ FFI 层: 提供标准 C FFI 函数,使用 C 兼容类型
  • 核心引擎层: 提供 WebView 核心功能
  • 无需 nativecommon: 直接使用 C 基本类型和结构体

目录结构

interfaces/kits/cj/
├── BUILD.gn                    # 构建配置
├── include/                    # 公共头文件 (导出)
│   ├── webview_ffi.h           # WebView FFI 声明
│   ├── web_cookie_manager_ffi.h    # Cookie FFI
│   ├── web_data_base_ffi.h         # 数据库 FFI
│   ├── web_storage_ffi.h           # 存储 FFI
│   ├── web_download_ffi.h          # 下载 FFI
│   ├── webview_adsblock_ffi.h      # 广告拦截 FFI
│   ├── webview_media_ffi.h         # 媒体 FFI
│   ├── webview_message_ffi.h       # 消息 FFI
│   ├── webview_scheme_ffi.h        # 自定义协议 FFI
│   └── ...                        # 更多 FFI 头文件
└── src/                        # 实现文件
    ├── webview_ffi.cpp          # WebView FFI 实现
    ├── web_cookie_manager_ffi.cpp
    ├── web_data_base_ffi.cpp
    ├── web_storage_ffi.cpp
    ├── web_download_ffi.cpp
    ├── webview_controller_impl.cpp  # 控制器实现
    ├── web_runtime_delegate.cpp     # 运行时代理
    └── ...                        # 更多实现文件

核心组件

组件 FFI 头文件 说明
WebviewController webview_ffi.h WebView 控制器核心
WebCookieManager web_cookie_manager_ffi.h Cookie 管理
WebDataBase web_data_base_ffi.h 数据库管理
WebStorage web_storage_ffi.h 存储
WebDownload web_download_ffi.h 下载管理
WebAdsBlock webview_adsblock_ffi.h 广告拦截
WebMedia webview_media_ffi.h 媒体播放
WebMessage webview_message_ffi.h 消息通信
WebScheme webview_scheme_ffi.h 自定义协议

CJ FFI 绑定机制

1. FFI 函数声明

头文件: webview_ffi.h

extern "C" {
    // FFI_EXPORT 宏导出函数符号
    FFI_EXPORT int32_t FfiOHOSWebviewCtlLoadUrl(int64_t id, char* url);
    FFI_EXPORT int32_t FfiOHOSWebviewCtlRefresh(int64_t id);
    FFI_EXPORT RetDataCString FfiOHOSWebviewCtlGetUrl(int64_t id);
    FFI_EXPORT int32_t FfiOHOSWebviewCtlRunJavaScript(
        int64_t id,
        char* cScript,
        void (*callbackRef)(RetDataCString infoRef)
    );
}

关键特性:

  • extern "C": 使用 C 链接约定,避免 name mangling
  • FFI_EXPORT: 导出符号为公共 API
  • 使用 C 基本类型 (int64_t, char*, int32_t)
  • 使用函数指针作为回调

2. FFI 数据类型

基本类型映射:

C 类型 Rust 类型 Python 类型 说明
int32_t i32 int 32位整数
int64_t i64 int 64位整数 (对象 ID)
float f32 float 单精度浮点
bool bool bool 布尔值
char* *const c_char bytes C 字符串

FFI 特定结构体:

// 字符串返回值
struct RetDataCString {
    int32_t code;         // 错误码
    char* data;           // 字符串数据
};

// 数组结构
template<typename T>
struct CArr {
    T* data;              // 数据指针
    int64_t size;         // 数组大小
};

// WebHeader 数组
struct ArrWebHeader {
    CArrCString keys;     // 键数组
    CArrCString values;   // 值数组
};

// 加载数据
struct LoadDatas {
    char* data;           // 数据内容
    char* mimeType;       // MIME 类型
    char* encoding;       // 编码
    char* baseUrl;        // 基础 URL
};

3. 函数实现模式

基本函数实现 (无回调):

// FFI 函数实现
FFI_EXPORT int32_t FfiOHOSWebviewCtlLoadUrl(int64_t id, char* url)
{
    // 1. 参数验证
    if (!url) {
        WEBVIEWLOGE("url is nullptr");
        return NWebError::PARAM_CHECK_ERROR;
    }

    // 2. 获取控制器对象
    WebviewControllerImpl* controller = GetControllerById(id);
    if (!controller) {
        WEBVIEWLOGE("controller is nullptr, id: %{public}lld", id);
        return NWebError::INIT_ERROR;
    }

    // 3. URL 验证
    std::string urlStr(url);
    if (!CheckUrl(urlStr)) {
        return NWebError::INVALID_URL;
    }

    // 4. 调用核心引擎
    ErrCode errCode = controller->LoadUrl(urlStr);

    // 5. 返回错误码
    return errCode;
}

异步函数实现 (带回调):

// 回调函数类型
typedef void (*JavaScriptCallback)(RetDataCString infoRef);

// 异步 FFI 函数
FFI_EXPORT int32_t FfiOHOSWebviewCtlRunJavaScript(
    int64_t id,
    char* cScript,
    void (*callbackRef)(RetDataCString infoRef))
{
    // 1. 参数验证
    if (!cScript || !callbackRef) {
        return NWebError::PARAM_CHECK_ERROR;
    }

    // 2. 获取控制器
    WebviewControllerImpl* controller = GetControllerById(id);
    if (!controller) {
        return NWebError::INIT_ERROR;
    }

    // 3. 转换脚本字符串
    std::string script(cScript);

    // 4. 创建 C++ 回调
    auto callback = std::make_shared<WebviewJavaScriptExecuteCallback>();

    // 5. 设置 FFI 回调包装器
    callback->SetCallback([callbackRef](const std::string& result) {
        // 创建返回数据
        RetDataCString ret = {
            .code = NWebError::NO_ERROR,
            .data = strdup(result.c_str())  // 复制字符串
        };

        // 调用 FFI 回调
        callbackRef(ret);
    });

    // 6. 执行 JavaScript
    ErrCode errCode = controller->RunJavaScript(script, callback);

    return errCode;
}

4. 对象管理

对象 ID 管理:

// 全局对象映射
static std::unordered_map<int64_t, WebviewControllerImpl*> g_controllerMap;
static std::atomic<int64_t> g_nextControllerId{1};

// 创建对象
FFI_EXPORT int64_t FfiOHOSWebviewCtlConstructor()
{
    // 1. 创建控制器对象
    WebviewControllerImpl* controller = new WebviewControllerImpl();

    // 2. 分配 ID
    int64_t id = g_nextControllerId++;

    // 3. 存储到映射表
    g_controllerMap[id] = controller;

    // 4. 返回 ID 给调用者
    return id;
}

// 获取对象
WebviewControllerImpl* GetControllerById(int64_t id)
{
    auto it = g_controllerMap.find(id);
    if (it != g_controllerMap.end()) {
        return it->second;
    }
    return nullptr;
}

// 销毁对象
FFI_EXPORT void FfiOHOSWebviewCtlDestructor(int64_t id)
{
    auto it = g_controllerMap.find(id);
    if (it != g_controllerMap.end()) {
        delete it->second;
        g_controllerMap.erase(it);
    }
}

核心模块详解

1. WebviewController FFI

头文件: include/webview_ffi.h 实现文件: src/webview_ffi.cpp

主要功能:

  • 对象管理: Constructor, Destructor
  • 页面导航: LoadUrl, LoadData, Refresh, Stop
  • 页面控制: Forward, Backward, Zoom, Scroll
  • JavaScript: RunJavaScript, RegisterJavaScriptProxy
  • 状态查询: GetUrl, GetTitle, GetProgress
  • 事件处理: OnActive, OnInactive

核心函数示例:

// 获取 URL (返回字符串结构)
FFI_EXPORT RetDataCString FfiOHOSWebviewCtlGetUrl(int64_t id)
{
    RetDataCString ret = {
        .code = NWebError::NWEB_ERROR,
        .data = nullptr
    };

    WebviewControllerImpl* controller = GetControllerById(id);
    if (!controller) {
        ret.code = NWebError::INIT_ERROR;
        return ret;
    }

    std::string url = controller->GetUrl();
    ret.code = NWebError::NO_ERROR;
    ret.data = strdup(url.c_str());  // 复制字符串

    return ret;
}

// 设置自定义 UserAgent
FFI_EXPORT int32_t FfiOHOSWebviewCtlSetCustomUserAgent(int64_t id, char* cUserAgent)
{
    if (!cUserAgent) {
        return NWebError::PARAM_CHECK_ERROR;
    }

    WebviewControllerImpl* controller = GetControllerById(id);
    if (!controller) {
        return NWebError::INIT_ERROR;
    }

    std::string userAgent(cUserAgent);
    return controller->SetCustomUserAgent(userAgent);
}

2. WebCookieManager FFI

头文件: include/web_cookie_manager_ffi.h 实现文件: src/web_cookie_manager_ffi.cpp

主要功能:

  • Cookie 获取: FfiOHOSWebviewCookieFetchCookie
  • Cookie 设置: FfiOHOSWebviewCookieConfigCookie
  • Cookie 清除: FfiOHOSWebviewCookieClearAllCookies

3. WebDataBase FFI

头文件: include/web_data_base_ffi.h 实现文件: src/web_data_base_ffi.cpp

主要功能:

  • 数据库管理
  • 地理位置权限管理

4. WebStorage FFI

头文件: include/web_storage_ffi.h 实现文件: src/web_storage_ffi.cpp

主要功能:

  • LocalStorage 管理
  • SessionStorage 管理

5. WebDownload FFI

头文件: include/web_download_ffi.h 实现文件: src/web_download_ffi.cpp

主要功能:

  • 下载项管理
  • 下载代理设置
  • 下载控制 (开始、暂停、取消)

跨语言使用示例

Rust 使用示例

// FFI 声明
#[link(name = "cj_webview_ffi")]
extern "C" {
    fn FfiOHOSWebviewCtlConstructor() -> i64;
    fn FfiOHOSWebviewCtlLoadUrl(id: i64, url: *const c_char) -> i32;
    fn FfiOHOSWebviewCtlRefresh(id: i64) -> i32;
    fn FfiOHOSWebviewCtlGetUrl(id: i64) -> RetDataCString;
}

// Rust 包装器
struct WebviewController {
    id: i64,
}

impl WebviewController {
    fn new() -> Self {
        unsafe {
            let id = FfiOHOSWebviewCtlConstructor();
            WebviewController { id }
        }
    }

    fn load_url(&self, url: &str) -> Result<(), i32> {
        unsafe {
            let c_url = CString::new(url).unwrap();
            let result = FfiOHOSWebviewCtlLoadUrl(self.id, c_url.as_ptr());
            if result != 0 {
                Err(result)
            } else {
                Ok(())
            }
        }
    }

    fn refresh(&self) -> Result<(), i32> {
        unsafe {
            let result = FfiOHOSWebviewCtlRefresh(self.id);
            if result != 0 {
                Err(result)
            } else {
                Ok(())
            }
        }
    }

    fn get_url(&self) -> Result<String, i32> {
        unsafe {
            let ret = FfiOHOSWebviewCtlGetUrl(self.id);
            if ret.code != 0 {
                return Err(ret.code);
            }

            if ret.data.is_null() {
                return Ok(String::new());
            }

            let url = CStr::from_ptr(ret.data)
                .to_string_lossy()
                .into_owned();

            // 释放字符串 (需要提供释放函数)
            // FfiOHOSWebviewFreeCString(ret.data);

            Ok(url)
        }
    }
}

// 使用
fn main() {
    let controller = WebviewController::new();
    controller.load_url("https://www.example.com").unwrap();
    controller.refresh().unwrap();

    match controller.get_url() {
        Ok(url) => println!("Current URL: {}", url),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Python 使用示例 (使用 ctypes)

import ctypes
from ctypes import c_int64, c_int32, c_char_p, c_bool, POINTER

# 加载库
lib = ctypes.CDLL("libcj_webview_ffi.so")

# 定义函数签名
lib.FfiOHOSWebviewCtlConstructor.restype = c_int64
lib.FfiOHOSWebviewCtlLoadUrl.argtypes = [c_int64, c_char_p]
lib.FfiOHOSWebviewCtlLoadUrl.restype = c_int32

lib.FfiOHOSWebviewCtlGetUrl.argtypes = [c_int64]
lib.FfiOHOSWebviewCtlGetUrl.restype = RetDataCString

# 定义返回结构
class RetDataCString(ctypes.Structure):
    _fields_ = [
        ("code", c_int32),
        ("data", c_char_p),
    ]

# Python 包装器
class WebviewController:
    def __init__(self):
        self.id = lib.FfiOHOSWebviewCtlConstructor()

    def load_url(self, url):
        result = lib.FfiOHOSWebviewCtlLoadUrl(self.id, url.encode('utf-8'))
        if result != 0:
            raise Exception(f"LoadUrl failed with error: {result}")

    def get_url(self):
        ret = lib.FfiOHOSWebviewCtlGetUrl(self.id)
        if ret.code != 0:
            raise Exception(f"GetUrl failed with error: {ret.code}")

        if ret.data:
            url = ctypes.string_at(ret.data).decode('utf-8')
            # 需要提供释放函数
            # lib.FfiOHOSWebviewFreeCString(ret.data)
            return url
        return ""

# 使用
controller = WebviewController()
controller.load_url("https://www.example.com")
print(f"Current URL: {controller.get_url()}")

C++ 使用示例

#include "webview_ffi.h"
#include <iostream>

int main()
{
    // 1. 创建控制器
    int64_t id = FfiOHOSWebviewCtlConstructor();

    // 2. 加载 URL
    int32_t result = FfiOHOSWebviewCtlLoadUrl(id, "https://www.example.com");
    if (result != 0) {
        std::cerr << "LoadUrl failed: " << result << std::endl;
        return 1;
    }

    // 3. 获取当前 URL
    RetDataCString ret = FfiOHOSWebviewCtlGetUrl(id);
    if (ret.code == 0 && ret.data) {
        std::cout << "Current URL: " << ret.data << std::endl;
        free(ret.data);  // 释放字符串
    }

    // 4. 刷新页面
    result = FfiOHOSWebviewCtlRefresh(id);
    if (result != 0) {
        std::cerr << "Refresh failed: " << result << std::endl;
    }

    // 5. 销毁控制器
    // FfiOHOSWebviewCtlDestructor(id);

    return 0;
}

数据类型转换

字符串处理

C → 跨语言:

// 返回字符串 (使用 strdup 复制)
FFI_EXPORT RetDataCString FfiOHOSWebviewCtlGetTitle(int64_t id)
{
    RetDataCString ret = { .code = NWebError::NWEB_ERROR, .data = nullptr };

    std::string title = controller->GetTitle();
    ret.code = NWebError::NO_ERROR;
    ret.data = strdup(title.c_str());  // 复制到堆内存

    return ret;
}

跨语言 → C:

// 接收字符串 (假设有效)
FFI_EXPORT int32_t FfiOHOSWebviewCtlLoadUrl(int64_t id, char* url)
{
    if (!url) {
        return NWebError::PARAM_CHECK_ERROR;
    }

    // 创建 std::string (复制)
    std::string urlStr(url);

    return controller->LoadUrl(urlStr);
}

数组处理

传递数组到 FFI:

// 字符串数组结构
struct CArrString {
    char** data;
    int64_t size;
};

// 使用示例
FFI_EXPORT int32_t FfiOHOSWebviewCtlRegisterJavaScriptProxy(
    int64_t id,
    CArrI64 cFuncIds,
    const char* cName,
    CArrString cMethodList)
{
    // 1. 转换方法列表
    std::vector<std::string> methods;
    for (int64_t i = 0; i < cMethodList.size; i++) {
        if (cMethodList.data[i]) {
            methods.push_back(std::string(cMethodList.data[i]));
        }
    }

    // 2. 调用核心引擎
    return controller->RegisterJavaScriptProxy(funcIds, cName, methods);
}

错误处理

错误码定义

使用 nativecommon 的错误码系统:

namespace NWebError {
constexpr ErrCode NO_ERROR = 0;
constexpr ErrCode PARAM_CHECK_ERROR = 401;
constexpr ErrCode INVALID_URL = 17100002;
constexpr ErrCode INIT_ERROR = 17100001;
}

返回错误

// 方式 1: 返回错误码
FFI_EXPORT int32_t FfiOHOSWebviewCtlLoadUrl(int64_t id, char* url)
{
    if (!url) {
        return NWebError::PARAM_CHECK_ERROR;
    }
    // ...
    return NWebError::NO_ERROR;
}

// 方式 2: 返回结构体包含错误码
FFI_EXPORT RetDataCString FfiOHOSWebviewCtlGetUrl(int64_t id)
{
    RetDataCString ret = {
        .code = NWebError::NWEB_ERROR,
        .data = nullptr
    };

    auto controller = GetControllerById(id);
    if (!controller) {
        ret.code = NWebError::INIT_ERROR;
        return ret;
    }

    std::string url = controller->GetUrl();
    ret.code = NWebError::NO_ERROR;
    ret.data = strdup(url.c_str());

    return ret;
}

错误处理示例 (Rust)

fn load_url(&self, url: &str) -> Result<(), i32> {
    unsafe {
        let c_url = CString::new(url).unwrap();
        let result = FfiOHOSWebviewCtlLoadUrl(self.id, c_url.as_ptr());

        match result {
            0 => Ok(()),
            401 => Err(401),  // PARAM_CHECK_ERROR
            17100002 => Err(17100002),  // INVALID_URL
            code => Err(code),
        }
    }
}

内存管理

内存分配策略

基本原则:

  • 返回的字符串: 使用 strdup() 复制到堆内存
  • 接收的字符串: 假设有效,立即复制到 std::string
  • 回调数据: 调用者负责释放

释放机制:

// 1. 字符串释放 (需要导出)
FFI_EXPORT void FfiOHOSWebviewFreeCString(char* str)
{
    if (str) {
        free(str);
    }
}

// 2. 数组释放
FFI_EXPORT void FfiOHOSWebviewFreeCStringArray(CArrCString* arr)
{
    if (arr) {
        for (int64_t i = 0; i < arr->size; i++) {
            if (arr->data[i]) {
                free(arr->data[i]);
            }
        }
        free(arr->data);
    }
}

// 3. 对象释放
FFI_EXPORT void FfiOHOSWebviewCtlDestructor(int64_t id)
{
    auto it = g_controllerMap.find(id);
    if (it != g_controllerMap.end()) {
        delete it->second;
        g_controllerMap.erase(it);
    }
}

Rust 内存管理

impl Drop for WebviewController {
    fn drop(&mut self) {
        unsafe {
            FfiOHOSWebviewCtlDestructor(self.id);
        }
    }
}

// 字符串自动释放
struct CStringWrapper {
    ptr: *const c_char,
}

impl CStringWrapper {
    fn new(ptr: *const c_char) -> Self {
        CStringWrapper { ptr }
    }

    fn as_str(&self) -> &str {
        unsafe {
            CStr::from_ptr(self.ptr)
                .to_str()
                .unwrap_or("")
        }
    }
}

impl Drop for CStringWrapper {
    fn drop(&mut self) {
        unsafe {
            FfiOHOSWebviewFreeCString(self.ptr as *mut c_char);
        }
    }
}

构建和集成

构建配置 (BUILD.gn)

ohos_shared_library("cj_webview_ffi") {
    sources = [
        "src/webview_ffi.cpp",
        "src/web_cookie_manager_ffi.cpp",
        "src/web_data_base_ffi.cpp",
        # ... 更多源文件
    ]

    include_dirs = [
        "include",
        # ... 更多头文件路径
    ]

    deps = [
        "../../../ohos_nweb:libnweb",
        "../../native:ohweb",
    ]

    external_deps = [
        "hilog:libhilog",
        "napi:cj_bind_ffi",
        "napi:cj_bind_native",
    ]

    part_name = "webview"
    subsystem_name = "web"
}

构建命令

# 构建 CJ FFI 库
./build.sh --product-name <产品名> \
    --build-target //base/web/webview/interfaces/kits/cj:cj_webview_ffi

# 输出
# libcj_webview_ffi.so (安装到 /usr/lib/)

集成到 Rust 项目

Cargo.toml:

[package]
name = "arkweb_ffi"
version = "0.1.0"
edition = "2021"

[dependencies]
libc = "0.2"

[build-dependencies]
cc = "1.0"

build.rs:

fn main() {
    println!("cargo:rustc-link-search=/usr/lib");
    println!("cargo:rustc-link-lib=cj_webview_ffi");
    println!("cargo:rerun-if-changed=build.rs");
}

与 nativecommon 的关系

不使用 nativecommon

CJ FFI 接口 不使用 nativecommon 层:

原因:

  1. C 兼容性: nativecommon 使用 C++ 特性 (shared_ptr, 引用计数),不兼容纯 C
  2. 轻量级: FFI 需要最小化依赖
  3. 多语言支持: C++ 对象难以直接映射到其他语言

替代方案:

  • 对象 ID 管理: 使用 int64_t ID 代替对象指针
  • 手动内存管理: 使用 strdup()free()
  • C 结构体: 使用 FFI 兼容的结构体

对比:

NAPI/ANI 路径:
应用 → NAPI/ANI → nativecommon (shared_ptr + 引用计数) → ohos_nweb

CJ FFI 路径:
应用 → CJ FFI (C 函数) → ohos_nweb (直接调用)

性能考虑

优势

  1. 零拷贝潜力: 可以实现零拷贝的数据传递
  2. 编译优化: 编译器可以优化跨语言调用
  3. 低开销: 最小运行时开销

劣势

  1. 字符串拷贝: strdup() 需要复制字符串
  2. 手动管理: 需要手动释放内存
  3. 类型不安全: C FFI 缺乏类型检查

优化建议

// ❌ 多次调用
for _ in 0..1000 {
    controller.run_javascript("setValue(1)");
}

// ✅ 批量操作
controller.run_javascript("
    for (let i = 0; i < 1000; i++) {
        setValue(i);
    }
");

调试技巧

日志查看

# 查看 CJ FFI 日志
hilog -T WebView

# 查看 FFI 调用日志
hilog -T WebView | grep "FFI"

常见问题

Q: 对象 ID 无效

可能原因:

  1. 对象已销毁
  2. ID 类型错误 (int32 vs int64)
  3. 未调用 Constructor

解决方法:

// 始终检查 ID 有效性
WebviewControllerImpl* controller = GetControllerById(id);
if (!controller) {
    WEBVIEWLOGE("Invalid controller ID: %{public}lld", id);
    return NWebError::INIT_ERROR;
}

Q: 字符串乱码

可能原因:

  1. 编码不匹配
  2. 字符串未正确终止
  3. 内存已释放

解决方法:

// 确保 UTF-8 编码
std::string urlStr(url);  // 假设是 UTF-8

// 复制字符串
ret.data = strdup(urlStr.c_str());

// 使用后释放
free(ret.data);

Q: 内存泄漏

可能原因:

  1. 未释放返回的字符串
  2. 未销毁对象
  3. 循环引用

解决方法:

// 1. 提供释放函数
FFI_EXPORT void FfiOHOSWebviewFreeCString(char* str);

// 2. 调用者负责释放
let ret = FfiOHOSWebviewCtlGetUrl(id);
FfiOHOSWebviewFreeCString(ret.data);

// 3. 使用 RAII (Rust)
impl Drop for CStringWrapper {
    fn drop(&mut self) {
        FfiOHOSWebviewFreeCString(self.ptr);
    }
}

最佳实践

1. 参数验证

FFI_EXPORT int32_t FfiOHOSWebviewCtlLoadUrl(int64_t id, char* url)
{
    // 1. 检查指针
    if (!url) {
        return NWebError::PARAM_CHECK_ERROR;
    }

    // 2. 检查对象
    WebviewControllerImpl* controller = GetControllerById(id);
    if (!controller) {
        return NWebError::INIT_ERROR;
    }

    // 3. 验证参数
    std::string urlStr(url);
    if (!CheckUrl(urlStr)) {
        return NWebError::INVALID_URL;
    }

    // ... 处理逻辑
}

2. 内存安全

// 1. 使用 strdup 复制返回值
ret.data = strdup(string.c_str());

// 2. 导出释放函数
FFI_EXPORT void FfiOHOSWebviewFreeCString(char* str)
{
    if (str) {
        free(str);
    }
}

// 3. 文档化内存所有权
/**
 * @return 返回的字符串必须使用 FfiOHOSWebviewFreeCString() 释放
 */
FFI_EXPORT RetDataCString FfiOHOSWebviewCtlGetUrl(int64_t id);

3. 错误处理

// 1. 统一错误码
namespace NWebError {
constexpr ErrCode NO_ERROR = 0;
constexpr ErrCode PARAM_CHECK_ERROR = 401;
}

// 2. 返回详细错误
RetDataCString ret = {
    .code = NWebError::INVALID_URL,
    .data = nullptr
};

// 3. 调用者检查错误
if (ret.code != 0) {
    // 处理错误
}

4. 线程安全

// 1. 使用互斥锁保护全局状态
std::mutex g_controllerMapMutex;

WebviewControllerImpl* GetControllerById(int64_t id)
{
    std::lock_guard<std::mutex> lock(g_controllerMapMutex);
    auto it = g_controllerMap.find(id);
    return (it != g_controllerMap.end()) ? it->second : nullptr;
}

// 2. 避免在回调中持有锁

相关文档