麦子学院 2017-07-17 17:05
python和C的互相调用方法步骤详解
回复:0 查看:2733
最近在考虑基于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
垃圾回收导致回调函数变成野指针。这算是一个小小的坑吧。基本上一个小小的库也就用到这些功能。
来源:简书