QT与JS交互初识

949 阅读6分钟

背景

在利用 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 对象上自动拥有了该对象,不用特别关心

参考

参考地址