python和C的互相调用

最近在考虑基于udp做一个用于网游战斗中的数据同步协议,为了前期测试数据,决定先做一个外部的代理tunnel,原理是在server端和client端分别建立网络转发proxy,即原来的C/S连接改为两个proxy之间数据快速传输。因为udp库是用C++写的代码,在测试数据的时候需要不断地修改参数,重新编译,修改输出统计数据制表等,不胜其烦,最终决定导出接口由python脚本来进行逻辑调用。

C/C++导出到python有多种方法,根据不同的需求,可以使用下面不同的方式:
1.ctypes绑定。ctypes就包含在万能的python标准库模块里面,它可以运行时载入动态链接库(dll,so),在CPython 2.x/3.x和PyPy上都支持。这种方式好处就是不用针对性地用python api写导出函数,可以直接加载动态链接库的符号表,在python中就可以直接调用了。
2.第三方的python binding。例子有boost-python,实现方式是工具自动化用Python/C api生成一系列C++ wrapper函数。特别适用于大型的库或引擎导出到python。
3.手动写python binding函数。如果对Python C api熟悉的话,这种方式应该是最灵活的,读一遍API文档就可以使用。理论上效率应该是最好的,但对于我这种python初学者,可能需要花上不少时间。

以之前折腾C函数导出到Lua脚本的经历,本以为要先研究一番python c api,再搞上半天才能搞定。后面发现python标准库模块的ctypes已经非常强大,虽然性能应该是三种方式里面最差的,但在这个最高60fps的tunnel里面,C/Python接口边界调用的损耗先忽略。跟其他两种方式设计不一样的是,ctypes采用的是非入侵式调用接口的方式,不需要修改原来的C接口或者写一些绑定代码,直接对编译出来的动态库进行调用。ctypes使用过程也是非常愉悦的。

下面介绍下ctypes的使用:
1. 加载DLL动态链接库
这里需要注意区分动态链接库函数是使用cdecl还是stdcall的调用约定,分别使用cdll或windll加载动态库。
例如:

# 加载udp库函数  
udp_server = cdll.LoadLibrary("./udp_server.so")  
init_udp_server = udp_server.init_udp_server  
destroy_udp_server = udp_server.destroy_udp_server  
update_udp_server = udp_server.update_udp_server  
SendMsg = udp_server.SendMsg  
  
SetConnectCallback = udp_server.SetConnectCallback  
SetDisconnectCallback = udp_server.SetDisconnectCallback  
SetTimeoutCallback = udp_server.SetTimeoutCallback  
SetRecvCallback = udp_server.SetRecvCallback  

2. 数据类型映射
除了ctypes定义的基本数据类型(c_char, c_int, c_double等),还能使用pointer函数转换成指针类型。对于要导出的网络库,设置回调函数是必不可少的,在C++库里面,回调函数是通过设置一个函数指针完成的,ctypes同样支持函数指针的声明。如:recv_cb = CFUNCTYPE( None, c_char_p, c_int ),表示一个返回值为void,参数为char*和int类型的回调函数。

def __init__(self, port, ip="127.0.0.1"):  
        self._port = port  
        self._ip = ip  
  
        self._clients = {}  
  
        self.c_connect_cb = connect_cb(self.server_connect)  
        self.c_disconnect_cb = disconnect_cb(self.server_disconnect)  
        self.c_timeout_cb = timeout_cb(self.server_timeout)  
        self.c_recv_cb = recv_cb(self.server_recv)  
  
def create(self):  
        if self._port:  
            if init_udp_server(self._ip, self._port) == 0:  
                print "server listen %s:%d" % (self._ip, self._port)  
                SetConnectCallback( self.c_connect_cb )  
                SetDisconnectCallback( self.c_disconnect_cb )  
                SetTimeoutCallback( self.c_timeout_cb )  
                SetRecvCallback( self.c_recv_cb )  
                return True  
        print "[error] init_udp_server error", self._ip, self._port  
        return False  

绑定回调参数需要注意的是,绑定的回调函数需要保存为成员变量(上面的写法),目的是避免python垃圾回收导致回调函数变成野指针。这算是一个小小的坑吧。基本上一个小小的库也就用到这些功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,626评论 6 427
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,919评论 0 27
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 174,236评论 25 709
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,276评论 4 61
  • 没事翻了翻一些推送文章,不知道是推的内容扎堆了还是偶然,满篇的成功学或者短期的成功方法。 今天想...
    吕行者LV阅读 807评论 0 1