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 注意事项
-
回调函数的生命周期管理
- 确保回调对象在使用期间不被销毁
- 适时清理不再需要的回调
-
错误处理
- 在回调中添加适当的错误处理
- 避免在回调中抛出未捕获的异常
-
性能考虑
- 避免在回调中进行耗时操作
- 合理使用异步操作
7. 调试建议
-
日志记录
TRACE("OnReadCompleted called: %zu bytes\n", bytes_read); -
状态检查
if (!callback_) { TRACE("Warning: Callback not set\n"); return; } -
错误处理
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. 最佳实践
- 始终在正确的线程上执行回调
- 适当管理回调的生命周期
- 添加必要的错误处理
- 避免回调中的阻塞操作
- 保持代码的可维护性和可测试性
9. 常见问题
-
Q: 为什么需要使用 ClientContext?
A: ClientContext 提供了一种在 C++ 和 JavaScript 之间传递上下文的机制,使得静态回调函数能够找到对应的对象实例。 -
Q: 如何确保回调的线程安全?
A: 通过 CallInJs 机制和线程安全函数确保回调在正确的线程上执行,并使用互斥锁和条件变量进行同步。 -
Q: 如何处理回调中的异常?
A: 在 C++ 层添加适当的错误处理,并确保异常不会跨越语言边界。

浙公网安备 33010602011771号