背景
在利用 Qt 框架的 QWebEngineView 进行嵌入浏览器开发时,可以很方便的通过 QWebChannel 实现与 js 的交互;在使用 Qt(C++)和 JavaScript 之间实现通信时,通常会使用一些模块和技术来使两者能够交互和传递数据。这种通信通常用于在 Qt 应用程序中嵌入 Web 内容,或者在 Web 页面中嵌入 Qt 应用程序。以下是一些常用的模块和技术,以及它们的作用
Qt WebEngine 模块
-
作用:Qt WebEngine 是 Qt 中的 Web 引擎,允许在 Qt 应用程序中嵌入 Web 内容,包括 JavaScript 脚本。它基于 Chromium 提供了一个完整的 Web 浏览器引擎
-
用法:您可以使用 Qt WebEngine 将 Web 页面嵌入到 Qt 应用程序中,并通过 JavaScript 与应用程序进行通信。这可以通过 JavaScript 和 C++之间的信号和槽机制来实现et_tools 与 rtx 即采用该方式
Qt QWebChannel 模块
-
作用:QWebChannel 是一个用于在 Qt 和 JavaScript 之间进行通信的模块。它使 Qt 中的 C++对象能够通过 WebSocket 与嵌入在 Web 页面中的 JavaScript 进行通信
-
用法:您可以使用 QWebChannel 在 Qt 应用程序和 Web 页面之间传递数据和调用函数。这样编可以在 Qt 中暴露 C++对象,使其可以在 JavaScript 中访问,反之亦然
Qt QJSEngine 模块
-
作用:QJSEngine 是一个用于在 Qt 应用程序中执行 JavaScript 代码的模块。它允许您在 C++中嵌入 JavaScript,并在两者之间交换数据
-
用法:您可以使用 QJSEngine 在 Qt 应用程序中执行 JavaScript 代码,并通过 QJSEngine 来访问 C++对象和数据。这在需要动态执行和控制 JavaScript 代码的情况下很有用
JavaScript 与 C++交互的桥接技术:
-
作用:除了上述 Qt 提供的模块,还可以使用其他桥接技术来实现 JavaScript 与 C++之间的通信,如 Embind、Boost.JS 等。这些技术允许在 C++和 JavaScript 之间创建双向的函数调用和数据传递
-
用法:您可以使用这些技术将 C++函数暴露给 JavaScript 调用,并在 C++中调用 JavaScript 函数。这样可以实现更紧密的集成和通信
QT WebChannel Examples
-
注意:在同一页面中,每次传输只能创建一个 QWebChannel 对象
-
QObjects 交互: 一旦调用传递给 QWebChannel 对象的回调,通道就完成了初始化,HTML 客户端就可以通过 channel.objects 属性访问所有已发布的对象
// 假设一个对象是以标识符“foo”发布的,我们可以如下面的示例所示与它进行交互。
// HTML客户端和QML/C++服务器之间的所有通信都是异步的
new QWebChannel(yourTransport, function(channel) {
// Connect to a signal:
channel.objects.foo.mySignal.connect(function(response) {
// This callback will be invoked whenever the signal is emitted on the C++/QML side.
// 每当C++/QML端发出信号,这个回调函数都会被调用
console.log(response); // 响应的数据
});
// To make the object known globally, assign it to the window object, i.e.:
// 要想使该对象在全局可见, 需要将其赋值给window对象
window.foo = channel.objects.foo;
// Invoke a method:
foo.myMethod(arg1, arg2, function(returnValue) {
// This callback will be invoked when myMethod has a return value.
// 当myMethod方法有返回值时, 将调用此回调函数;
// Keep in mind that the communication is asynchronous, hence the need for this callback.
// 通信是异步的, 需要在回调里面接收返回值
console.log(returnValue);
});
// Read a property value, which is cached on the client side:
console.log(foo.myProperty);
// Writing a property will instantly update the client side cache.
// The remote end will be notified about the change asynchronously
foo.myProperty = "Hello World!";
// To get notified about remote property changes, simply connect to the corresponding notify signal:
// 连接到相应的通知信号,以获得远程属性(后端推送的数据)更改的通知
foo.myPropertyChanged.connect(function() {
console.log(foo.myProperty);
});
// One can also access enums that are marked with Q_ENUM:
console.log(foo.MyEnum.MyEnumerator);
});
QTWebChannel 与 JS 通信
- 要与 QWebChannel 或 WebChannel 通信,客户端必须使用并设置 QWebChannel.js 提供的 JavaScript API
- 通过 npm 安装 qwebchannel
npm install qwebchannel@^6.2.0 - 通过
<script>方式导入<script src="qwebchannel.js"></script>
建立连接
一旦调用传递给 QWebChannel 对象的回调,通道就完成了初始化,HTML 客户端可以通过 channel.objects 属性访问所有已发布的对象
在确保qwebchannel.js被正确导入的情况下,执行如下操作
import { QWebChannel } from 'qwebchannel';
new QWebChannel((window as any).qt.webChannelTransport, (channel) => {
// 获取Qt对象channel.objects.webobj
// webobj对象是与QT端约定好,由QT端提供的对象
// 此时已经可以获取到QT发布的webobj对象
// 将该对象添加到window对象,以便全局可用
window['webobj'] = channel.objects.webobj;
// 如果项目中只在一处使用,可直接接下来往下写通信逻辑
});
相互通信
在建立连接的基础上, 进行数据交换
- 调用 QT 端发布对象上的方法
// 以webobj对象为例
// JS调用QT对象webobj提供的update方法
webobj.update();
- 通过方法连接接收 QT 端发布对象
// 以webobj对象为例
// 通过QT对象webobj提供的resUpdate对象(信号)提供的connect方法,通过回调函数参数的形式接收QT端响应的数据
webobj.resUpdate.connect((response) => {
console.log("webobj.resUpdate.connect reponse: ", response)
});
完整示例
new QWebChannel((window as any).qt.webChannelTransport, channel => {
const webobj = channel.objects.webobj;
window['webobj'] = webobj;
webobj.resUpdate.connect((response) => {
console.log("webobj.resUpdate.connect reponse: ", response)
});
webobj.update();
// 请注意: update()调用和webobj.resUpdate.connect()调用的顺序
});
EtTools 项目结构
Project
├── public # html模版文件及静态资源等
│ ├── index.html
│ └── favicon.ico
└── src
│ ├── pages
│ │ ├── page1
│ │ │ └── index.tsx # page1组件
│ │ └── page2
│ │ └── index.tsx # page2组件
│ ├── router
│ │ └── AppRouter.tsx # 路由配置文件
│ ├── utils
│ │ └── index.ts # 项目公共方法定义文件
│ ├── index.tsx # 项目初始化组件
│ ├── reportWebVitals.ts
│ ├── setupTests.ts
│ ├── typings.d.ts
│ └── react-app-env.d.ts
└── webpack.config.js # webpack配置文件
└── .gitignore
└── .prettierignore
└── tsconfig.json # typescript配置文件
└── package.json
└── README.md
获取 QT 标识符对象
在项目开发中,初始化 QT 标识符对象和调用 QT 标识符对象的方法可能位于不同的模块或页面,此时我们就会用到全局对象(window)上存储的对象
- HTML 客户端和 QML/C++服务器之间的所有通信都是异步的,因此我们需要确保调用方法前对象可以被成功获取, 以下示例供参考使用
export const getQTChannelObject = (modName) => {
let timer;
return new Promise((resolve, reject) => {
const getChannel = () => {
try {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
if (window[modName]) {
if (timer) {
clearTimeout(timer);
}
resolve(window[modName]);
} else {
getChannel();
}
}, 40);
} catch (e) {
console.log("Get Channel Error: ", e);
}
};
getChannel();
});
};
// 获取webobj对象如: getQTChannelObject('webobj')
Et_tools 打包
前端可将打包后的 dist 目录给 QT 或者提供远程服务地址,如: http://192.168.2.110/#/icd
npm run build
本地调试
调试是软件开发中非常重要的一环,无法调试将寸步难行,下面介绍基于公司项目的前端与 QT 端的联调; 前端本地调试需要以下工具或工程
- 前端静态文件包,并以此启动一个 web 服务,启动后的访问地址如: http://127.0.0.1:8080
- RTS:bin 包, 需要 QT 提前打包好,QT 嵌入的服务地址为前端服务地址,如: http://127.0.0.1:8080
- RTS:bin 提供的 debug 服务,如: http://127.0.0.1:9222, 只有启动该服务前端才可以本地调试
- 后端服务[如果需要]
注意事项
- 通信是异步的,请确保 QT 标识符对象已初始化完毕
- 调用顺序很关键
- qt.webChannelTransport 对象只在 QT 环境有效,直接打开浏览器会报 undefined 错误; 页面加载到 QT 后,QT 内嵌的浏览器 window 对象上自动拥有了该对象,不用特别关心