资料:
https://github.com/PyQt5/PyQt/tree/master/QtRemoteObjects
https://github.com/PyQt5/PyQt/blob/master/QtRemoteObjects/simpleswitch/directconnectdynamicclient.py
由于项目要求,需要在python程序与c++程序之间通讯。经过取舍,觉得还是直接用QtRemoteObjects来实现这个通讯会比较合适。
QtRemoteObjects(以下简称QtRO)是一个qt的用于进程间通讯的模块。用QtRO通讯的两个进程(程序)可以在同一台电脑上,也可以在不同的电脑上(不同的电脑时,只需要在QRemoteObjectHost的url上修改一下就行)。
QtRO本质上是基于TCP/IP(可能也用到UDP?)实现的一个通讯协议,他可以让两个不同的进程(程序)也能享受到Qt的信号-槽机制,极大地减低了通讯成本。
关于QtRO的服务端与客户端的C++的实现可以参考这位大神写的介绍, 这里就不做赘述了。
这里主要介绍在python与c++之间的实现(这里是简单实现了一些功能,复杂的可以自行实现)
在python端,我们定义一个类,这个类包含了一个信号和一个槽。
RootComObj.py:
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty
from PyQt5.QtCore import QVariant
class RootComObj(QObject):
def __init__(self):
super().__init__()
# 用来发送信息到C++那边去,主要是告知此时的状态
serverToClient = pyqtSignal(QVariant)
# 这个是供c++那边直接调用的槽函数,可以用来设置参数,也可以通过返回值读取参数。
# 这里只是提供基础的数据通讯,复杂的通讯可以在这个基础上实现自己的封装
# The slot exposed to a remote client.
@pyqtSlot(QVariant, result=QVariant)
def clientToServer(self, cmd):
return cmd
使用PySide2的版本:
from PySide2.QtCore import QObject, Slot, Signal
class RootComObj(QObject):
def __init__(self):
super().__init__()
# 用来发送信息到C++那边去,主要是告知此时的状态
serverToClient = Signal("QVariant")
# 这个是供c++那边直接调用的槽函数,可以用来设置参数,也可以通过返回值读取参数。
# 这里只是提供基础的数据通讯,复杂的通讯可以在这个基础上实现自己的封装
# The slot exposed to a remote client.
@Slot("QVariant", result="QVariant")
def clientToServer(self, cmd):
print("c to s:", cmd)
return cmd
main.py:
import sys
import PyQt5.QtCore
from PyQt5.QtCore import QUrl, QTimer, QDateTime
from PyQt5.QtWidgets import QApplication
from PyQt5.QtRemoteObjects import QRemoteObjectHost, QRemoteObjectNode
from RootComObj import RootComObj
comObj = RootComObj()
def timeout():
comObj.serverToClient.emit(QDateTime.currentDateTime().toString('yyyyMMdd hh:mm:ss'))
def main():
app = QApplication(sys.argv)
remoteHost = QRemoteObjectHost(QUrl("local:replica"))
ret = remoteHost.enableRemoting(comObj, 'demo')
print(remoteHost, ret, PyQt5.QtCore.PYQT_VERSION_STR)
tm = QTimer()
tm.setInterval(1000)
tm.setSingleShot(False)
tm.timeout.connect(timeout)
tm.start()
return app.exec()
if __name__ == '__main__':
main()
使用PySide2的版本:
import PySide2.QtCore
from PySide2.QtCore import QUrl, QTimer, QDateTime, QVariantAnimation
from PySide2.QtWidgets import QApplication
from PySide2.QtRemoteObjects import QRemoteObjectHost, QRemoteObjectNode
from RootComObj import RootComObj
comObj = RootComObj()
def main():
app = QApplication(sys.argv)
remoteHost = QRemoteObjectHost(QUrl("local:replica"))
ret = remoteHost.enableRemoting(comObj, 'demo')
return app.exec_()
接着,C++这边采用的是动态Replica的方式(实际上静态也行,只要你写对那个rep文件就行)
Receiver.h
class Receiver: public QObject
{
Q_OBJECT
public:
Receiver()
{
}
public slots:
void processServerCmd(QVariant cmd)
{
qDebug() << "the server cmd is:" << cmd;
}
};
main.cpp:
#include "mainwindow.h"
#include "Receiver.h"
#include <QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//MainWindow w;
//w.show();
Receiver *rev = new Receiver();
QRemoteObjectNode repNode; // create remote object node
repNode.connectToNode(QUrl("local:replica")); // connect with remote host node
auto rep = repNode.acquireDynamic("demo");
QObject::connect(rep, &QRemoteObjectReplica::stateChanged, [=](QRemoteObjectReplica::State sta, QRemoteObjectReplica::State oldSta){
qDebug() << "oldState:" << oldSta;
qDebug() << "state:" << sta;
});
//动态replica要等初始化完成之后(也就是连接上服务器+内部初始化完成)才能做信号槽的连接,静态就无所谓
QObject::connect(rep, &QRemoteObjectReplica::initialized, [=](){
// 可以利用Qt的反射机制输出一些东西看看
// auto obj = rep->metaObject();
// for(int i = 0; i < obj->methodCount(); i++)
// {
// auto method = obj->method(i);
// qDebug() << method.name() << method.tag() << method.typeName() << method.methodType();
// for(int j = 0; j < method.parameterCount(); j++)
// {
// qDebug() << "---" << method.parameterType(j) << method.parameterNames() << method.parameterTypes();
// }
// }
// 获取属性
// qDebug() << "initialized:" << rep->property("currState");
// 连接信号, 动态replica的话,连接信号就不能用lambda表达式了,需要这样直接指明信号槽字符串
QObject::connect(rep, SIGNAL(serverToClient(QVariant)), rev, SLOT(processServerCmd(QVariant)));
// clientToServer的返回值并不直接是我们指定的类型,而是返回一个类似操作器的东西,通过这个东西我们再取得返回值。
QRemoteObjectPendingCall val;
// 执行槽函数
QMetaObject::invokeMethod(rep, "clientToServer", Qt::DirectConnection,
Q_RETURN_ARG(QRemoteObjectPendingCall, val),
Q_ARG(QVariant, "sdfdsfdf"));
val.waitForFinished();
qDebug() << "return:" << val.returnValue();
});
return a.exec();
}