Qt(PyQt)事件循环中的xmlrpc?

xmlprc inside Qt's (PyQt) event loop?

我想将 xmlprc 服务器与 Qt 应用程序一起使用,在这种情况下是 Qt 网络引擎,以便 xmlrpc 客户端可以向其发送命令。我必须与现有客户打交道。

我们这里有两个事件循环。 运行 Qt 的exec_() 是阻塞的,运行 xmlrpc 的serve_forever() 是阻塞的。

我首先想知道您是否知道 Python 的 xmlrpc 库,它与 Qt 的事件循环很好地集成在一起。所以一切都会开箱即用。


我没有找到,所以我试着在一个线程中启动 rpc 服务器。它基本上可以工作,但不能与 Qt 小部件交互。我们该怎么做?

下面使用 pyqt5==5.12 和 PyQtWebEngine。

from PyQt5.QtCore import QUrl from PyQt5.QtCore import QThread
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget

from xmlrpc.server import SimpleXMLRPCServer

# Qt
URL_START = "http://ddg.gg"

app = QApplication([])
window = QWidget()
layout = QVBoxLayout()

webview = QWebEngineView()
webview.setUrl(QUrl(URL_START))

minibuffer = QWebEngineView()
mb_prompt = """
<html>
<div> hello minibuffer </div>
</html>
"""
minibuffer.setHtml(mb_prompt)

layout.addWidget(webview)
layout.addWidget(minibuffer)

window.setWindowTitle("My browser")
window.setLayout(layout)
window.show()

# xmlrpc
def hello(name):
    return "hello " + name

def set_minibuffer(name):
    return minibuffer.setHtml(mb_prompt.replace("minibuffer", name))


class RPCThread(QThread):
    def run(self):
        # sleep a little bit to make sure QApplication is running.
        self.sleep(1)
        print("--- starting server…")
        self.rpcserver = SimpleXMLRPCServer(("localhost", 8282))
        self.rpcserver.register_function(hello)
        self.rpcserver.register_function(set_minibuffer)

        self.rpcserver.serve_forever()

class RPCWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.thread = RPCThread(self)
        self.thread.start()

rpcwidget = RPCWidget()
rpcwidget.show()

# Qt main loop.
print("--- Qt loop")
app.exec_()

来自客户:

from xmlrpc.client import ServerProxy
client = ServerProxy("http://localhost:8282"
print(client.hello("me"))  # OK
# Trying to set the web engine's html content:
print(client.set_minibuffer("me")  # fails

这失败了

--- starting server…
127.0.0.1 - - [27/Mar/2019 16:52:01] "POST /RPC2 HTTP/1.1" 200 -
Received signal 11 SEGV_MAPERR 000000000000
#0 0x7f9b5bf3e8bf <unknown>
#1 0x7f9b5bf3ecbb <unknown>
#2 0x7f9b5bf3f33e <unknown>
#3 0x7f9b6667ef20 <unknown>
#4 0x7f9b5b9f3515 <unknown>
#5 0x7f9b5b9f37eb <unknown>
#6 0x7f9b5b9f3b79 <unknown>
#7 0x7f9b5a4731c0 QtWebEngineCore::WebContentsAdapter::setContent()
#8 0x7f9b610c7bf5 QWebEnginePage::setHtml()
#9 0x7f9b612fb824 meth_QWebEngineView_setHtml
#10 0x0000005030d5 <unknown>
#11 0x000000506859 _PyEval_EvalFrameDefault
#12 0x000000504c28 <unknown>
#13 0x00000058644b <unknown>
#14 0x00000059ebbe PyObject_Call
#15 0x000000507c17 _PyEval_EvalFrameDefault
[…]

您有 2 个错误:

  • 您不能也不能直接从另一个线程修改 GUI,在您的情况下 set_minibuffer() 是在 SimpleXMLRPCServer 所在的线程中执行的。相反,您必须通过 pyqtSignalQMetaObject::invokeMethod、自定义 QEventsQTimer.singleShot(0, ...).

  • 间接执行此操作
  • 我不是 xmlrpc 专家,但在 Why can't xmlrpc client append item to list accessable via xmlrpc server procedure? 中,它说您必须将 SimpleXMLRPCServer 对象传递给构造函数 allow_none = True.

综合以上,解决方案是:

from functools import partial

# ...

from PyQt5.QtCore import QTimer

# ...

def set_minibuffer(name):
    html = mb_prompt.replace("minibuffer", name)
    wrapper = partial(minibuffer.setHtml, html)
    QTimer.singleShot(0, wrapper)
    return html


class RPCThread(QThread):
    def run(self):
        # sleep a little bit to make sure QApplication is running.
        self.sleep(1)
        print("--- starting server…")
        self.rpcserver = SimpleXMLRPCServer(("localhost", 8282), allow_none=True)
        self.rpcserver.register_function(hello)
        self.rpcserver.register_function(set_minibuffer)

        self.rpcserver.serve_forever()

# ...