Modul ctypes
mimo jiné umožňuje volat funkce ze sdílených knihoven, nejen k čemuž především poskytuje typy kompatibilní s Céčkem. Nejsnažší je sice celkem pochopitelně práce s knihovnami napsanými v jazyce C, resp. C++, ale je možné obsluhovat i jiné jazyky, pokud nějakým způsobem poskytují s Céčkem kompatibilní rozhraní.
Typicky se používá právě pro volání funkcí cizích knihoven přímo z Python'u nebo rovnou pro psaní kompletních pythoních „wrapperů“ nad těmito knihovnami.
Vzhledem k rozdílnosti typů mezi Python'em a Céčkem lze přímo předávat (a vracet) pouze následující čtyři základní typy:
Jakékoliv jiné typy, ať už jednoduché nebo složené, musí být zavedeny pomocí příslušných konstruktorů z modulu ctypes.
ctypes | C | Python |
---|---|---|
c_bool | _Bool | bool |
c_char | char | jednoznakový bajtový objekt |
c_wchar | wchar_t | jednoznakový řetězec |
c_byte | char | int |
c_ubyte | unsigned char | int |
c_short | short | int |
c_ushort | unsigned short | int |
c_int | int | int |
c_uint | unsigned int | int |
c_long | long | int |
c_ulong | unsigned long | int |
c_longlong | __int64 | long long | int |
c_ulonglong | unisgned __int64 | unsigned long long | int |
c_size_t | size_t | int |
c_ssize_t | ssize_t | Py_ssize_t | int |
c_float | float | float |
c_double | double | float |
c_longdouble | long double | float |
c_char_p | char * (zakončený NUL ) |
bajtový objekt | None |
c_wchar_p | wchar_t * (zakončený NUL ) |
řetězec | None |
c_void_p | void * | int | None |
create_string_buffer()
, která vrací ctypes-pole typů c_char
, respektive funkce create_unicode_buffer()
, která vrací ctypes-pole typů c_wchar
.
Sdílenou funkci musíte tedy samozřejmě zavolat se správnými typy parametrů. To můžete zařídit buď jejich explicitním přetypováním při každém volání nebo lépe vyjmenováním příslušných typů v seznamu atributu argtypes
:
Uvedený způsob volání vás pak zachrání při pokusu předat špatné typy:
Práci s parametry si můžete na jednu stranu zesložitit, ale na druhu také zpřehlednit, použitím tříd s atributem _as_parameter_
(ať už datovým nebo ne). Modul ctypes se totiž po tomto parametru bude koukat, použijete-li při volání instanci takové třídy:
PS: Rozumí se samo sebou, že uvedený parametr musí vracet buď celé číslo nebo řetězec (i bajtový).
Chcete-li podobným způsobem použít vlastní třídy uvnitř seznamů pro atribut argtypes, musíte na nich nadefinovat metodu from_param()
, která se postará o ověření správnosti typu a vrácení příslušné návratové hodnoty (klidně opět v podobě atributu objektu _as_parameter_
):
Tělo metody from_param() může být samozřejmě mnohem složitější a můžete se v něm třebas v rámci podmínky rozhodovat podle aktuálního tvaru parametrů a vracet v každé větvi něco jiného.
Podobně se ve výchozím nastavení od sdílených funkcí čeká, že spořádaně vrátí Céčkovské celé číslo (int). Není-li tomu tak, musíte správný typ nastavit pomocí atributu restype
:
Pokud volaná sdílená funkce vrací celé číslo (a jen tehdy), můžete pro návratové hodnoty (atribut restype
) také použít volatelný objekt (typicky funkci nebo třídu). Ten dostane jako vstupní parametr právě toto celé číslo, podle kterého se můžete dál zařídit:
Přestože byste mohli býti v pokušení používat kód z předchozího slajdu pro ošetření případných návratových chyb, častější je použít pro to vlastní atribut errcheck
, který nabízí více informací:
Často se může stát, že volaná „cizí“ funkce bude očekávat parametry předávané odkazem (typicky byla-li by předávaná data zbytečně veliká na předání hodnotou a podobně). V takovém případě máte dvě možnosti:
ctypes.byref()
;
ctypes.pointer()
.
Nepřekvapivě je druhá možnost náročnější a pomalejší, protože toho prostě musí na pozadí udělat mnohem více.
Při předávání datových typů struct a union musíte podědit z příslušných konstruktorů ctypes.Structure
a ctypes.Union
, které už se postarají o všechno ostatní.
Jeden příklad na zavedení struktury podle dokumentace:
PS 1: Zarovnání jednotlivých prvků je ve výchozím nastavení stejné jako u kódu produkovaného kompilátorem Céčka.
PS 2: Pro celá čísla je možno vyrábět i bitová pole. Jejich délka/šířka je pak určena třetím parametrem v n-tici definující příslušný prvek, např. _fields_ = [("first_16", c_int, 16), ("second_16", c_int, 16)]
.
Doporučený způsob tvorby polí je vynásobení příslušného datového typu (pro jeden konkrétní prvek pole) celým číslem představujícím délku pole. Několik příkladů podle dokumentace:
Modul ctypes toho nepřekvapivě umí MNOHEM více. A také obsahuje pár překvapení, kdy se ne všechno chová tak, jak by člověk na první pohled čekal. Nicméně pro obsluhu jednodušších externích knihoven napsaných v Céčku (nebo s kompatibilním API) snad dosud popsané bude stačit.
Kdo bude potřebovat více nebo si to prostě celé nastudovat pořádně, podívá se do oficální dokumentace nebo na patnáctou kapitolu knihy Python Cookbook. Dobrou chuť ^_~