使用 QWebChannel 实现 JS 与 C++ 双向通信(超详细 + 踩坑总结 + Demo)

使用 QWebChannel 实现 JS 与 C++ 双向通信(超详细 + 踩坑总结 + Demo)

使用 QWebChannel 实现 JS 与 C++ 双向通信(超详细 + 踩坑总结 + Demo)

在基于 QWebEngine 的项目中,要让 前端 JavaScript 与 后端 C++ 互相通信,是非常关键的能力。 Qt 官方提供的方案就是 QWebChannel,它能让你像调用本地对象一样从 JS 访问 C++,并且支持信号/槽、异步回调等。

但实际项目中常见各种问题:

  • JS 侧无法拿到对象?
  • 信号不触发?
  • 跨线程导致闪退?
  • 对象销毁后 JS 仍然在调用?
  • Page/Page再创建导致 channel 失效?

本文将带你彻底搞懂 QWebChannel 的机制,避坑,并给出可运行的 Demo。

一、QWebChannel 的通信原理

JS 与 C++ 的交互流程如下:

C++ QObject ←→ WebChannel Transport (QWebEngine) ←→ JS 对象 

实际通信依赖两部分:

① C++ 侧:QObject + QWebChannel

  • QObject 必须继承自 QObject
  • 想暴露给 JS 的属性/方法必须加 Q_INVOKABLE 或 Q_PROPERTY
  • 信号可以直接给 JS 发送事件
  • 将 QObject 注册进 QWebChannel:
channel->registerObject("bridge", myBridgeObject); 

② JS 侧:qwebchannel.js

网页加载后必须初始化:

new QWebChannel(qt.webChannelTransport, function(channel) {     window.bridge = channel.objects.bridge; }); 

然后:

// 调用 C++ bridge.sendMessage("hello"); // 接收 C++ 信号 bridge.messageChanged.connect(function(msg){     console.log("C++ emit:", msg); }); 

二、一个可跑的双向通信 Demo(最小可运行)

1. C++ 端代码
bridge.h
#pragma once #include <QObject> class Bridge :public QObject {     Q_OBJECT public:     explicit Bridge(QObject* parent = nullptr) : QObject(parent) {}     // JS 调用 C++     Q_INVOKABLE void sendMessage(const QString& msg) {         qDebug() << "JS 调用 C++:" << msg;         emit messageChanged("C++ 收到:" + msg);     } signals:     // C++ → JS     void messageChanged(const QString& msg); }; 
mainwindow.cpp
#include "mainwindow.h" #include <QWebEngineView> #include <QWebEnginePage> #include <QWebChannel> MainWindow::MainWindow(QWidget* parent)     : QMainWindow(parent) {     auto* view = new QWebEngineView(this);     auto* page = new QWebEnginePage(this);     view->setPage(page);     setCentralWidget(view);     // 创建 C++ 对象     auto* bridge = new Bridge(this);     // 创建 QWebChannel     auto* channel = new QWebChannel(this);     channel->registerObject("bridge", bridge);     page->setWebChannel(channel);     view->load(QUrl("qrc:/index.html"));     // 模拟 3 秒后给 JS 发消息     QTimer::singleShot(3000, [bridge]() {         emit bridge->messageChanged("来自 C++ 的问候!");     }); } 

2. 前端:index.html

<!DOCTYPE html> <html> <head>     <meta charset="UTF-8" />     <script src="qrc:///qtwebchannel/qwebchannel.js"></script> </head> <body>     <h2>QWebChannel Demo</h2>     <input id="msg" placeholder="发送给 C++ 的消息">     <button onclick="callCpp()">发送</button>     <p id="log"></p>     <script>         new QWebChannel(qt.webChannelTransport, function (channel) {             window.bridge = channel.objects.bridge;             // C++ → JS             bridge.messageChanged.connect(function (msg) {                 log("收到 C++:" + msg);             });         });         function callCpp() {             const msg = document.getElementById("msg").value;             bridge.sendMessage(msg);         }         function log(msg) {             document.getElementById("log").innerHTML += msg + "<br>";         }     </script> </body> </html> 

这个 Demo 能实现:

  • JS 调 C++
  • C++ 调 JS(信号)
  • 异步通信
  • 页面加载自动初始化 WebChannel

三、QWebChannel 常见问题与避坑指南(非常重要)

1. JS 侧总是拿不到对象(undefined)

典型错误:

console.log(bridge); // undefined 

正确做法:所有 JS 调用必须在 QWebChannel 初始化之后

new QWebChannel(qt.webChannelTransport, function(channel){     window.bridge = channel.objects.bridge; }); 

⚠ 不要在 window.onload 或顶层就调用 bridge。

2. 跨线程访问导致崩溃(最常见)

若你把 Bridge 放到子线程,会直接崩溃。

原因: QWebChannel 通信必须在 UI / WebEngine 所在线程使用,否则 QObject 会被跨线程访问。

正确方案:

  • bridge 必须在主线程
  • 如果你需要跨线程,可在 Bridge 中封装信号转发:
Q_INVOKABLE void updateDataFromWorkerThread(const QString& msg) {     emit messageChanged(msg); // 仍然在主线程发 } 

Qt 会自动通过 queued connection 切回主线程。

3. 页面重新加载后 channel 失效

当你 reload()、load() 新页面后: 之前的 JS 对象全部失效。

必须在每次页面加载后重新建立 WebChannel

示例:

connect(page, &QWebEnginePage::loadFinished, this, [=](bool ok){     if(ok) {         page->setWebChannel(channel);     } }); 

⚠ Qt 5 必做,Qt 6 已自动处理但建议仍写上。

4. C++ 信号没有触发 JS 回调

常见原因:

  • Bridge 对象被销毁
  • 信号签名不匹配
  • JS 回调写在 WebChannel 初始化外部
  • 信号参数为自定义类型但未 qRegisterMetaType
排查顺序:
  • C++ 打印信号是否发出
  • JS log 是否有回调
  • 参数类型是否是 QVariant 能转的基本类型
  • Bridge 是否挂靠在 MainWindow(不要让其随 WebPage 销毁)

5. 复杂参数(对象/数组)导致 JS 不接收

支持:

  • QString
  • int/double/bool
  • QVariantList(JS Array)
  • QVariantMap(JS Object)

但不支持 C++ 自定义结构体。

解决:
QVariantMap obj; obj["name"] = "Tom"; obj["age"] = 12; emit messageChanged(obj); 

JS:

bridge.messageChanged.connect(function (obj) {     console.log(obj.name); }); 

五、总结

QWebChannel 是 Qt WebEngine 中最可靠、最强大的前后端通信方式,但需要注意:

  • Bridge 必须在主线程
  • JS 必须在初始化回调后才能使用对象
  • 参数使用 QVariant 可序列化
  • 页面刷新后必须重新 setWebChannel
  • 跨线程调用需谨慎(信号转发)

往期精彩回顾

☞QWebEngine 实战:自定义右键菜单、文件下载、Cookie 管理与 User-Agent 设置

☞ QWebEngine 常用 API 全面梳理

图片

☞ QWebEngine 系列组件全关系梳理

Read more

无人机飞行空域申请全流程指南

无人机飞行空域申请全流程指南 一、哪些情况需要申请空域? 必须申请空域的情况: * 在管制空域内飞行(包括机场周边、军事区、120米以上空域等) * 微型/轻型无人机在适飞空域内超过真高120米飞行 * 轻型无人机进行特殊操作(如中继飞行、载运危险品、飞越人群) * 小型及以上无人机(空机>4kg或最大起飞重量>7kg)在任何空域飞行 无需申请的情况: * 微型无人机在真高50米以下适飞空域内飞行 * 轻型无人机在真高120米以下适飞空域内飞行 二、申请前必备准备 1️⃣ 实名登记(所有无人机必备) * 登录民用无人驾驶航空器综合管理平台(UOM)(https://uom.caac.gov.cn或UOM APP) * 个人用户:完成实名认证(上传身份证),为≥250g的无人机登记,获取唯一编码和二维码 * 企业用户:准备营业执照、法人身份证、运营合格证、无人机适航证 2️⃣ 人员资质要求

FPGA小白学习日志一:LED的点亮

1.工程准备 首先建立一个名为led的工程文件夹,文件夹下包含了doc、quartus_prj、rtl、sim四个子文件夹: 那么我们来分析各个文件夹包含了什么: doc:该文件夹主要包含了文档资料、数据手册、Visio波形等,相当于档案库; quartus_prj:该文件夹主要包括了使用Quartus II软件新建的工程,相当于操作台; rtl:该文件夹主要放置生成硬件电路的代码,相当于原材料; Sim:该文件夹放置对生成硬件电路代码的仿真文件,相当于质检室;     这四个文件夹各自完成不同的分工,但是它们之间有什么联系呢?答案是:他们之间通过路径关联和文件引用,形成一个完美的FPGA开发闭环。quartus_prj作为工程中枢,向上访问doc读取说明,向下访问rtl获取硬件代码,向外访问sim获取仿真脚本;sim向上访问rtl在逻辑上验证硬件代码的正确性。 2.设计过程    无论我们使用FPGA做什么类型的项目时,我们都要参照一个具体的流程,这里就介绍我自己的开发流程: 1.看手册和原理图,搞清楚我们需要实现什么功能,就像做饭时我们需要看食谱,要知道自己吃什么。

实测可用!发那科机器人与西门子PLC通讯全方案(网关+Modbus TCP双版本,避坑指南附代码)

实测可用!发那科机器人与西门子PLC通讯全方案(网关+Modbus TCP双版本,避坑指南附代码) 在工业自动化现场,发那科(FANUC)机器人与西门子PLC的组合十分常见,但两者“协议壁垒”常常让工程师头疼——发那科机器人原生支持EtherNet/IP,而西门子PLC(S7-1200/1500)主打Profinet,直接通讯往往“语言不通”。 本文结合3个实际产线项目经验,整理两种经过现场验证、100%可用的通讯方案(网关跨协议版 + Modbus TCP低成本版),步骤拆解到每一步按键操作,标注新手常踩的坑,附PLC测试代码和故障排查方法,适合工控工程师直接照搬落地,再也不用为通讯调试熬夜! 核心前提(避免做无用功) * 发那科机器人:支持EtherNet/IP或Modbus TCP功能(需确认系统选件,无选件需联系厂家授权,如Modbus TCP需R602选件),本文以R-30iB系列为例。 * 西门子PLC:S7-1200/S7-1500(本文分型号适配步骤),安装**TIA

基于开源鸿蒙(OpenHarmony)的【智能家居综合应用】系统

基于开源鸿蒙(OpenHarmony)的【智能家居综合应用】系统

基于开源鸿蒙OpenHarmony的智能家居综合应用系统 * 1. 智能安防与门禁系统 * 1) 系统概述 * 2) 系统架构 * 3)关键功能实现 * 4)安全策略 * 5)总结 * 2.环境智能调节系统 * 1)场景描述 * 2)技术实现 * 3)总结 * 3.健康管理与睡眠监测 * 1)业务场景描述 * 2)技术实现方案 * 3 )总结 1. 智能安防与门禁系统 1) 系统概述 本智能安防与门禁系统是基于开源鸿蒙(OpenHarmony)操作系统设计的,旨在为用户提供一套高度集成、智能化的家庭安全防护解决方案。通过整合智能门锁、监控摄像头、门窗传感器等多种安防设备,结合智能手机或智能音箱等控制终端,实现远程监控、身份识别、异常警报等功能,全面提升家庭居住的安全性和便利性。 2) 系统架构 1. 设备层