这两天刚好有一个需求需要在WPF 中调用Python 编写的算法功能。之前都是使用gRPCthrift 框架进行远程过程调用。这次的需求和过程都比较简单,因此想直接尝试下使用Python 提供的SimpleXMLRPCServer 直接包装下已有函数,获得了一个新的技能,也觉得挺好用的,因此把这个项目也整理到Simple WPF这个系列当中。

如果觉得本博文或者Simple-WPF这个项目有用,请为它点亮一颗小星星。

本次实验的依赖环境如下

WPF:使用Kveer.XmlRPC这个包来实现远程过程调用客户端

Python: 依赖于Python自带的标准库中的SimpleXMLRPCServer

实验中将演示如何进行最简单的XmlRpc调用,以结构体作为函数返回值和数组的传递。

Q-vC0N7pcmxtIvpBRYrB5dbML9wUtzvxZYjBoTavJUg.png

我们先介绍Kveer.XmlRPC ,这是一个基于CookComputing.XmlRpc 并支持.net 2.0 标准的XmlRpc 函数库。因此他的整个命名空间还是沿用的CookComputing.XmlRpc

废话不多说,直接上代码,继承XmlRpcClientProtocol 并实现对应的Rpc 方法,在类定义上增加注解指定调用的端口和URL即可。

需要提一下的是,XmlRpcStruct 可以看作一个字典,对于XmlRpcStruct 类型我们也只需要声明一个类并对对应的成员加上对应注解来映射对象即可。

using CookComputing.XmlRpc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SimpleXMLRpcDemo
{
    internal class Structure
    {
        [XmlRpcMember("name")]
        public string Name { get; set; }

        [XmlRpcMember("age")]
        public int Age { get; set; }

        [XmlRpcMember("gender")]
        public string Gender { get; set; }
    }

    [XmlRpcUrl("http://127.0.0.1:8000/rpc")]
    internal class RpcWorker : XmlRpcClientProtocol
    {
        [XmlRpcMethod]
        public string Foo()
        {
            return (string)Invoke("Foo", new object[] { });
        }

        [XmlRpcMethod]
        public Structure BarWithStructureInvoke()
        {
            return (Structure)Invoke("BarWithStructureInvoke", new object[] { });
        }

        [XmlRpcMethod]
        public double SumArray(double[] array)
        {
            return (double)Invoke("SumArray", new object[] { array });
        }
    }
}

有了客户端调用的代码,我们来到Python 环境来编写远程过程调用的实现。在这里我们也使用一个工作类来管理和注册所有用到的函数。值得一提的是,我们可以通过继承SimpleXMLRPCRequestHandler 并指定rpc_paths的方法来自定义调用的URL地址.

from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
import logging
import numpy as np

logging.basicConfig(level=logging.INFO)
class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = ('/rpc',)

class Funcs:
    def __init__(self) -> None:
       # do nothing in this function
       pass
    
    def Foo(self):
        return 'bar'
    
    def BarWithStructureInvoke(self):
        return {'name': 'Bob', 'age': 10, 'gender': 'male'}
    
    def SumArray(self, a:[]):
        a = np.array(a)
        sum = np.sum(a)
        return float(sum)
    
   
    
server = SimpleXMLRPCServer(('127.0.0.1', 8000), logRequests=True, requestHandler=RequestHandler)

server.register_instance(Funcs())
server.serve_forever()

在完成上述编码工作后,先启动Python 的远程过程调用服务,再启动刚刚编写的WPF程序应该就可以看到下图的效果啦

Q-vC0N7pcmxtIvpBRYrB5dbML9wUtzvxZYjBoTavJUg.png

踩坑

  1. 这个远程过程调用怎么那么慢????
    可以检查一下是不是写了localhost 作为服务器客户端调用地址,localhost 是一个特殊的主机名也需要DNS解析,因此有可能是DNS慢。
    博主实测,使用localhost ,每2秒调用一次。使用127.0.0.1 ,每秒调用可以达到1000次。
  2. 为什么不能传float ????
    实测只能接受stringintdoubleXmlRpcStruct ,以及以上基本类型的数组。其他数据类型需要自行实现序列化和反序列化支持。

参考文章

使用rpc进行python和C#的传参通讯

通过XMLRPC简单构建 python服务端和C#客户端通信框架

Python XML-RPC data type

Python's xmlrpc extremely slow: one second per call