ctypes

ctypes 是Python的外部函式館。它提供了與 C語言兼容的數據類型,並允許調用 DLL 或共享庫中的函式。可使用該模組以純 Python 形式對這些庫進行封裝。

基本介紹

  • 中文名:C類型
  • 外文名:ctypes
載入動態程式庫,函式,調用函式,數據類型,

載入動態程式庫

ctypes導出了cdll對象,在 Windows 系統中還導出了windlloledll對象用於載入動態程式庫
通過操作這些對象的屬性,你可以載入外部的動態程式庫。cdll載入按標準的cdecl調用協定導出的函式,而windll導入的庫按stdcall調用協定調用其中的函式。oledll也按stdcall調用協定調用其中的函式,並假定該函式返回的是 WindowsHRESULT錯誤代碼,並當函式調用失敗時,自動根據該代碼甩出一個OSError異常。
在 3.3 版更改: 原來在 Windows 下拋出的異常類型WinError目前是OSError的一個別名。
這是一些 Windows 下的例子。注意:msvcrt是微軟 C 標準庫,包含了大部分 C 標準函式,這些函式都是以 cdecl 調用協定進行調用的。
>>> from ctypes import *
>>> print(windll.kernel32)
<WinDLL 'kernel32', handle ... at ...>
>>> print(cdll.msvcrt)
<CDLL 'msvcrt', handle ... at ...>
>>> libc = cdll.msvcrt 
Windows會自動添加通常的.dll檔案擴展名。
通過cdll.msvcrt調用的標準 C 函式,可能會導致調用一個過時的,與當前 Python 所不兼容的函式。因此,請儘量使用標準的 Python 函式,而不要使用msvcrt模組。
在 Linux 下,必須使用包含檔案擴展名的檔案名稱來導入共享庫。因此不能簡單使用對象屬性的方式來導入庫。因此,你可以使用方法LoadLibrary(),或構造 CDLL 對象來導入庫。
>>> cdll.LoadLibrary("libc.so.6")
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")
>>> libc
<CDLL 'libc.so.6', handle ... at ...>

函式

通過操作dll對象的屬性來操作這些函式。
>>> from ctypes import *
>>> libc = cdll.msvcrt  # Windows
>>>  libc = CDLL("libc.so.6") # Linux
>>> libc.printf<_FuncPtr object at 0x...>
>>> print(windll.kernel32.GetModuleHandleA)
<_FuncPtr object at 0x...>
>>> print(windll.kernel32.MyOwnFunction)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ctypes.py", line 239, in __getattr__
    func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
注意:Win32系統的動態庫,比如kernel32和user32,通常會同時導出同一個函式的 ANSI 版本和 UNICODE 版本。UNICODE 版本通常會在名字最後以W結尾,而 ANSI 版本的則以A結尾。 win32的GetModuleHandle函式會根據一個模組名返回一個模組句柄,該函式暨同時包含這樣的兩個版本的原型函式,並通過宏 UNICODE 是否定義,來決定宏GetModuleHandle導出的是哪個具體函式。
/* ANSI version */ HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */ HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll不會通過這樣的魔法手段來幫你決定選擇哪一種函式,你必須顯式的調用GetModuleHandleA或GetModuleHandleW,並分別使用位元組對象或字元串對象作參數。
有時候,dlls的導出的函式名不符合 Python 的標識符規範,比如"??2@YAPAXI@Z"。此時,你必須使用getattr()方法來獲得該函式。
>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
<_FuncPtr object at 0x...>
>>>
Windows 下,有些 dll 導出的函式沒有函式名,而是通過其順序號調用。對此類函式,你也可以通過 dll 對象的數值索引來操作這些函式。
>>> cdll.kernel32[1]
<_FuncPtr object at 0x...>
>>> cdll.kernel32[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "ctypes.py", line 310, in __getitem__
    func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found

調用函式

你可以貌似是調用其它 Python 函式那樣直接調用這些函式。在這個例子中,我們調用了time()函式,該函式返回一個系統時間戳(從 Unix 時間起點到現在的秒數),而``GetModuleHandleA()`` 函式返回一個 win32 模組句柄。
此示例使用Null調用這兩個函式(將None用作NULL):
>>> print(libc.time(None))
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None)))
0x1d000000
如果你用cdecl調用方式調用stdcall約定的函式,則會甩出一個異常ValueError。反之亦然。
>>> cdll.kernel32.GetModuleHandleA(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>> windll.msvcrt.printf(b"spam")
Traceback (most recent call last):
... ...
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
你必須閱讀這些庫的頭檔案或說明文檔來確定它們的正確的調用協定。
在Windows中,ctypes使用 win32 結構化異常處理來防止由於在調用函式時使用非法參數導致的程式崩潰。
>>>
>>> windll.kernel32.GetModuleHandleA(32)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: exception: access violation reading 0x00000020
然而,總有許多辦法,通過調用ctypes使得 Python 程式崩潰。因此,你必須小心使用。faulthandler模組可以用於幫助診斷程式崩潰的原因。(比如由於錯誤的C庫函式調用導致的段錯誤)。
None,整型,位元組對象和(UNICODE)字元串是僅有的可以直接作為函式參數使用的四種Python本地數據類型。None`作為C的空指針 (NULL),位元組和字元串類型作為一個指向其保存數據的記憶體塊指針 (char*或wchar_t*)。Python 的整型則作為平台默認的C的int類型,他們的數值被截斷以適應C類型的整型長度。
在我們開始調用函式前,我們必須先了解作為函式參數的ctypes數據類型。

數據類型

ctypes定義了一些和C兼容的基本數據類型:
ctypes 類型
C 類型
Python 類型
c_bool
_Bool
bool (1)
c_char
char
單字元位元組對象
c_wchar
wchar_t
單字元字元串
c_byte
char
整型
c_ubyte
unsignedchar
整型
c_short
short
整型
c_ushort
unsignedshort
整型
c_int
int
整型
c_uint
unsignedint
整型
c_long
long
整型
c_ulong
unsignedlong
整型
c_longlong
__int64或longlong
整型
c_ulonglong
unsigned__int64或unsignedlonglong
整型
c_size_t
size_t
整型
c_ssize_t
ssize_t或Py_ssize_t
整型
c_float
float
浮點數
c_double
double
浮點數
c_longdouble
longdouble
浮點數
c_char_p
char*(NUL terminated)
位元組串對象或None
c_wchar_p
wchar_t*(NUL terminated)
字元串或None
c_void_p
void*
int 或None
  1. 構造函式接受任何具有真值的對象。
所有這些類型都可以通過使用正確類型和值的可選初始值調用它們來創建:
>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> c_ushort(-3)
c_ushort(65533)
由於這些類型是可變的,它們的值也可以在以後更改:
>>> i = c_int(42)
>>> print(i)
c_long(42)
>>> print(i.value)
42
>>> i.value = -99
>>> print(i.value)
-99
當給指針類型的對象c_char_p,c_wchar_p和c_void_p等賦值時,將改變它們所指向的記憶體地址,而不是它們所指向的記憶體區域的內容(這是理所當然的,因為 Python 的 bytes 對象是不可變的):
>>> s = "Hello, World"
>>> c_s = c_wchar_p(s)
>>> print(c_s)
c_wchar_p(139966785747344)
>>> print(c_s.value)
Hello World
>>> c_s.value = "Hi, there"
>>> print(c_s)       # the memory location has changed
c_wchar_p(139966783348904)
>>> print(c_s.value)
Hi, there
>>> print(s)        # first object is unchanged
Hello, World
但你要注意不能將它們傳遞給會改變指針所指記憶體的函式。如果你需要可改變的記憶體塊,ctypes 提供了create_string_buffer()函式,它提供多種方式創建這種記憶體塊。當前的記憶體塊內容可以通過raw屬性存取,如果你希望將它作為NUL結束的字元串,請使用value屬性:
>>> from ctypes import *
>>> p = create_string_buffer(3)      # create a 3 byte buffer, initialized to NUL bytes
>>> print(sizeof(p), repr(p.raw))
3 b'\x00\x00\x00'
>>> p = create_string_buffer(b"Hello")   # create a buffer containing a NUL terminated string
>>> print(sizeof(p), repr(p.raw))
6 b'Hello\x00'
>>> print(repr(p.value))
b'Hello'
>>> p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer
>>> print(sizeof(p), repr(p.raw))10 b'Hello\x00\x00\x00\x00\x00'
>>> p.value = b"Hi"
>>> print(sizeof(p), repr(p.raw))
10 b'Hi\x00lo\x00\x00\x00\x00\x00'
create_string_buffer()函式替代以前的ctypes版本中的c_buffer()函式 (仍然可當作別名使用)和c_string()函式。create_unicode_buffer()函式創建包含 unicode 字元的可變記憶體塊,與之對應的C語言類型是wchar_t。

相關詞條

熱門詞條

聯絡我們