在Windows平台上,把一些常用的算法或者功能封装成库是非常常见的,在开发python程序的时候,我们可能会用到这功能,把C/C++代码转化为python代码可能是一个非常出力不讨好的事情,这时候python调用C就会体现出巨大的优势(不过多考虑运行效率的情况下)。Python调C的核心就在于数据类型的转换,这里和大家一起探讨一下ctypes的用法。
https://docs.python.org/zh-cn/3.7/library/ctypes.html
作为准备请读一读官方的文档。
对于简单的数据类型,这里就不过多介绍了,请参考官方文档。
接下来,我们重点看一下复杂的数据结构:结构体。
首先,我们来编写一个Windows平台下的动态库。
typedef struct _Coordinate {
int x;
int y;
}Coordinate, * PCoordinate;
int add(Coordinate pt)
{
int result = 0;
result = pt.x + pt.y;
return result;
}
接下来我们,来编写python代码来调用dll,代码如下:
import ctypes
from ctypes import *
class Coordinate(Structure):
_fields_ = [
('x', ctypes.c_int),
('y', ctypes.c_int)
]
def main():
coord = Coordinate(2, 3)
mydll = ctypes.WinDLL("path/to/dll")
result = mydll.add(coord)
print(result)
if __name__ == "__main__":
main()
这个例子应该很好懂,接下来升级一下这个问题,结构体嵌套
修改我们的dll 库,代码如下
typedef struct _Coordinate {
int x;
int y;
}Coordinate, * PCoordinate;
typedef struct _Point {
Coordinate coordinate;
}Point, *PPoint;
int add(Point pt)
{
int result = 0;
result = pt.coordinate.x + pt.coordinate.y;
return result;
}
修改python 代码如下:
import ctypes
from ctypes import *
class Coordinate(Structure):
_fields_ = [
('x', ctypes.c_int),
('y', ctypes.c_int)
]
class Point(Structure):
_fields_ = [
('coordinate', Coordinate)
]
def main():
coord = Coordinate(2, 3)
pt = Point(coord)
mydll = ctypes.WinDLL("path/to/dll")
result = mydll.add(pt)
print(result)
if __name__ == "__main__":
main()
进一步升级这个问题,传结构体指针给dll
修改dll 代码如下:
typedef struct _Coordinate {
int x;
int y;
}Coordinate, * PCoordinate;
typedef struct _Point {
Coordinate coordinate;
}Point, *PPoint;
int add(PPoint pt)
{
int result = 0;
result = pt->coordinate.x + pt->coordinate.y;
return result;
}
修改python代码如下:
import ctypes
from ctypes import *
class Coordinate(Structure):
_fields_ = [
('x', ctypes.c_int),
('y', ctypes.c_int)
]
class Point(Structure):
_fields_ = [
('coordinate', Coordinate)
]
def main():
coord = Coordinate(2, 3)
pt = Point(coord)
mydll = ctypes.WinDLL("D:\\Code\\C\\Test\\Test\\x64\\Debug\\Test.dll")
result = mydll.add(ctypes.byref(pt))
print(result)
if __name__ == "__main__":
main()
让我们来进一步升级这个问题, 在结构体内定义,指针变量。
修改dll代码如下:
typedef struct _Coordinate {
int x;
int y;
}Coordinate, * PCoordinate;
typedef struct _Point {
PCoordinate coordinate;
}Point, *PPoint;
int add(PPoint pt)
{
int result = 0;
result = pt->coordinate->x + pt->coordinate->y;
return result;
}
修改python代码如下:
import ctypes
from ctypes import *
class Coordinate(Structure):
_fields_ = [
('x', ctypes.c_int),
('y', ctypes.c_int)
]
class Point(Structure):
_fields_ = [
('coordinate', POINTER(Coordinate))
]
def main():
coord = Coordinate(2, 3)
pt = Point(pointer(coord))
mydll = ctypes.WinDLL("D:\\Code\\C\\Test\\Test\\x64\\Debug\\Test.dll")
res = mydll.add(ctypes.byref(pt))
print(res)
if __name__ == "__main__":
main()
最后我们再来聊一下,POINTER, byref和pointer三者的区别。
POINTER:接受ctypes类型返回新的类型,类似 T*
pointer:返回的是一个具体的实例对象
关于byref官方的解释如下:
“ctypes exports the byref() function which is used to pass parameters by reference. The same effect can be achieved with the pointer() function, although pointer() does a lot more work since it constructs a real pointer object, so it is faster to use byref() if you don’t need the pointer object in Python itself:”
大概就是pointer会返回对象而byref不会,所以byref更快,另外byref只在传参的时候使用。
接下来还有数组,字节序,以及位域相关的东西,下一篇来写。