在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只在傳參的時候使用。
接下來還有數組,字節序,以及位域相關的東西,下一篇來寫。