C++ 与 JavaScript 的回调处理机制

C++ 与 JavaScript 的回调处理机制

本文档详细介绍了 Cronet.js 中 C++ 和 JavaScript 之间的回调处理机制。

目录

1. 回调注册机制

1.1 注册过程

在 Cronet.js 中,回调的注册主要通过 CronetUrlRequestCallback 类完成:

CronetUrlRequestCallback::CronetUrlRequestCallback()
    : ptr_(_Cronet_UrlRequestCallback_CreateWith(
          CronetUrlRequestCallback::OnRedirectReceived,    // 静态回调函数
          CronetUrlRequestCallback::OnResponseStarted,     // 静态回调函数
          CronetUrlRequestCallback::OnReadCompleted,       // 静态回调函数
          CronetUrlRequestCallback::OnSucceeded,           // 静态回调函数
          CronetUrlRequestCallback::OnFailed,              // 静态回调函数
          CronetUrlRequestCallback::OnCanceled))           // 静态回调函数
{
    _Cronet_UrlRequestCallback_SetClientContext(ptr_, this);
}

1.2 回调类型

系统支持以下几种回调类型:

  • OnRedirectReceived: 处理重定向
  • OnResponseStarted: 响应开始
  • OnReadCompleted: 数据读取完成
  • OnSucceeded: 请求成功
  • OnFailed: 请求失败
  • OnCanceled: 请求取消

2. 回调地址管理

2.1 ClientContext 机制

回调地址通过 ClientContext 进行管理:

typedef void* Cronet_ClientContext;  // 基础类型定义

// 设置 context
_Cronet_UrlRequestCallback_SetClientContext(ptr_, this);

// 获取 context
static CronetUrlRequestCallback* GetThis(Cronet_UrlRequestCallbackPtr self) {
    return static_cast<CronetUrlRequestCallback*>(
        _Cronet_UrlRequestCallback_GetClientContext(self)
    );
}

2.2 生命周期管理

CronetUrlRequestCallback::~CronetUrlRequestCallback() {
    // 清理回调对象
    _Cronet_UrlRequestCallback_Destroy(ptr_);
    ptr_ = nullptr;
    
    // 清理执行器
    SetExecutor(nullptr);
    
    // 清理 JavaScript 回调引用
    if (on_read_completed_ref_) {
        napi_delete_reference(env_, on_read_completed_ref_);
    }
    // ... 清理其他回调引用
}

3. 线程安全的回调执行

3.1 CallInJs 机制

CallInJs 确保回调在正确的线程上执行:

void CronetExecutor::CallInJs(std::function<void(napi_env)> cb) {
    // 创建上下文
    CallJsContext* ctx = new CallJsContext;
    ctx->cb_ = cb;
    ctx->called_ = false;

    // 调用线程安全函数
    DCHECK(napi_call_threadsafe_function(tsfn_,
                                       ctx,
                                       napi_tsfn_blocking));

    // 等待回调完成
    std::unique_lock<std::mutex> lock(lock_);
    while (!ctx->called_)
        ctx->cv_.wait(lock);
    delete ctx;
}

3.2 线程同步

使用互斥锁和条件变量确保线程同步:

typedef struct {
    std::function<void(napi_env)> cb_;  // 回调函数
    bool called_;                        // 执行状态
    std::mutex lock_;                    // 互斥锁
    std::condition_variable cv_;         // 条件变量
} CallJsContext;

4. 完整调用链

网络事件触发时的完整调用流程:

[网络事件] → [Cronet库] → [静态回调函数] → [获取this指针] → [实例方法] → [JavaScript回调]

详细步骤:
1. 网络事件触发
2. Cronet 调用注册的静态回调函数
3. 静态函数通过 GetClientContext 获取 this 指针
4. 使用 this 指针调用实例方法
5. 通过 CallInJs 确保在 JS 线程中执行
6. 最终调用 JavaScript 回调

4.1 示例: 数据读取回调

void CronetUrlRequestCallback::OnReadCompleted(
    napi_env env,
    Cronet_UrlRequestPtr request,
    Cronet_UrlResponseInfoPtr info,
    Cronet_BufferPtr buffer,
    uint64_t bytes_read) 
{
    if (on_read_completed_ref_) {
        napi_value cb;
        DCHECK(napi_get_reference_value(env, on_read_completed_ref_, &cb));

        napi_value jsthis;
        DCHECK(napi_get_reference_value(env, wrapper_, &jsthis));

        size_t argc = 4;
        napi_value argv[argc];
        argv[0] = jsrequest;
        argv[1] = CronetUrlResponseInfo::WrapUnowned(env, info);
        argv[2] = CronetBuffer::WrapOwned(env, buffer);
        DCHECK(napi_create_uint32(env, static_cast<uint32_t>(bytes_read), &argv[3]));
        DCHECK(napi_call_function(env, jsthis, cb, argc, argv, nullptr));
    }
}

5. 设计优势

5.1 线程安全

  • 通过执行器确保回调在正确的线程上执行
  • 使用互斥锁和条件变量保证线程同步

5.2 内存管理

  • 清晰的对象生命周期管理
  • 适当的资源清理机制

5.3 灵活性

  • 支持多种类型的回调
  • 可扩展的回调机制

5.4 性能

  • 高效的线程间通信
  • 最小化线程切换开销

5.5 可靠性

  • 完善的错误处理
  • 稳定的跨语言调用

6. 使用示例

6.1 JavaScript 端使用

const callback = new CronetUrlRequestCallback();

// 设置回调函数
callback.onReadCompleted = (request, responseInfo, buffer, bytesRead) => {
    console.log(`Read ${bytesRead} bytes`);
    // 处理接收到的数据
};

callback.onSucceeded = (request, responseInfo) => {
    console.log('Request succeeded');
    // 处理请求成功
};

callback.onFailed = (request, responseInfo, error) => {
    console.error('Request failed:', error);
    // 处理请求失败
};

6.2 注意事项

  1. 回调函数的生命周期管理

    • 确保回调对象在使用期间不被销毁
    • 适时清理不再需要的回调
  2. 错误处理

    • 在回调中添加适当的错误处理
    • 避免在回调中抛出未捕获的异常
  3. 性能考虑

    • 避免在回调中进行耗时操作
    • 合理使用异步操作

7. 调试建议

  1. 日志记录

    TRACE("OnReadCompleted called: %zu bytes\n", bytes_read);
    
  2. 状态检查

    if (!callback_) {
        TRACE("Warning: Callback not set\n");
        return;
    }
    
  3. 错误处理

    napi_status status = napi_call_function(env, jsthis, cb, argc, argv, nullptr);
    if (status != napi_ok) {
        TRACE("Error: Failed to call JavaScript callback\n");
        // 处理错误
    }
    

8. 最佳实践

  1. 始终在正确的线程上执行回调
  2. 适当管理回调的生命周期
  3. 添加必要的错误处理
  4. 避免回调中的阻塞操作
  5. 保持代码的可维护性和可测试性

9. 常见问题

  1. Q: 为什么需要使用 ClientContext?
    A: ClientContext 提供了一种在 C++ 和 JavaScript 之间传递上下文的机制,使得静态回调函数能够找到对应的对象实例。

  2. Q: 如何确保回调的线程安全?
    A: 通过 CallInJs 机制和线程安全函数确保回调在正确的线程上执行,并使用互斥锁和条件变量进行同步。

  3. Q: 如何处理回调中的异常?
    A: 在 C++ 层添加适当的错误处理,并确保异常不会跨越语言边界。

10. 参考资料

  1. Node.js N-API 文档
  2. Cronet 文档
  3. 线程安全编程指南
posted @ 2025-02-28 18:31  strive-sun  阅读(22)  评论(0)    收藏  举报