本篇主要介绍实现JS调用C++的另一种方式,即QWebSocket+QWebChannel。与之前的一篇文章QWebEngine 加载网页及交互,实现C++与JS 相互调用中提到的仅通过QWebChannel 实现JS调C++相比,本文介绍的这种方式,更灵活,能实现更加复杂的业务功能。
这篇文章中提到的QWebChannel实现JS调用C++,有两个重要的步骤:
qwebchannel.js
,并创建QWebChannel对象。ui->webview->page()->setWebChannel(pWebChannel);
。为什么要这么做呢?根据Qt官方文档可知,要通过 QWebChannel 进行C++与JS 通信,前端必须使用 qwebchannel.js 提供的 JavaScript API。对于在 Qt WebEngine 中运行的前端页面,可以通过 qrc:///qtwebchannel/qwebchannel.js 加载该文件。对于外部浏览器页面,需要将该文件复制到 Web 服务器上。然后,实例化一个 QWebChannel 对象,并传递一个传输对象和一个回调函数给它。该回调函数将在QWebChannel初始化完成并发布的对象可用时被调用。代码如下:
new QWebChannel(qt.webChannelTransport, function(channel) {
//在此处获取C++中注册到QWebChannel的对象
})
qt.webChannelTransport 是 QtWebEngine 挂载到前端全局环境中的 window.qt.webChannelTransport,即传输对象。传输对象实现了一个最小的消息传递接口。它有 send() 函数,该函数接受一个序列化的 JSON 消息并将其传输给服务器端的 QWebChannelAbstractTransport 对象。此外,当接收到来自服务器的消息时,应调用其 onmessage 属性。这部分代码在qwebchannel.js文件中,如下所示:
var QWebChannel = function(transport, initCallback)
{
if (typeof transport !== "object" || typeof transport.send !== "function") {
console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
" Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
return;
}
var channel = this;
this.transport = transport;
//JS端向C++端发送消息
this.send = function(data)
{
if (typeof(data) !== "string") {
data = JSON.stringify(data);
}
channel.transport.send(data);
}
//接收C++端传来的消息
this.transport.onmessage = function(message)
{
var data = message.data;
if (typeof data === "string") {
data = JSON.parse(data);
}
switch (data.type) {
case QWebChannelMessageTypes.signal:
channel.handleSignal(data);
break;
case QWebChannelMessageTypes.response:
channel.handleResponse(data);
break;
case QWebChannelMessageTypes.propertyUpdate:
channel.handlePropertyUpdate(data);
break;
default:
console.error("invalid message received:", message.data);
break;
}
}
};
那qt.webChannelTransport何时挂载的呢?就是在C++端调用page()->setWebChannel(pWebChannel)时将qt.webChannelTransport挂载到JS环境中,这一点可以通过注销这行代码运行程序看效果。你会看到终端报一个 js: Uncaught ReferenceError: qt is not defined
的错误。所以要在网页加载完成之前调用setWebChannel函数。通过下图能更清楚的了解这个交互过程
先看代码,WebSocketTransport类继承自QWebChannelAbstractTransport,用于发送和接收消息。它通过 textMessageReceived 处理所有QWebSocket接收的消息。同样,所有 sendTextMessage 的调用将通过 QWebSocket 发送给远程客户端。类声明如下
class QWebSocket;
class WebSocketTransport : public QWebChannelAbstractTransport
{
Q_OBJECT
public:
explicit WebSocketTransport(QWebSocket *socket);
virtual ~WebSocketTransport();
void sendMessage(const QJsonObject &message) override;
private slots:
void textMessageReceived(const QString &message);
private:
QWebSocket *m_socket;
};
源码如下:
#include "websockettransport.h"
#include
#include
#include
#include
WebSocketTransport::WebSocketTransport(QWebSocket *socket)
: QWebChannelAbstractTransport(socket)
, m_socket(socket)
{
connect(socket, &QWebSocket::textMessageReceived,
this, &WebSocketTransport::textMessageReceived);
connect(socket, &QWebSocket::disconnected,
this, &WebSocketTransport::deleteLater);
}
void WebSocketTransport::sendMessage(const QJsonObject &message)
{
QJsonDocument doc(message);
m_socket->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
}
void WebSocketTransport::textMessageReceived(const QString &messageData)
{
QJsonParseError error;
QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error);
if (error.error) {
qWarning() << "Failed to parse text message as JSON object:" << messageData
<< "Error is:" << error.errorString();
return;
} else if (!message.isObject()) {
qWarning() << "Received JSON message that is not an object: " << messageData;
return;
}
emit messageReceived(message.object(), this);
}
WebSocketClientWrapper 是连接到WebSocket服务的客户端的简单封装,将连接的socket 通过clientConnected信号传给消费者。
class WebSocketTransport;
class QWebSocketServer;
class WebSocketClientWrapper : public QObject
{
Q_OBJECT
public:
WebSocketClientWrapper(QWebSocketServer *server, QObject *parent = nullptr);
signals:
void clientConnected(WebSocketTransport *client);
private slots:
void handleNewConnection();
private:
QWebSocketServer *m_server;
};
源码如下:
#include "websocketclientwrapper.h"
#include "websockettransport.h"
#include
WebSocketClientWrapper::WebSocketClientWrapper(QWebSocketServer *server, QObject *parent)
: QObject(parent)
, m_server(server)
{
connect(server, &QWebSocketServer::newConnection,
this, &WebSocketClientWrapper::handleNewConnection);
}
void WebSocketClientWrapper::handleNewConnection()
{
emit clientConnected(new WebSocketTransport(m_server->nextPendingConnection()));
}
初始化WebSocket服务器,并连接到QWebChannel
m_webSocketServer = new QWebSocketServer(QStringLiteral("QWebSocketServer + QWebChannel Test"), QWebSocketServer::NonSecureMode);
if (!m_webSocketServer->listen(QHostAddress::LocalHost, 65535)) {
qFatal("Failed to open web socket server.");
return 1;
}
m_webSocketClientWrapper = new WebSocketClientWrapper(m_webSocketServer);
m_pWebObj = new WebObject();
QWebChannel *pWebChannel = new QWebChannel();
pWebChannel->registerObject("nativeObj", m_pWebObj);
//连接到webchannel
connect(m_webSocketClientWrapper, &WebSocketClientWrapper::clientConnected,
&pWebChannel, &QWebChannel::connectTo);
前端网页代码如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, viewport-fit=cover">
<title>QWebEngineTest</title>
<script type="text/javascript" src="./qwebchannel.js"></script>
<script type="text/javascript">
//C++ 调用showalert函数
function showalert(text)
{
alert(text)
}
//C++ 调用getJsData函数
function getJsData()
{
return "C++ Call JS demo"
}
var nativeObj
window.onload = function() {
//创建websocket客户端
var socket = new WebSocket('ws://127.0.0.1:65535');
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
new QWebChannel(socket, function(channel) {
nativeObj = channel.objects.nativeObj;
nativeObj.nativeTextChanged.connect(function(text)
{
alert("nativeTextChanged: " + text)
})
});
}
}
function jsCallCpp ()
{
nativeObj.setNativeText("JS Call C++ test ")
}
function getNativeText()
{
alert("new nativeText is: " + nativeObj.nativeText)
}
</script>
</head>
<body>
<p>
QWebEngineTest
</p>
<button onclick="jsCallCpp()" >调用C++对象的函数setNativeText</button>
<button onclick="getNativeText()" >获取C++对象属性nativeText </button>
</body>
</html>
运行效果如下图所示
至此我们实现了QWebSocket+QWebChannel与网页通信的功能,与单纯使用QWebChannel实现网页通信相比,QWebSocket+QWebChannel方式允许前端代码在任何浏览器上运行,而单纯使用QWebChannel的方式只能将前端网页嵌入到QWebEngine中展现。除此之外,前端代码中,QWebChannel 对象的创建时机也不同,QWebSocket+QWebChannel方式要求在onopen回调中创建QWebChannel对象,而只使用QWebChannel的方式在C++端调用该接口setWebChannel(pWebChannel)后就可以。
var socket = new WebSocket('ws://127.0.0.1:65535');
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
//在onOpen函数中创建QWebChannel
new QWebChannel(socket, function(channel) {
nativeObj = channel.objects.nativeObj;
nativeObj.nativeTextChanged.connect(function(text)
{
alert("nativeTextChanged: " + text)
})
});
}
以上就是本文要讲的内容了,本文详细介绍了QWebChannel与网页端通信的两种方式,希望通过阅读本文,能帮你快速掌握在Qt 前后端混合开发模式下C++与JS通信的方法。对文中内容有任何疑问,都可以留言讨论!