ctypes(二)
— 基本函數調用

上一次提到了,ctypes的兼容層,這次我們直接講利用兼容層調用函數的相關規則。

一、 用兼容層封裝數據類型

在這之前首先先思考一個問題,我們調用的C語言的函數,那麼傳進去的數據也一定要滿足C語言的規範。雖然Python底層是C,但是做了高度抽象封裝。那麼它還符不符合C的要求呢?

首先,我們來看個例子,看看如果不利用兼容層會有什麼問題:

示例代碼:

from ctypes import cdll
libc = cdll.msvcrt

printf = libc.printf
print(printf(b"Hello, %s
", b"World!"))
print(printf(b"Hello, %S
", "World"))
print(printf(b"%d bottles of beer
", 42))
print(printf(b"%f bottles of beer
", 42.5))

輸出結果:
Hello, World!
14
Hello, World
13
42 bottles of beer
19
Traceback (most recent call last):
File "mytest1.py", line 69, in <module>
print(printf(b"%f bottles of beer
", 42.5))
ctypes.ArgumentError: argument 2: <class TypeError>: Dont know how to convert parameter 2

果不其然,就這麼報錯了!看樣子其中有一些任然可以,有一些就不符合要求了。

其實,除了整數,字元串和位元組對象以外的所有Python類型必須要通過它們相應的ctypes類型來包裝,因此我們可以用兼容層將他們封裝成需要的C數據類型。

在來看看如下的代碼:

示例代碼:
from ctypes import cdll, c_double
libc = cdll.msvcrt
printf = libc.printf
print(printf(b"%f bottles of beer
", c_double(42.5)))
print(printf(b"An int %d, a double %f
", 1234, c_double(3.14)))
輸出結果:
42.500000 bottles of beer
26
An int 1234, a double 3.140000
31

我們使用 c_double 類型封裝了我們的浮點數。果不其然,我們就可以正確運行我們的代碼了。看樣子,兼容層,幫我們做了一些事情。

那麼究竟兼容層棒我們做了什麼呢?我們能不能利用這個機制傳遞我們自定義的類型呢?當然是有辦法的!

二、 自定義類型

話不多說,直接上代碼:

示例代碼:
from ctypes import cdll
libc = cdll.msvcrt

printf = libc.printf
class Bottles:
def __init__(self, number):
self._as_parameter_ = number
bottles = Bottles(42)
class Flasks:
def makeit(self):
from random import randint
return randint(1, 10)
_as_parameter_ = property(makeit)
print(printf(b"%d bottles of beer
", bottles))
print(printf(b"%d bottles of beer
", Flasks()))

輸出結果:
42 bottles of beer
19
10 bottles of beer
19

看來我們給的這個Flasks類型完全可以傳入,果然有這麼一種機制。

從以上代碼可以看出來,其實真正的秘訣就在於 _as_parameter_ 屬性。

那麼究竟發生了什麼呢?

其實,ctypes參數轉換是允許使用自定義的類的實例來作為ctypes函數參數的。ctypes會查找 _as_parameter_ 屬性並用作函數參數。當然,這必須符合ctypes的要求(如果不包裝則只能是整數,字元串或位元組)。

簡單來說,就是,通過 _as_parameter_ 參數來將我們的數據轉換成符合C語言要求的類型,說到底還是使用兼容層。當然,如果你不想把數據在
_as_parameter_ 中存死,可以使用描述符讓數據臨時請求。這樣就能時間動態生成參數了。

推薦閱讀:

相关文章