ctypes --- کتابخانه‌ای از توابع خارجی برای پایتون

کد منبع: Lib/ctypes


ctypes یک کتابخانه برای توابع خارجی در پایتون است. این کتابخانه انواع داده‌ی سازگار با C را فراهم می‌کند و فراخوانی توابع در DLLها یا کتابخانه‌های مشترک را ممکن می‌سازد. می‌توان از آن برای دربرگرفتن این کتابخانه‌ها در پایتون خالص استفاده کرد.

این یک ماژول اختیاری است. اگر این ماژول در نسخه CPython شما موجود نیست، به مستندات توزیع‌کننده خود (یعنی هرکسی که پایتون را در اختیار شما قرار داده است) مراجعه کنید. اگر شما توزیع‌کننده هستید، نیازمندی‌های ماژول‌های اختیاری را ببینید.

هشدار

ctypes دسترسی سطح پایین به کتابخانه‌های بومی و حافظه‌ی فرایند را فراهم می‌کند، سازوکارهای ایمنی پایتون را دور می‌زند و امکان اجرای کد بومی دلخواه را می‌دهد. استفاده‌ی نادرست می‌تواند داده‌ها و اشیاء را تخریب کند، اطلاعات حساس را افشا کند، باعث فروپاشی شود یا به‌گونه‌ای دیگر فرایند در حال اجرا را به خطر بیندازد.

آموزش ctypes

نکته: برخی نمونه‌کدها به نوع c_int در ctypes ارجاع می‌دهند. در سکوهایی که sizeof(long) == sizeof(int) است، این یک نام مستعار برای c_long است. بنابراین، اگر در شرایطی که انتظار c_int را دارید، c_long چاپ شود، نباید دچار تردید شوید --- این دو در واقع یک نوع هستند.

دسترسی به توابع از DLLهای بارگذاری‌شده

توابع به‌عنوان ویژگی‌های اشیای dll قابل دسترسی هستند:

>>> 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
>>>

توجه داشته باشید که DLLهای سیستمی win32 مانند kernel32 و user32 اغلب نسخه‌های ANSI و همچنین UNICODE یک تابع را اکسپورت می‌کنند. نسخه UNICODE با افزودن W به نام اکسپورت می‌شود، در حالی که نسخه ANSI با افزودن A به نام اکسپورت می‌شود. تابع GetModuleHandle در win32، که یک دسته ماژول (module handle) را برای نام ماژول داده‌شده برمی‌گرداند، پیش‌نمونه C زیر را دارد، و از یک ماکرو استفاده می‌شود تا یکی از آن‌ها را بسته به اینکه UNICODE تعریف شده باشد یا نه، به‌عنوان GetModuleHandle در دسترس قرار دهد:

/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll تلاش نمی‌کند یکی از آن‌ها را به‌صورت جادویی انتخاب کند، شما باید با مشخص کردن صریح GetModuleHandleA یا GetModuleHandleW به نسخه‌ای که نیاز دارید دسترسی پیدا کنید و سپس آن را به‌ترتیب با اشیای بایتی یا رشته‌ای فراخوانی کنید.

گاهی اوقات، DLLها توابعی را با نام‌هایی اکسپورت می‌کنند که شناسه‌های معتبر پایتون نیستند، مانند "??2@YAPAXI@Z". در این صورت باید برای بازیابی تابع از getattr() استفاده کنید:

>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
<_FuncPtr object at 0x...>
>>>

در ویندوز، برخی 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
>>>

فراخوانی توابع

می‌توانید این توابع را مانند هر شیء فراخوانی‌پذیر دیگری در پایتون فراخوانی کنید. این مثال از تابع rand() استفاده می‌کند که هیچ آرگومانی نمی‌گیرد و یک عدد صحیح شبه‌تصادفی برمی‌گرداند:

>>> print(libc.rand())
1804289383

در ویندوز، می‌توانید تابع GetModuleHandleA() را فراخوانی کنید، که یک دسته برای ماژول win32 برمی‌گرداند (برای فراخوانی آن با اشاره‌گر NULL، None را به‌عنوان تنها آرگومان ارسال کنید):

>>> print(hex(windll.kernel32.GetModuleHandleA(None)))
0x1d000000
>>>

ValueError هنگامی پرتاب می‌شود که یک تابع stdcall را با قرارداد فراخوانی cdecl فراخوانی کنید، یا برعکس:

>>> 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):
  File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>

برای اطلاع از قرارداد فراخوانی صحیح، باید پرونده‌ی سرآیند C یا مستندات تابعی را که می‌خواهید فراخوانی کنید، بررسی کنید.

در ویندوز، ctypes از مدیریت استثنای ساختاریافته win32 برای جلوگیری از فروپاشی ناشی از خطاهای حفاظت عمومی هنگام فراخوانی توابع با مقادیر نامعتبر آرگومان استفاده می‌کند:

>>> windll.kernel32.GetModuleHandleA(32)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: exception: access violation reading 0x00000020
>>>

ماژول faulthandler می‌تواند به اشکال‌زدایی فروپاشی‌ها، مانند خطاهای قطعه‌بندی (segmentation faults) ناشی از فراخوانی‌های نادرست کتابخانه C کمک کند.

None، اعداد صحیح، اشیای بایتی و رشته‌های (یونیکدی) تنها اشیای بومی پایتون هستند که می‌توانند مستقیماً به‌عنوان پارامتر در این فراخوانی‌های تابع استفاده شوند. None به‌عنوان اشاره‌گر NULL در C ارسال می‌شود، اشیای بایتی و رشته‌ها به‌عنوان اشاره‌گری به بلوک حافظه‌ای که شامل داده‌های آن‌هاست (char* یا wchar_t*) ارسال می‌شوند. اعداد صحیح پایتون به‌عنوان نوع C int پیش‌فرض پلتفرم ارسال می‌شوند، مقدار آن‌ها نقاب می‌شود تا در نوع C جای گیرد.

پیش از آنکه به فراخوانی توابع با سایر انواع پارامتر بپردازیم، باید درباره‌ی انواع داده‌ی ctypes بیشتر بیاموزیم.

انواع داده بنیادی

ctypes تعدادی از انواع داده‌ی اولیه‌ی سازگار با C را تعریف می‌کند:

نوع ctypes

نوع C

نوع پایتون

_type_

c_bool

_Bool

bool

'?'

c_char

char

bytes تک‌نویسه‌ای

'c'

c_wchar

wchar_t

str یک‌نویسه‌ای

'u'

c_byte

char

int

'b'

c_ubyte

unsigned char

int

'B'

c_short

short

int

'h'

c_ushort

unsigned short

int

'H'

c_int

int

int

'i' *

c_int8

int8_t

int

*

c_int16

int16_t

int

*

c_int32

int32_t

int

*

c_int64

int64_t

int

*

c_uint

unsigned int

int

'I' *

c_uint8

uint8_t

int

*

c_uint16

uint16_t

int

*

c_uint32

uint32_t

int

*

c_uint64

uint64_t

int

*

c_long

long

int

'l'

c_ulong

unsigned long

int

'L'

c_longlong

long long

int

'q' *

c_ulonglong

unsigned long long

int

'Q' *

c_size_t

size_t

int

*

c_ssize_t

Py_ssize_t

int

*

c_time_t

time_t

int

*

c_float

float

float

'f'

c_double

double

float

'd'

c_longdouble

long double

float

'g' *

c_char_p

char* (پایان‌یافته با NUL)

bytes یا None

'z'

c_wchar_p

wchar_t* (پایان‌یافته با NUL)

str یا None

'Z'

c_void_p

void*

int یا None

'P'

py_object

PyObject*

object

'O'

VARIANT_BOOL

short int

bool

'v'

علاوه بر این، اگر محاسبات مختلط سازگار با IEC 60559 (پیوست G) در هر دو C و libffi پشتیبانی شود، انواع مختلط زیر در دسترس هستند:

نوع ctypes

نوع C

نوع پایتون

_type_

c_float_complex

float complex

complex

'F'

c_double_complex

double complex

complex

'D'

c_longdouble_complex

long double complex

complex

'G'

همه‌ی این نوع‌ها را می‌توان با فراخوانی آن‌ها به‌همراه یک مقدارده اولیه‌ی اختیاری از نوع و مقدار صحیح ایجاد کرد:

>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> c_ushort(-3)
c_ushort(65533)
>>>

سازنده‌های انواع عددی، ورودی را با استفاده از __bool__()، __index__() (برای int__float__() یا __complex__() تبدیل می‌کنند. این بدان معناست که c_bool هر شیء دارای مقدار درستی را می‌پذیرد:

>>> empty_list = []
>>> c_bool(empty_list)
c_bool(False)

از آنجا که این نوع‌ها تغییرپذیرند، می‌توان مقدار آن‌ها را نیز بعداً تغییر داد:

>>> 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، موقعیت حافظه‌ای را که به آن اشاره می‌کنند تغییر می‌دهد، نه محتوای بلوک حافظه را (البته که نه، زیرا اشیاء رشته‌ای پایتون تغییرناپذیر هستند):

>>> 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() جایگزین تابع قدیمی c_buffer() شده است (که همچنان به‌عنوان یک نام مستعار در دسترس است). برای ایجاد یک بلوک حافظه تغییرپذیر حاوی نویسه‌های یونیکد از نوع C wchar_t، از تابع create_unicode_buffer() استفاده کنید.

فراخوانی توابع، ادامه

توجه داشته باشید که printf در کانال خروجی استاندارد واقعی چاپ می‌کند، نه در sys.stdout، بنابراین این مثال‌ها فقط در اعلان خط فرمان کار می‌کنند، نه از داخل IDLE یا PythonWin:

>>> printf = libc.printf
>>> printf(b"Hello, %s\n", b"World!")
Hello, World!
14
>>> printf(b"Hello, %S\n", "World!")
Hello, World!
14
>>> printf(b"%d bottles of beer\n", 42)
42 bottles of beer
19
>>> printf(b"%f bottles of beer\n", 42.5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 2: TypeError: Don't know how to convert parameter 2
>>>

همان‌طور که پیش‌تر ذکر شد، همه انواع پایتون به‌جز اعداد صحیح، رشته‌ها و اشیای bytes باید در نوع متناظر ctypes خود قرار داده شوند تا بتوان آن‌ها را به نوع داده C موردنیاز تبدیل کرد:

>>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>

فراخوانی توابع چندآرگومانی (variadic functions)

در بسیاری از سکوها، فراخوانی توابع چندآرگومانی آرگومان از طریق ctypes دقیقاً مانند فراخوانی توابع با تعداد ثابت پارامتر است. در برخی سکوها، به‌ویژه ARM64 برای سکوهای اپل، قرارداد فراخوانی برای توابع چندآرگومانی آرگومان با قرارداد فراخوانی توابع معمولی متفاوت است.

در آن سکوها، لازم است ویژگی argtypes را برای آرگومان‌های عادی و غیرچندآرگومانی (non-variadic) تابع تعیین کنید:

libc.printf.argtypes = [ctypes.c_char_p]

از آن‌جا که تعیین این ویژگی مانع قابلیت حمل نمی‌شود، توصیه می‌شود همیشه argtypes را برای تمام توابع چندآرگومانی آرگومان (variadic) مشخص کنید.

فراخوانی توابع با انواع داده سفارشی خودتان

همچنین می‌توانید تبدیل آرگومان ctypes را سفارشی‌سازی کنید تا بتوان از نمونه‌های کلاس‌های خودتان به‌عنوان آرگومان‌های تابع استفاده کرد. ctypes به دنبال ویژگی _as_parameter_ می‌گردد و از آن به‌عنوان آرگومان تابع استفاده می‌کند. این ویژگی باید عدد صحیح، رشته، بایت‌ها، نمونه‌ای از ctypes، یا شیءای دارای ویژگی _as_parameter_ باشد:

>>> class Bottles:
...     def __init__(self, number):
...         self._as_parameter_ = number
...
>>> bottles = Bottles(42)
>>> printf(b"%d bottles of beer\n", bottles)
42 bottles of beer
19
>>>

اگر نمی‌خواهید داده‌های نمونه را در متغیر نمونه‌ی _as_parameter_ ذخیره کنید، می‌توانید یک @property تعریف کنید که ویژگی را در صورت درخواست در دسترس قرار می‌دهد.

مشخص کردن انواع آرگومان‌های مورد نیاز (function prototypes)

می‌توان انواع مورد نیاز برای آرگومان‌های توابع اکسپورتشده از DLLها را با تنظیم ویژگی argtypes مشخص کرد.

argtypes باید دنباله‌ای از انواع داده C باشد (تابع printf() احتمالاً مثال خوبی در اینجا نیست، زیرا بسته به رشته قالب، تعداد متغیر و انواع مختلفی از پارامترها را می‌گیرد؛ از طرف دیگر، این موضوع برای آزمایش این قابلیت بسیار مفید است):

>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
>>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2)
String 'Hi', Int 10, Double 2.200000
37
>>>

مشخص کردن یک قالب، در برابر انواع آرگومان ناسازگار محافظت می‌کند (درست مانند پیش‌نمونه یک تابع C) و تلاش می‌کند آرگومان‌ها را به انواع معتبر تبدیل کند:

>>> printf(b"%d %d %d", 1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 2: TypeError: 'int' object cannot be interpreted as ctypes.c_char_p
>>> printf(b"%s %d %f\n", b"X", 2, 3)
X 2 3.000000
13
>>>

اگر کلاس‌های خودتان را تعریف کرده‌اید و آن‌ها را به فراخوانی‌های تابع ارسال می‌کنید، باید برای آن‌ها یک متد کلاس from_param() پیاده‌سازی کنید تا بتوانید از آن‌ها در دنباله argtypes استفاده کنید. متد کلاس from_param() شیء پایتونی ارسال‌شده به فراخوانی تابع را دریافت می‌کند؛ این متد باید بررسی نوع یا هر کاری را که برای اطمینان از قابل‌قبول بودن این شیء لازم است انجام دهد و سپس خود شیء، ویژگی _as_parameter_ آن، یا هر چیزی را که می‌خواهید در این حالت به‌عنوان آرگومان تابع C ارسال کنید، برگرداند. باز هم، نتیجه باید یک عدد صحیح، رشته، بایت‌ها، یک نمونه از ctypes، یا شیءای با ویژگی _as_parameter_ باشد.

انواع بازگشتی

به‌طور پیش‌فرض فرض می‌شود که توابع نوع int در C را برمی‌گردانند. سایر انواع بازگشتی را می‌توان با تنظیم ویژگی restype شیء تابع مشخص کرد.

پیش‌نمونه‌ی C برای time() به‌صورت time_t time(time_t *) است. از آن‌جا که time_t ممکن است نوعی متفاوت از نوع بازگشتی پیش‌فرض int داشته باشد، باید ویژگی restype را مشخص کنید:

>>> libc.time.restype = c_time_t

انواع آرگومان‌ها را می‌توان با استفاده از argtypes:: مشخص کرد:

>>> libc.time.argtypes = (POINTER(c_time_t),)

برای فراخوانی تابع با یک اشاره‌گر NULL به‌عنوان اولین آرگومان، از None استفاده کنید:

>>> print(libc.time(None))
1150640792

در اینجا یک مثال پیشرفته‌تر آمده است که از تابع strchr() استفاده می‌کند؛ این تابع یک اشاره‌گر به رشته و یک نویسه دریافت می‌کند و اشاره‌گری به یک رشته برمی‌گرداند:

>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d"))
8059983
>>> strchr.restype = c_char_p    # c_char_p is a pointer to a string
>>> strchr(b"abcdef", ord("d"))
b'def'
>>> print(strchr(b"abcdef", ord("x")))
None
>>>

اگر می‌خواهید از فراخوانی‌های ord("x") بالا اجتناب کنید، می‌توانید ویژگی argtypes را تنظیم کنید، و آرگومان دوم از یک شیء بایت پایتون تک‌نویسه‌ای به یک C char تبدیل می‌شود:

>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
b'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
ctypes.ArgumentError: argument 2: TypeError: one character bytes, bytearray or integer expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
b'def'
>>>

اگر تابع خارجی یک عدد صحیح برگرداند، همچنین می‌توانید از یک شیء فراخوانی‌پذیر پایتون (برای مثال یک تابع یا یک کلاس) به‌عنوان ویژگی restype استفاده کنید. آن شیء فراخوانی‌پذیر با عدد صحیحی که تابع C برمی‌گرداند، فراخوانی می‌شود و نتیجه‌ی این فراخوانی به‌عنوان نتیجه‌ی فراخوانی تابع شما استفاده می‌شود. این کار برای بررسی مقادیر بازگشتی خطا و پرتاب خودکار استثنا مفید است:

>>> GetModuleHandle = windll.kernel32.GetModuleHandleA
>>> def ValidHandle(value):
...     if value == 0:
...         raise WinError()
...     return value
...
>>>
>>> GetModuleHandle.restype = ValidHandle
>>> GetModuleHandle(None)
486539264
>>> GetModuleHandle("something silly")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.
>>>

WinError تابعی است که API FormatMessage() ویندوز را برای دریافت نمایش رشته‌ای یک کد خطا فراخوانی می‌کند و یک استثنا بازمی‌گرداند. WinError یک پارامتر اختیاری کد خطا می‌گیرد؛ اگر از آن استفاده نشود، برای دریافت آن GetLastError() را فراخوانی می‌کند.

لطفاً توجه داشته باشید که سازوکار بررسی خطای بسیار قدرتمندتری از طریق ویژگی errcheck در دسترس است؛ برای جزئیات، راهنمای مرجع را ببینید.

انتقال اشاره‌گرها (یا: انتقال پارامترها با ارجاع)

گاهی یک تابع API در C انتظار دارد یک اشاره‌گر به یک نوع داده به‌عنوان پارامتر دریافت کند، احتمالاً برای نوشتن در مکان متناظر، یا اگر داده برای ارسال با مقدار بیش از حد بزرگ باشد. این به‌عنوان ارسال پارامترها با ارجاع نیز شناخته می‌شود.

ctypes تابع byref() را اکسپورت می‌کند که برای ارسال پارامترها به‌صورت ارجاعی استفاده می‌شود. می‌توان همان اثر را با تابع pointer() به دست آورد، اگرچه pointer() کار بسیار بیشتری انجام می‌دهد، زیرا یک شیء اشاره‌گر واقعی می‌سازد؛ بنابراین اگر به خود شیء اشاره‌گر در پایتون نیاز ندارید، استفاده از byref() سریع‌تر است:

>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer(b'\000' * 32)
>>> print(i.value, f.value, repr(s.value))
0 0.0 b''
>>> libc.sscanf(b"1 3.14 Hello", b"%d %f %s",
...             byref(i), byref(f), s)
3
>>> print(i.value, f.value, repr(s.value))
1 3.1400001049 b'Hello'
>>>

ساختارها و اجتماع‌ها (unions)

ساختارها و اجتماع‌ها باید از کلاس‌های پایه Structure و Union که در ماژول ctypes تعریف شده‌اند، ارث‌بری کنند. هر زیرکلاس باید ویژگی _fields_ را تعریف کند. _fields_ باید فهرستی از تاپل‌های ۲تایی باشد که شامل یک نام فیلد و یک نوع فیلد هستند.

نوع فیلد باید یک نوع از ctypes مانند c_int یا هر نوع مشتق‌شده دیگری از ctypes باشد: ساختار، اجتماع، آرایه، اشاره‌گر.

در اینجا نمونه‌ای ساده از ساختار POINT آمده است که شامل ۲ عدد صحیح به نام‌های x و y است و همچنین نحوه مقداردهی اولیه یک ساختار در سازنده را نشان می‌دهد:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = [("x", c_int),
...                 ("y", c_int)]
...
>>> point = POINT(10, 20)
>>> print(point.x, point.y)
10 20
>>> point = POINT(y=5)
>>> print(point.x, point.y)
0 5
>>> POINT(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: too many initializers
>>>

با این حال، می‌توانید ساختارهای بسیار پیچیده‌تری بسازید. یک ساختار می‌تواند خود با استفاده از یک ساختار به‌عنوان نوع فیلد، شامل ساختارهای دیگری باشد.

در اینجا یک ساختار RECT آمده است که شامل دو POINT به نام‌های upperleft و lowerright است:

>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
...
>>> rc = RECT(point)
>>> print(rc.upperleft.x, rc.upperleft.y)
0 5
>>> print(rc.lowerright.x, rc.lowerright.y)
0 0
>>>

همچنین می‌توان ساختارهای تودرتو را در سازنده به چند روش مقداردهی اولیه کرد:

>>> r = RECT(POINT(1, 2), POINT(3, 4))
>>> r = RECT((1, 2), (3, 4))

می‌توان توصیف‌گرهای فیلد (descriptor) را از کلاس بازیابی کرد؛ این توصیف‌گرها برای اشکال‌زدایی مفید هستند، زیرا می‌توانند اطلاعات مفیدی ارائه دهند. CField را ببینید:

>>> POINT.x
<ctypes.CField 'x' type=c_int, ofs=0, size=4>
>>> POINT.y
<ctypes.CField 'y' type=c_int, ofs=4, size=4>
>>>

هشدار

ctypes از ارسال اجتماع‌ها (unions) یا ساختارهایی که فیلدهای بیتی (bit-fields) دارند به توابع به‌صورت مقداری پشتیبانی نمی‌کند. هرچند ممکن است این روش روی x86 ۳۲ بیتی کار کند، اما کارکرد آن در حالت کلی توسط کتابخانه تضمین نمی‌شود. اجتماع‌ها و ساختارهای دارای فیلدهای بیتی باید همیشه به‌صورت اشاره‌گر به توابع ارسال شوند.

چیدمان ساختار/اجتماع (union)، هم‌ترازی و ترتیب بایت

به‌طور پیش‌فرض، فیلدهای Structure و Union به همان شیوه‌ای چیدمان می‌شوند که کامپایل‌کننده C آن‌ها را چیدمان می‌کند. با تعیین ویژگی کلاس _layout_ در تعریف زیرکلاس، می‌توان این رفتار را به‌طور کامل لغو کرد؛ برای جزئیات، مستندات این ویژگی را ببینید.

می‌توانید حداکثر ترازبندی را برای فیلدها و/یا برای خود ساختار، به‌ترتیب با تنظیم ویژگی‌های کلاس _pack_ و/یا _align_ مشخص کنید. برای جزئیات، مستندات ویژگی‌ها را ببینید.

ctypes برای ساختارها و اجتماع‌ها از ترتیب بایت بومی استفاده می‌کند. برای ساختن ساختارها با ترتیب بایت غیربومی، می‌توانید از یکی از کلاس‌های پایه BigEndianStructure، LittleEndianStructure، BigEndianUnion و LittleEndianUnion استفاده کنید. این کلاس‌ها نمی‌توانند شامل فیلدهای اشاره‌گر باشند.

فیلدهای بیتی در ساختارها و اجتماعها

ایجاد ساختارها و اجتماع‌های حاوی فیلدهای بیتی امکان‌پذیر است. فیلدهای بیتی تنها برای فیلدهای عدد صحیح امکان‌پذیر هستند و پهنای بیتی به‌عنوان سومین آیتم در تاپل‌های _fields_ مشخص می‌شود:

>>> class Int(Structure):
...     _fields_ = [("first_16", c_int, 16),
...                 ("second_16", c_int, 16)]
...
>>> print(Int.first_16)
<ctypes.CField 'first_16' type=c_int, ofs=0, bit_size=16, bit_offset=0>
>>> print(Int.second_16)
<ctypes.CField 'second_16' type=c_int, ofs=0, bit_size=16, bit_offset=16>

توجه داشته باشید که تخصیص و چیدمان میدان‌های بیتی در حافظه در استاندارد C تعریف نشده‌اند؛ پیاده‌سازی آن‌ها وابسته به کامپایلر است. به‌طور پیش‌فرض، پایتون تلاش خواهد کرد با رفتار یک کامپایلر «بومی» برای پلتفرم فعلی مطابقت کند. برای جزئیات درباره رفتار پیش‌فرض و نحوه تغییر آن، ویژگی _layout_ را ببینید.

آرایه‌ها

آرایه‌ها دنباله‌هایی هستند که شامل تعداد ثابتی نمونه از یک نوع می‌شوند.

روش توصیه‌شده برای ایجاد انواع آرایه، ضرب یک نوع داده در یک عدد صحیح مثبت است:

TenPointsArrayType = POINT * 10

در اینجا مثالی از یک نوع داده تا حدی مصنوعی آمده است، ساختاری که در میان سایر موارد شامل ۴ POINT است:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class MyStruct(Structure):
...     _fields_ = [("a", c_int),
...                 ("b", c_float),
...                 ("point_array", POINT * 4)]
>>>
>>> print(len(MyStruct().point_array))
4
>>>

نمونه‌ها به روش معمول، با فراخوانی کلاس ایجاد می‌شوند:

arr = TenPointsArrayType()
for pt in arr:
    print(pt.x, pt.y)

کد بالا دنباله‌ای از سطرهای 0 0 را چاپ می‌کند، زیرا محتوای آرایه با صفرها مقداردهی اولیه شده است.

همچنین می‌توان مقدارده‌های اولیه از نوع درست را نیز تعیین کرد:

>>> from ctypes import *
>>> TenIntegers = c_int * 10
>>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
>>> print(ii)
<c_long_Array_10 object at 0x...>
>>> for i in ii: print(i, end=" ")
...
1 2 3 4 5 6 7 8 9 10
>>>

اشاره‌گرها

نمونه‌های اشاره‌گر با فراخوانی تابع pointer() بر روی یک نوع ctypes ایجاد می‌شوند:

>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)
>>>

نمونه‌های اشاره‌گر دارای یک ویژگی contents هستند که شیء مورد اشاره‌ی اشاره‌گر را بازمی‌گرداند، همان شیء i در بالا:

>>> pi.contents
c_long(42)
>>>

توجه داشته باشید که ctypes دارای بازگشت شیء اصلی (OOR) نیست، و هر بار که یک ویژگی را بازیابی می‌کنید، یک شیء جدید و معادل می‌سازد:

>>> pi.contents is i
False
>>> pi.contents is pi.contents
False
>>>

انتساب یک نمونه دیگر از c_int به ویژگی contents اشاره‌گر باعث می‌شود اشاره‌گر به مکان حافظه‌ای اشاره کند که این در آن ذخیره‌شده است:

>>> i = c_int(99)
>>> pi.contents = i
>>> pi.contents
c_long(99)
>>>

نمونه‌های اشاره‌گر همچنین می‌توانند با اعداد صحیح اندیس‌دهی شوند:

>>> pi[0]
99
>>>

انتساب به یک اندیس عدد صحیح، مقدار اشاره‌شده را تغییر می‌دهد:

>>> print(i)
c_long(99)
>>> pi[0] = 22
>>> print(i)
c_long(22)
>>>

همچنین می‌توانید از اندیس‌هایی غیر از ۰ استفاده کنید، اما باید بدانید چه کاری انجام می‌دهید، درست مانند C: می‌توانید به مکان‌های دلخواه حافظه دسترسی داشته باشید یا آن‌ها را تغییر دهید. به‌طور کلی، فقط زمانی از این قابلیت استفاده می‌کنید که یک اشاره‌گر از یک تابع C دریافت کرده‌اید و می‌دانید که آن اشاره‌گر در واقع به‌جای یک آیتم واحد، به یک آرایه اشاره می‌کند.

در پشت صحنه، تابع pointer() فراتر از صرف ایجاد نمونه‌های اشاره‌گر عمل می‌کند؛ ابتدا باید انواع اشاره‌گر را ایجاد کند. این کار با تابع POINTER() انجام می‌شود که هر نوعی از ctypes را می‌پذیرد و نوع جدیدی را برمی‌گرداند:

>>> PI = POINTER(c_int)
>>> PI
<class 'ctypes.LP_c_long'>
>>> PI(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: expected c_long instead of int
>>> PI(c_int(42))
<ctypes.LP_c_long object at 0x...>
>>>

فراخوانی نوع اشاره‌گر بدون آرگومان، یک اشاره‌گر NULL ایجاد می‌کند. اشاره‌گرهای NULL مقدار بولی False دارند:

>>> null_ptr = POINTER(c_int)()
>>> print(bool(null_ptr))
False
>>>

ctypes هنگام دسترسی به مقدار اشاره‌گرها، NULL را بررسی می‌کند (اما دسترسی به مقدار اشاره‌گرهای نامعتبر غیرNULL باعث فروپاشی پایتون می‌شود):

>>> null_ptr[0]
Traceback (most recent call last):
    ....
ValueError: NULL pointer access
>>>

>>> null_ptr[0] = 1234
Traceback (most recent call last):
    ....
ValueError: NULL pointer access
>>>

ایمنی نخ بدون GIL

از پایتون 3.13 به بعد، می‌توان GIL را در ساخت نخ‌آزاد (free-threaded build) غیرفعال کرد. در ctypes، خواندن و نوشتن همزمان به یک شیء واحد امن است، اما بین چندین شیء امن نیست:

>>> number = c_int(42)
>>> pointer_a = pointer(number)
>>> pointer_b = pointer(number)

در مثال بالا، اگر قفل سراسری مفسر (GIL) غیرفعال باشد، تنها در صورتی ایمن است که فقط یک شیء در یک زمان عملیات خواندن و نوشتن در آن نشانی را انجام دهد. بنابراین، pointer_a می‌تواند بین چندین نخ به اشتراک گذاشته شود و توسط چندین نخ در آن نوشته شود، اما تنها در صورتی که pointer_b نیز سعی در انجام همین کار نداشته باشد. اگر این موضوع مسئله‌ساز است، استفاده از threading.Lock را برای همگام‌سازی دسترسی به حافظه در نظر بگیرید:

>>> import threading
>>> lock = threading.Lock()
>>> # Thread 1
>>> with lock:
...    pointer_a.contents = 24
>>> # Thread 2
>>> with lock:
...    pointer_b.contents = 42

تبدیل نوع‌ها

معمولاً ctypes بررسی سخت‌گیرانه‌ی نوع را انجام می‌دهد. این بدان معناست که اگر POINTER(c_int) را در فهرست argtypes یک تابع یا به‌عنوان نوع یک فیلد عضو در تعریف ساختار داشته باشید، تنها نمونه‌هایی که دقیقاً از همان نوع هستند پذیرفته می‌شوند. استثناهایی بر این قاعده وجود دارد که در آن‌ها ctypes اشیای دیگر را می‌پذیرد. برای مثال، می‌توانید نمونه‌های آرایه‌ی سازگار را به‌جای انواع اشاره‌گر ارسال کنید. بنابراین، برای POINTER(c_int)، ctypes آرایه‌ای از c_int را می‌پذیرد:

>>> class Bar(Structure):
...     _fields_ = [("count", c_int), ("values", POINTER(c_int))]
...
>>> bar = Bar()
>>> bar.values = (c_int * 3)(1, 2, 3)
>>> bar.count = 3
>>> for i in range(bar.count):
...     print(bar.values[i])
...
1
2
3
>>>

علاوه بر این، اگر یک آرگومان تابع به‌صراحت به‌عنوان یک نوع اشاره‌گر (مانند POINTER(c_int)) در argtypes اعلان شده باشد، می‌توان یک شیء از نوع اشاره‌شده (در این مورد c_int) را به تابع ارسال کرد. در این حالت، ctypes تبدیل byref() لازم را به‌طور خودکار اعمال می‌کند.

برای تنظیم یک فیلد از نوع POINTER به NULL، می‌توانید None را انتساب دهید:

>>> bar.values = None
>>>

گاهی اوقات نمونه‌هایی از انواع ناسازگار دارید. در C، می‌توانید یک نوع را به نوع دیگری تبدیل کنید. ctypes تابع cast() را ارائه می‌دهد که می‌توان از آن به همان روش استفاده کرد. ساختار Bar تعریف‌شده در بالا، اشاره‌گرهای POINTER(c_int) یا آرایه‌های c_int را برای فیلد values خود می‌پذیرد، اما نمونه‌هایی از انواع دیگر را نمی‌پذیرد:

>>> bar.values = (c_byte * 4)()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: incompatible types, c_byte_Array_4 instance instead of LP_c_long instance
>>>

برای این موارد، تابع cast() مفید است.

از تابع cast() می‌توان برای تبدیل یک نمونه ctypes به اشاره‌گری به یک نوع داده ctypes متفاوت استفاده کرد. cast() دو پارامتر می‌گیرد: یک شیء ctypes که اشاره‌گر است یا می‌تواند به اشاره‌گری از یک نوع تبدیل شود، و یک نوع اشاره‌گر ctypes. این تابع یک نمونه از آرگومان دوم را برمی‌گرداند که به همان بلوک حافظه‌ی آرگومان اول ارجاع می‌دهد:

>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>
>>>

بنابراین، می‌توان از cast() برای انتساب ساختار به فیلد values در Bar استفاده کرد:

>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print(bar.values[0])
0
>>>

انواع ناقص

انواع ناقص ساختارها، اجتماعها یا آرایه‌هایی هستند که اعضای آن‌ها هنوز مشخص نشده‌اند. در C، آن‌ها با اعلان‌های پیش‌رو (forward declarations) مشخص می‌شوند، که بعداً تعریف می‌شوند:

struct cell; /* forward declaration */

struct cell {
    char *name;
    struct cell *next;
};

ترجمه مستقیم به کد ctypes به این صورت خواهد بود، اما کار نمی‌کند:

>>> class cell(Structure):
...     _fields_ = [("name", c_char_p),
...                 ("next", POINTER(cell))]
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in cell
NameError: name 'cell' is not defined
>>>

زیرا class cell جدید در خود دستور کلاس در دسترس نیست. در ctypes، می‌توانیم کلاس cell را تعریف کنیم و ویژگی _fields_ را بعداً، پس از دستور کلاس، تنظیم کنیم:

>>> from ctypes import *
>>> class cell(Structure):
...     pass
...
>>> cell._fields_ = [("name", c_char_p),
...                  ("next", POINTER(cell))]
>>>

بیایید آن را امتحان کنیم. دو نمونه از cell می‌سازیم و کاری می‌کنیم که آن‌ها به یکدیگر اشاره کنند، و در نهایت زنجیره‌ی اشاره‌گرها را چند بار دنبال می‌کنیم:

>>> c1 = cell()
>>> c1.name = b"foo"
>>> c2 = cell()
>>> c2.name = b"bar"
>>> c1.next = pointer(c2)
>>> c2.next = pointer(c1)
>>> p = c1
>>> for i in range(8):
...     print(p.name, end=" ")
...     p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>

توابع کال‌بک

ctypes امکان ایجاد اشاره‌گرهای تابع فراخوانی‌پذیر از C را از روی اشیاء فراخوانی‌پذیر پایتون فراهم می‌کند. به این موارد گاهی توابع کال‌بک گفته می‌شود.

ابتدا باید یک کلاس برای تابع کال‌بک ایجاد کنید. این کلاس قرارداد فراخوانی، نوع بازگشتی، و تعداد و انواع آرگومان‌هایی را که این تابع دریافت خواهد کرد، می‌داند.

تابع کارخانه‌ای CFUNCTYPE()، انواعی برای توابع کال‌بک با استفاده از قرارداد فراخوانی cdecl ایجاد می‌کند. در ویندوز، تابع کارخانه‌ای WINFUNCTYPE() انواعی برای توابع کال‌بک با استفاده از قرارداد فراخوانی stdcall ایجاد می‌کند.

هر دوی این توابع کارخانه‌ای با نوع نتیجه به‌عنوان اولین آرگومان و انواع آرگومان‌های مورد انتظار برای توابع کال‌بک به‌عنوان آرگومان‌های باقی‌مانده فراخوانی می‌شوند.

در اینجا مثالی را ارائه می‌دهم که از تابع qsort() از کتابخانه استاندارد C استفاده می‌کند؛ این تابع برای مرتب‌سازی آیتم‌ها با کمک یک تابع کال‌بک به کار می‌رود. از qsort() برای مرتب‌سازی آرایه‌ای از اعداد صحیح استفاده خواهد شد:

>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
>>> qsort = libc.qsort
>>> qsort.restype = None
>>>

qsort() باید با یک اشاره‌گر به داده‌ها برای مرتب‌سازی، تعداد آیتم‌های آرایه داده، اندازه یک آیتم، و یک اشاره‌گر به تابع مقایسه، یعنی کال‌بک، فراخوانی شود. سپس کال‌بک با دو اشاره‌گر به آیتم‌ها فراخوانی می‌شود و باید اگر آیتم اول کوچک‌تر از آیتم دوم باشد، یک عدد صحیح منفی، اگر برابر باشند، صفر، و در غیر این صورت یک عدد صحیح مثبت برگرداند.

بنابراین تابع کال‌بک ما اشاره‌گرهایی به اعداد صحیح دریافت می‌کند و باید یک عدد صحیح برگرداند. ابتدا type را برای تابع کال‌بک ایجاد می‌کنیم:

>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>>

برای شروع، در اینجا یک کال‌بک ساده آمده است که مقادیری را که به آن داده می‌شود نمایش می‌دهد:

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

نتیجه:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func)
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 5 7
py_cmp_func 1 7
>>>

اکنون می‌توانیم عملاً دو آیتم را مقایسه کنیم و نتیجه‌ای مفید برگردانیم:

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return a[0] - b[0]
...
>>>
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func))
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>

همان‌طور که به‌راحتی می‌توانیم بررسی کنیم، آرایه ما اکنون مرتب است:

>>> for i in ia: print(i, end=" ")
...
1 5 7 33 99
>>>

می‌توان از کارخانه‌های تابع به‌عنوان کارخانه‌های دکوراتور استفاده کرد، بنابراین می‌توانیم به این صورت بنویسیم:

>>> @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
... def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return a[0] - b[0]
...
>>> qsort(ia, len(ia), sizeof(c_int), py_cmp_func)
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>

توجه

اطمینان حاصل کنید که تا زمانی که اشیای CFUNCTYPE() از کد C استفاده می‌شوند، ارجاع‌ها به آن‌ها را نگه می‌دارید. ctypes این کار را نمی‌کند، و اگر شما این کار را نکنید، ممکن است آن‌ها زباله‌روبی شوند و هنگام انجام یک کال‌بک، باعث فروپاشی برنامه‌ی شما شوند.

همچنین توجه داشته باشید که اگر تابع کال‌بک در نخی فراخوانی شود که خارج از کنترل پایتون ایجاد شده است (مثلاً توسط کد خارجی که کال‌بک را فراخوانی می‌کند)، ctypes در هر فراخوانی یک نخ پایتون ساختگی جدید ایجاد می‌کند. این رفتار برای بیشتر اهداف صحیح است، اما به این معناست که مقادیر ذخیره‌شده با threading.local در میان کال‌بک‌های مختلف باقی نمی‌مانند، حتی اگر آن فراخوانی‌ها از همان نخ C انجام شوند.

دسترسی به مقادیر اکسپورتشده از DLLها

برخی کتابخانه‌های اشتراکی نه‌تنها توابع، بلکه متغیرها را نیز اکسپورت می‌کنند. نمونه‌ای در خود کتابخانه پایتون Py_Version است، شماره نسخه ران‌تایم پایتون که در یک عدد صحیح ثابت کدگذاری‌شده است.

ctypes می‌تواند با متدهای کلاس in_dll() مربوط به نوع، به مقادیری مانند این دسترسی پیدا کند. pythonapi نمادی از پیش تعریف‌شده است که دسترسی به API C پایتون را فراهم می‌کند:

>>> version = ctypes.c_int.in_dll(ctypes.pythonapi, "Py_Version")
>>> print(hex(version.value))
0x30c00a0

یک مثال گسترده که همچنین کاربرد اشاره‌گرها را نشان می‌دهد، به اشاره‌گر PyImport_FrozenModules اکسپورتشده توسط پایتون دسترسی پیدا می‌کند.

با نقل‌قول از مستندات برای آن مقدار:

این اشاره‌گر به‌گونه‌ای مقداردهی اولیه شده است که به آرایه‌ای از رکوردهای _frozen اشاره کند؛ این آرایه با رکوردی که تمام اعضای آن NULL یا صفر هستند، پایان می‌یابد. هنگامی که یک ماژول فریز (frozen module) ایمپورت می‌شود، در این جدول جستجو می‌شود. کد شخص ثالث می‌تواند با دستکاری این اشاره‌گر، مجموعه‌ای از ماژول‌های فریز را که به‌صورت پویا ایجاد شده است فراهم کند.

بنابراین دستکاری این اشاره‌گر حتی می‌تواند مفید باشد. برای محدود کردن اندازه‌ی مثال، فقط نشان می‌دهیم که چگونه می‌توان این جدول را با ctypes خواند:

>>> from ctypes import *
>>>
>>> class struct_frozen(Structure):
...     _fields_ = [("name", c_char_p),
...                 ("code", POINTER(c_ubyte)),
...                 ("size", c_int),
...                 ("get_code", POINTER(c_ubyte)),  # Function pointer
...                ]
...
>>>

ما نوع داده _frozen را تعریف کرده‌ایم، بنابراین می‌توانیم اشاره‌گر به جدول را دریافت کنیم:

>>> FrozenTable = POINTER(struct_frozen)
>>> table = FrozenTable.in_dll(pythonapi, "_PyImport_FrozenBootstrap")
>>>

از آن‌جا که table یک اشاره‌گر (pointer) به آرایه‌ای از رکوردهای struct_frozen است، می‌توانیم آن را پیمایش کنیم، اما فقط باید مطمئن شویم که حلقه ما خاتمه می‌یابد، زیرا اشاره‌گرها اندازه ندارند. دیر یا زود احتمالاً با نقض دسترسی یا چیزی مشابه فرومی‌پاشند، بنابراین بهتر است هنگامی که به ورودی NULL می‌رسیم، از حلقه خارج شویم:

>>> for item in table:
...     if item.name is None:
...         break
...     print(item.name.decode("ascii"), item.size)
...
_frozen_importlib 31764
_frozen_importlib_external 41499
zipimport 12345
>>>

این واقعیت که پایتون استاندارد یک ماژول فریز (frozen module) و یک بسته فریز (frozen package) دارد (که با منفی بودن عضو size مشخص می‌شود) چندان شناخته‌شده نیست؛ این موارد فقط برای آزمایش استفاده می‌شوند. برای مثال، آن را با import __hello__ امتحان کنید.

شگفتی‌ها

در ctypes چند حالت مرزی وجود دارد که ممکن است انتظار چیزی غیر از آنچه واقعاً رخ می‌دهد داشته باشید.

مثال زیر را در نظر بگیرید:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class RECT(Structure):
...     _fields_ = ("a", POINT), ("b", POINT)
...
>>> p1 = POINT(1, 2)
>>> p2 = POINT(3, 4)
>>> rc = RECT(p1, p2)
>>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y)
1 2 3 4
>>> # now swap the two points
>>> rc.a, rc.b = rc.b, rc.a
>>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y)
3 4 3 4
>>>

هم. قطعاً انتظار داشتیم آخرین دستور 3 4 1 2 را چاپ کند. چه اتفاقی افتاد؟ مراحل خط rc.a, rc.b = rc.b, rc.a در بالا به شرح زیر است:

>>> temp0, temp1 = rc.b, rc.a
>>> rc.a = temp0
>>> rc.b = temp1
>>>

توجه داشته باشید که temp0 و temp1 اشیایی هستند که هنوز از بافر درونی شیء rc در بالا استفاده می‌کنند. بنابراین اجرای rc.a = temp0 محتوای بافر temp0 را به بافر rc رونوشت می‌کند. این کار به نوبه خود، محتوای temp1 را تغییر می‌کند. بنابراین، آخرین انتساب rc.b = temp1، اثر مورد انتظار را ندارد.

به خاطر داشته باشید که بازیابی زیرشیء‌ها از Structure، Unions و Arrays، زیرشیء را کپی نمی‌کند، بلکه شیء پوششی‌ای را بازیابی می‌کند که به بافر زیرین شیء ریشه دسترسی دارد.

مثال دیگری که ممکن است رفتاری متفاوت از آنچه انتظار می‌رود داشته باشد، این است:

>>> s = c_char_p()
>>> s.value = b"abc def ghi"
>>> s.value
b'abc def ghi'
>>> s.value is s.value
False
>>>

توجه

مقدار اشیاء نمونه‌سازی‌شده از c_char_p فقط می‌تواند روی بایت‌ها یا اعداد صحیح تنظیم شود.

چرا False چاپ می‌شود؟ نمونه‌های ctypes اشیایی هستند که شامل یک بلوک حافظه به‌همراه چند descriptor برای دسترسی به محتوای حافظه هستند. ذخیره کردن یک شیء پایتون در بلوک حافظه، خود شیء را ذخیره نمی‌کند، بلکه contents آن شیء ذخیره می‌شود. دسترسی دوباره به محتوا، هر بار یک شیء پایتون جدید می‌سازد!

انواع داده با اندازه متغیر

ctypes پشتیبانی تا حدی برای آرایه‌ها و ساختارهای با اندازه‌ی متغیر فراهم می‌کند.

می‌توان از تابع resize() برای تغییر اندازه بافر حافظه یک شیء ctypes موجود استفاده کرد. این تابع شیء را به‌عنوان آرگومان اول و اندازه درخواستی بر حسب بایت را به‌عنوان آرگومان دوم می‌گیرد. نمی‌توان بلوک حافظه را کوچک‌تر از بلوک حافظه طبیعی تعیین‌شده توسط نوع شیء کرد؛ در صورت تلاش برای این کار، استثنای ValueError پرتاب می‌شود:

>>> short_array = (c_short * 4)()
>>> print(sizeof(short_array))
8
>>> resize(short_array, 4)
Traceback (most recent call last):
    ...
ValueError: minimum size is 8
>>> resize(short_array, 32)
>>> sizeof(short_array)
32
>>> sizeof(type(short_array))
8
>>>

این خوب و مناسب است، اما چگونه می‌توان به المان‌های اضافی موجود در این آرایه دسترسی داشت؟ از آنجا که نوع هنوز تنها از ۴ المان آگاه است، هنگام دسترسی به سایر المان‌ها با خطا مواجه می‌شویم:

>>> short_array[:]
[0, 0, 0, 0]
>>> short_array[7]
Traceback (most recent call last):
    ...
IndexError: invalid index
>>>

راه دیگر برای استفاده از انواع داده با اندازه‌ی متغیر با ctypes این است که از ماهیت پویا پایتون استفاده کنید و نوع داده را پس از این‌که اندازه‌ی موردنیاز از قبل مشخص شد، به‌صورت مورد به مورد (باز)تعریف کنید.

مرجع ctypes

یافتن کتابخانه‌های مشترک

هنگام برنامه‌نویسی در یک زبان کامپایل‌شده، در زمان کامپایل/لینک کردن یک برنامه و نیز هنگامی که برنامه اجرا می‌شود، به کتابخانه‌های اشتراکی دسترسی پیدا می‌شود.

هدف از تابع find_library() این است که یک کتابخانه را به شیوه‌ای مشابه آنچه کامپایلر یا بارگذار ران‌تایم انجام می‌دهد بیابد (در سکوهایی که چند نسخه از یک کتابخانه اشتراکی دارند، باید جدیدترین نسخه بارگذاری شود)، در حالی که بارگذارهای کتابخانه ctypes مانند زمانی که یک برنامه اجرا می‌شود عمل می‌کنند و بارگذار ران‌تایم را مستقیماً فراخوانی می‌کنند.

ماژول ctypes.util تابعی فراهم می‌کند که می‌تواند به تعیین کتابخانه‌ای که باید بارگذاری شود کمک کند.

ctypes.util.find_library(name)

تلاش می‌کند یک کتابخانه را پیدا کند و یک مسیر را برگرداند. name نام کتابخانه بدون هیچ پیشوندی مانند lib، پسوندی مانند .so، .dylib یا شماره‌ی نسخه است (این همان قالبی است که برای گزینه‌ی پیونددهنده‌ی posix یعنی -l استفاده می‌شود). اگر هیچ کتابخانه‌ای پیدا نشود، None را برمی‌گرداند.

عملکرد دقیق، وابسته به سیستم است.

در لینوکس، find_library() تلاش می‌کند برنامه‌های خارجی (/sbin/ldconfig، gcc، objdump و ld) را اجرا کند تا پرونده کتابخانه را پیدا کند. نام پرونده کتابخانه را برمی‌گرداند.

توجه داشته باشید که اگر خروجی این برنامه‌ها با پیونددهنده‌ی پویایی که پایتون از آن استفاده می‌کند مطابقت نداشته باشد، نتیجه این تابع ممکن است گمراه‌کننده باشد.

تغییر یافته در نسخه‌ی 3.6: در لینوکس، هنگام جست‌وجوی کتابخانه‌ها، اگر کتابخانه‌ای به هیچ روش دیگری یافت نشود، مقدار متغیر محیطی LD_LIBRARY_PATH استفاده می‌شود.

در اینجا چند مثال آمده است:

>>> from ctypes.util import find_library
>>> find_library("m")
'libm.so.6'
>>> find_library("c")
'libc.so.6'
>>> find_library("bz2")
'libbz2.so.1.0'
>>>

در macOS و Android، find_library() از طرح‌های نام‌گذاری و مسیرهای استاندارد سیستم برای پیدا کردن کتابخانه استفاده می‌کند و در صورت موفقیت، یک نام مسیر کامل را برمی‌گرداند:

>>> from ctypes.util import find_library
>>> find_library("c")
'/usr/lib/libc.dylib'
>>> find_library("m")
'/usr/lib/libm.dylib'
>>> find_library("bz2")
'/usr/lib/libbz2.dylib'
>>> find_library("AGL")
'/System/Library/Frameworks/AGL.framework/AGL'
>>>

در ویندوز، find_library() در امتداد مسیر جستجوی سیستم جستجو می‌کند و مسیر کامل را برمی‌گرداند، اما از آنجا که هیچ الگوی نام‌گذاری از پیش تعریف‌شده‌ای وجود ندارد، فراخوانی مانند find_library("c") با شکست مواجه می‌شود و None را برمی‌گرداند.

اگر در حال پوشش دادن یک کتابخانه اشتراکی با ctypes هستید، ممکن است بهتر باشد نام کتابخانه اشتراکی را در زمان توسعه تعیین کنید و آن را به‌صورت مقدار ثابت (hardcode) در ماژول پوششی قرار دهید، به‌جای استفاده از find_library() برای یافتن کتابخانه در ران‌تایم.

فهرست کتابخانه‌های اشتراکی بارگذاری‌شده

هنگام نوشتن کدی که به کد بارگذاری‌شده از کتابخانه‌های اشتراکی متکی است، دانستن اینکه کدام کتابخانه‌های اشتراکی از قبل در فرایند جاری بارگذاری شده‌اند، می‌تواند مفید باشد.

ماژول ctypes.util تابع dllist() را فراهم می‌کند، که APIهای متفاوتی را که پلتفرم‌های گوناگون ارائه می‌دهند فراخوانی می‌کند تا به تعیین اینکه کدام کتابخانه‌های اشتراکی از قبل در فرایند جاری بارگذاری شده‌اند کمک کند.

خروجی دقیق این تابع به سیستم وابسته است. در بیشتر پلتفرم‌ها، نخستین آیتم این فهرست خود فرایند جاری را نشان می‌دهد، که ممکن است یک رشته خالی باشد. برای مثال، در لینوکس مبتنی بر glibc، ممکن است مقدار بازگشتی به شکل زیر باشد:

>>> from ctypes.util import dllist
>>> dllist()
['', 'linux-vdso.so.1', '/lib/x86_64-linux-gnu/libm.so.6', '/lib/x86_64-linux-gnu/libc.so.6', ... ]

بارگذاری کتابخانه‌های مشترک

چندین روش برای بارگذاری کتابخانه‌های مشترک در فرایند پایتون وجود دارد. یکی از روش‌ها، ایجاد نمونه‌ای از CDLL یا یکی از زیرکلاس‌های آن است:

class ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None)

نمایانگر یک کتابخانه اشتراکی بارگذاری‌شده است.

توابع این کتابخانه از قرارداد فراخوانی استاندارد C استفاده می‌کنند و فرض می‌شود که int را برمی‌گردانند. global interpreter lock پایتون پیش از فراخوانی هر تابع اکسپورتشده توسط این کتابخانه‌ها آزاد می‌شود و پس از آن دوباره گرفته می‌شود. برای رفتار متفاوت توابع، از یک زیرکلاس استفاده کنید: OleDLL، WinDLL یا PyDLL.

اگر یک handle موجود برای یک کتابخانه اشتراکی از پیش بارگذاری‌شده دارید، می‌توان آن را به‌عنوان آرگومان handle برای دربرگرفتن کتابخانه بازشده در قالب یک شیء جدید CDLL ارسال کرد. در این حالت، name فقط برای تنظیم ویژگی _name استفاده می‌شود، اما ممکن است تنظیم و/یا اعتبارسنجی شود.

اگر handle برابر None باشد، از تابع dlopen(3) یا LoadLibrary() پلتفرم زیرین برای بارگذاری کتابخانه در فرایند و دریافت یک دسته برای آن استفاده می‌شود.

name مسیر کتابخانه اشتراکی برای باز کردن است. اگر name شامل جداکننده مسیر نباشد، کتابخانه به شیوه‌ای وابسته به پلتفرم یافت می‌شود.

در سیستم‌های غیرویندوزی، name می‌تواند None باشد. در این حالت، dlopen() با NULL فراخوانی می‌شود، که برنامه‌ی اصلی را به‌عنوان یک «کتابخانه» باز می‌کند. (برخی سیستم‌ها اگر name خالی باشد نیز همین کار را انجام می‌دهند؛ None/NULL قابل‌حمل‌تر است.)

جزئیات پیاده‌سازی CPython

از آنجا که CPython به libc پیوند داده شده است، اغلب از یک name با مقدار None برای دسترسی به کتابخانه استاندارد C استفاده می‌شود:

>>> printf = ctypes.CDLL(None).printf
>>> printf.argtypes = [ctypes.c_char_p]
>>> printf(b"hello\n")
hello
6

برای دسترسی به Python C API، ترجیحاً از ctypes.pythonapi استفاده کنید که در همه پلتفرم‌ها کار می‌کند.

از پارامتر mode می‌توان برای مشخص کردن نحوه بارگذاری کتابخانه استفاده کرد. برای جزئیات، به صفحه راهنمای dlopen(3) مراجعه کنید. در ویندوز، mode نادیده گرفته می‌شود. در سیستم‌های POSIX، RTLD_NOW همیشه اضافه می‌شود و قابل پیکربندی نیست.

پارامتر use_errno، هنگامی که روی true تنظیم شود، سازوکاری در ctypes را فعال می‌کند که امکان دسترسی به شماره‌ی خطای سیستم errno را به‌شکلی امن فراهم می‌کند. ctypes یک نسخه‌ی محلی نخی (thread-local) از متغیر errno سیستم را نگه می‌دارد؛ اگر توابع خارجی ساخته‌شده با use_errno=True را فراخوانی کنید، آنگاه مقدار errno پیش از فراخوانی تابع با نسخه‌ی خصوصی ctypes مبادله می‌شود؛ همین مبادله بلافاصله پس از فراخوانی تابع نیز رخ می‌دهد.

تابع ctypes.get_errno() مقدار نسخه خصوصی ctypes را برمی‌گرداند، و تابع ctypes.set_errno() نسخه خصوصی ctypes را به یک مقدار جدید تغییر می‌دهد و مقدار پیشین را برمی‌گرداند.

پارامتر use_last_error، هنگامی که روی true تنظیم شود، همان سازوکار را برای کد خطای ویندوز، که توسط توابع API ویندوز GetLastError() و SetLastError() مدیریت می‌شود، فعال می‌کند؛ از ctypes.get_last_error() و ctypes.set_last_error() برای درخواست و تغییر نسخه‌ی خصوصی ctypes از کد خطای ویندوز استفاده می‌شود.

پارامتر winmode در ویندوز برای مشخص کردن چگونگی بارگذاری کتابخانه استفاده می‌شود (زیرا mode در نظر گرفته نمی‌شود). این پارامتر هر مقداری را می‌پذیرد که برای پارامتر پرچم‌های LoadLibraryEx در Win32 API معتبر باشد. در صورت حذف، پیش‌فرض استفاده از پرچم‌هایی است که به امن‌ترین بارگذاری DLL منجر می‌شوند و از مسائلی مانند ربایش DLL (DLL hijacking) جلوگیری می‌کنند. ارسال مسیر کامل DLL، امن‌ترین راه برای اطمینان از بارگذاری کتابخانه و وابستگی‌های صحیح است.

در ویندوز، ایجاد یک نمونه از CDLL ممکن است حتی اگر نام DLL وجود داشته باشد شکست بخورد. هنگامی که یک DLL وابسته به DLL بارگذاری‌شده پیدا نشود، یک خطای OSError با پیام "[WinError 126] The specified module could not be found". پرتاب می‌شود. این پیام خطا حاوی نام DLL مفقود نیست، زیرا API ویندوز این اطلاعات را برنمی‌گرداند و این موضوع، تشخیص این خطا را دشوار می‌کند. برای رفع این خطا و تعیین این‌که کدام DLL پیدا نشده است، باید فهرست DLLهای وابسته را پیدا کنید و با استفاده از ابزارهای اشکال‌زدایی و ردگیری ویندوز مشخص کنید کدام‌یک پیدا نشده است.

همچنین ملاحظه نمائید

ابزار DUMPBIN مایکروسافت -- ابزاری برای یافتن وابسته‌های DLL.

تغییر یافته در نسخه‌ی 3.8: پارامتر winmode اضافه شد.

تغییر یافته در نسخه‌ی 3.12: پارامتر name اکنون می‌تواند یک path-like object باشد.

نمونه‌های این کلاس هیچ متد عمومی ندارند. می‌توان به توابع اکسپورتشده توسط کتابخانه مشترک به‌صورت صفت یا از طریق اندیس دسترسی داشت. لطفاً توجه داشته باشید که دسترسی به تابع از طریق یک صفت، نتیجه را در نهانگاه ذخیره می‌کند و بنابراین دسترسی مکرر به آن هر بار همان شیء را برمی‌گرداند. از سوی دیگر، دسترسی به آن از طریق یک اندیس هر بار یک شیء جدید برمی‌گرداند:

>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6")  # On Linux
>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False

ویژگی‌های عمومی زیر در دسترس هستند. نام آن‌ها با یک زیرخط آغاز می‌شود تا با نام توابع اکسپورتشده تداخل نداشته باشد:

_handle

دسته‌ی سیستمی که برای دسترسی به کتابخانه استفاده می‌شود.

_name

نام کتابخانه‌ای که در سازنده ارسال می‌شود.

class ctypes.OleDLL

برای اطلاعات مشترک، CDLL، ابرکلاس را ببینید.

توابع این کتابخانه از قرارداد فراخوانی stdcall استفاده می‌کنند، و فرض می‌شود کد HRESULT مخصوص ویندوز را برمی‌گردانند. مقادیر HRESULT حاوی اطلاعاتی هستند که مشخص می‌کنند آیا فراخوانی تابع ناموفق بوده است یا موفق، به همراه کد خطای اضافی. اگر مقدار بازگشتی نشان‌دهنده‌ی شکست باشد، یک OSError به‌طور خودکار پرتاب می‌شود.

تغییر یافته در نسخه‌ی 3.3: پیش‌تر WindowsError پرتاب می‌شد، که اکنون نام مستعاری از OSError است.

class ctypes.WinDLL

برای اطلاعات مشترک، CDLL، ابرکلاس را ببینید.

توابع در این کتابخانه‌ها از قرارداد فراخوانی stdcall استفاده می‌کنند، و فرض می‌شود که به‌طور پیش‌فرض int برمی‌گردانند.

class ctypes.PyDLL

برای اطلاعات مشترک، CDLL، ابرکلاس را ببینید.

هنگامی که توابع این کتابخانه فراخوانی می‌شوند، GIL پایتون در حین فراخوانی تابع آزاد نمی‌شود، و پس از اجرای تابع، پرچم خطای پایتون بررسی می‌شود. اگر پرچم خطا تنظیم شده باشد، یک استثنای پایتون پرتاب می‌شود.

بنابراین، این تنها برای فراخوانی مستقیم توابع Python C API مفید است.

ctypes.RTLD_GLOBAL

پرچمی که به‌عنوان پارامتر mode استفاده می‌شود. در سکوهای فاقد این پرچم، به‌صورت عدد صحیح صفر تعریف شده است.

ctypes.RTLD_LOCAL

پرچمی برای استفاده به‌عنوان پارامتر mode. در سکوهایی که این در دسترس نیست، همانند RTLD_GLOBAL است.

ctypes.DEFAULT_MODE

حالت پیش‌فرضی که برای بارگذاری کتابخانه‌های اشتراکی به کار می‌رود. در OSX 10.3، این RTLD_GLOBAL است، در غیر این صورت همانند RTLD_LOCAL است.

همچنین می‌توان کتابخانه‌های اشتراکی را با استفاده از یکی از اشیاء از پیش ساخته‌شده، که نمونه‌هایی از کلاس LibraryLoader هستند، بارگذاری کرد؛ خواه با فراخوانی متد LoadLibrary()، خواه با بازیابی کتابخانه به‌عنوان ویژگیِ نمونه‌ی بارگذار.

class ctypes.LibraryLoader(dlltype)

کلاسی که کتابخانه‌های اشتراکی را بارگذاری می‌کند. dlltype باید یکی از انواع CDLL، PyDLL، WinDLL یا OleDLL باشد.

__getattr__() رفتار ویژه‌ای دارد: این متد امکان بارگذاری یک کتابخانه مشترک را با دسترسی به آن به‌عنوان یک ویژگی از یک نمونه بارگذار کتابخانه فراهم می‌کند. نتیجه در نهانگاه ذخیره می‌شود، بنابراین دسترسی‌های مکرر به ویژگی، هر بار همان کتابخانه را برمی‌گردانند.

LoadLibrary(name)

یک کتابخانه اشتراکی را در فرایند بارگذاری می‌کند و آن را برمی‌گرداند. این متد همیشه یک نمونه جدید از کتابخانه را برمی‌گرداند.

این بارگذارهای کتابخانه‌ای از پیش ساخته‌شده در دسترس هستند:

ctypes.cdll

نمونه‌های CDLL را ایجاد می‌کند.

ctypes.windll

نمونه‌های WinDLL را ایجاد می‌کند.

ctypes.oledll

نمونه‌های OleDLL را ایجاد می‌کند.

ctypes.pydll

نمونه‌های PyDLL را ایجاد می‌کند.

برای دسترسی مستقیم به API پایتون C، یک شیء کتابخانه مشترک پایتون آماده استفاده در دسترس است:

ctypes.pythonapi

نمونه‌ای از PyDLL که توابع Python C API را به‌عنوان ویژگی‌ها در دسترس قرار می‌دهد. توجه داشته باشید که فرض می‌شود همه‌ی این توابع مقداری از نوع C int را برمی‌گردانند، که البته همیشه درست نیست، بنابراین برای استفاده از این توابع باید ویژگی restype صحیح را اختصاص دهید.

بارگذاری یک کتابخانه از طریق هر یک از این اشیاء، یک رویداد حسابرسی ctypes.dlopen را با آرگومان رشته‌ای name، نام استفاده‌شده برای بارگذاری کتابخانه، پرتاب می‌کند.

دسترسی به یک تابع در یک کتابخانه بارگذاری‌شده، باعث پرتاب یک رویداد حسابرسی ctypes.dlsym با آرگومان‌های library (شیء کتابخانه) و name (نام نماد به‌صورت یک رشته یا عدد صحیح) می‌شود.

در مواردی که به جای شیء، تنها دسته‌ی کتابخانه در دسترس باشد، دسترسی به یک تابع، رویداد حسابرسی ctypes.dlsym/handle را با آرگومان‌های handle (دسته‌ی خام کتابخانه) و name پرتاب می‌کند.

توابع خارجی

همان‌طور که در بخش پیشین توضیح داده شد، توابع خارجی به‌عنوان ویژگی‌های کتابخانه‌های اشتراکی بارگذاری‌شده قابل دسترسی هستند. اشیای تابعی که به این روش ایجاد می‌شوند، به‌طور پیش‌فرض هر تعداد آرگومان را می‌پذیرند، هر نمونه داده‌ای از ctypes را به‌عنوان آرگومان می‌پذیرند و نوع نتیجه پیش‌فرضی که توسط بارگذار کتابخانه مشخص شده است را برمی‌گردانند.

آن‌ها نمونه‌هایی از یک کلاس محلی خصوصی _FuncPtr هستند (در ctypes در معرض نیست) که از کلاس خصوصی _CFuncPtr ارث می‌برد:

>>> import ctypes
>>> lib = ctypes.CDLL(None)
>>> issubclass(lib._FuncPtr, ctypes._CFuncPtr)
True
>>> lib._FuncPtr is ctypes._CFuncPtr
False
class ctypes._CFuncPtr

کلاس پایه برای توابع خارجی فراخوانی‌پذیر در C.

نمونه‌های توابع خارجی نیز انواع داده سازگار با C هستند؛ این نمونه‌ها اشاره‌گرهای توابع C را نشان می‌دهند.

این رفتار را می‌توان با انتساب به ویژگی‌های خاص شیء تابع خارجی سفارشی کرد.

restype

برای مشخص کردن نوع نتیجه تابع خارجی، یک نوع ctypes را انتساب کنید. برای void، تابعی که چیزی را برنمی‌گرداند، از None استفاده کنید.

می‌توان یک شیء پایتونی فراخوانی‌پذیر را که یک نوع ctypes نیست، انتساب داد؛ در این صورت فرض می‌شود که تابع یک int در C را برمی‌گرداند و شیء فراخوانی‌پذیر با این عدد صحیح فراخوانی می‌شود تا امکان پردازش بیشتر یا بررسی خطا فراهم شود. استفاده از این روش منسوخ است؛ برای پس‌پردازش یا بررسی خطای انعطاف‌پذیرتر، از یک نوع داده ctypes به‌عنوان restype استفاده کنید و یک شیء فراخوانی‌پذیر را به ویژگی errcheck انتساب دهید.

argtypes

برای مشخص کردن انواع آرگومان‌هایی که تابع می‌پذیرد، یک تاپل از انواع ctypes را اختصاص دهید. توابعی که از قرارداد فراخوانی stdcall استفاده می‌کنند، فقط می‌توانند با همان تعداد آرگومانی که برابر با طول این تاپل است، فراخوانی شوند؛ توابعی که از قرارداد فراخوانی C استفاده می‌کنند، آرگومان‌های اضافی و مشخص‌نشده را نیز می‌پذیرند.

هنگامی که یک تابع خارجی فراخوانی می‌شود، هر آرگومان واقعی به متد کلاس from_param() آیتم‌های موجود در تاپل argtypes ارسال می‌شود؛ این متد امکان سازگار کردن آرگومان واقعی با شیءای را فراهم می‌کند که تابع خارجی می‌پذیرد. برای مثال، یک آیتم c_char_p در تاپل argtypes، رشته‌ای را که به‌عنوان آرگومان ارسال‌شده است، با استفاده از قوانین تبدیل ctypes به یک شیء bytes تبدیل می‌کند.

جدید: اکنون می‌توان آیتم‌هایی را در argtypes قرار داد که از انواع ctypes نیستند، اما هر آیتم باید یک متد from_param() داشته باشد که مقداری قابل‌استفاده به‌عنوان آرگومان (عدد صحیح، رشته، نمونه ctypes) برمی‌گرداند. این امکان تعریف آداپتورهایی را می‌دهد که می‌توانند اشیاء سفارشی را به‌عنوان پارامترهای تابع سازگار کنند.

errcheck

یک تابع پایتون یا یک شیء فراخوانی‌پذیر دیگر را به این ویژگی اختصاص دهید. این شیء قابل فراخوانی با سه آرگومان یا بیشتر فراخوانی می‌شود:

callable(result, func, arguments)

result چیزی است که تابع خارجی برمی‌گرداند، همان‌طور که توسط ویژگی restype مشخص شده است.

func خودِ شیء تابع خارجی است، این امکان را می‌دهد که از همان شیء فراخوانی‌پذیر برای بررسی یا پس‌پردازش نتایج چندین تابع مجدداً استفاده شود.

arguments یک تاپل حاوی پارامترهایی است که در ابتدا به فراخوانی تابع ارسال‌شده‌اند؛ این امر امکان ویژه‌سازی رفتار بر اساس آرگومان‌های استفاده‌شده را فراهم می‌کند.

شیءای که این تابع برمی‌گرداند، از فراخوانی تابع خارجی برگردانده خواهد شد، اما همچنین می‌تواند مقدار نتیجه را بررسی کند و در صورت ناموفق بودن فراخوانی تابع خارجی، یک استثنا پرتاب کند.

در ویندوز، هنگامی که یک فراخوانی تابع خارجی یک استثنای سیستمی را پرتاب می‌کند (برای مثال، به‌دلیل نقض دسترسی)، آن استثنا گرفته می‌شود و با یک استثنای پایتون مناسب جایگزین می‌گردد. علاوه بر این، یک رویداد حسابرسی ctypes.set_exception با آرگومان code پرتاب می‌شود که به یک قلاب حسابرسی اجازه می‌دهد استثنا را با استثنای خود جایگزین کند.

برخی روش‌های فراخوانی توابع خارجی و همچنین برخی از توابع این ماژول ممکن است رویداد حسابرسی ctypes.call_function را با آرگومان‌های function pointer و arguments پرتاب کنند.

پیش‌نمونه‌های تابع

همچنین می‌توان توابع خارجی را با نمونه‌سازی از پیش‌نمونه‌های تابع (function prototypes) ایجاد کرد. پیش‌نمونه‌های تابع مشابه پیش‌نمونه‌های تابع در C هستند؛ آن‌ها یک تابع را (نوع بازگشتی، انواع آرگومان‌ها، قرارداد فراخوانی) بدون تعریف پیاده‌سازی توصیف می‌کنند. توابع کارخانه باید با نوع نتیجه دلخواه و انواع آرگومان‌های تابع فراخوانی شوند و می‌توانند به‌عنوان کارخانه‌های دکوراتور استفاده شوند و به همین عنوان، از طریق سینتکس @wrapper به توابع اعمال شوند. برای نمونه‌ها، توابع کال‌بک را ببینید.

ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)

پیش‌نمونه تابع برگردانده‌شده، توابعی را ایجاد می‌کند که از قرارداد فراخوانی استاندارد C استفاده می‌کنند. تابع در حین فراخوانی، GIL را آزاد می‌کند. اگر use_errno روی true تنظیم شود، نسخه‌ی خصوصی ctypes از متغیر سیستمی errno پیش و پس از فراخوانی با مقدار واقعی errno مبادله می‌شود؛ use_last_error نیز همین کار را برای کد خطای ویندوز انجام می‌دهد.

ctypes.WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)

پیش‌نمونه تابع برگشتی، توابعی را ایجاد می‌کند که از قرارداد فراخوانی stdcall استفاده می‌کنند. تابع در حین فراخوانی، GIL را آزاد خواهد کرد. use_errno و use_last_error همان معنای بالا را دارند.

ctypes.PYFUNCTYPE(restype, *argtypes)

پیش‌نمونه تابع برگردانده‌شده، توابعی ایجاد می‌کند که از قرارداد فراخوانی پایتون استفاده می‌کنند. تابع در حین فراخوانی، GIL را آزاد نخواهد کرد.

می‌توان پیش‌نمونه‌های تابع ایجادشده توسط این توابع کارخانه‌ای را به روش‌های مختلف نمونه‌سازی کرد، بسته به نوع و تعداد پارامترها در فراخوانی:

prototype(address)

یک تابع خارجی را در آدرس مشخص‌شده برمی‌گرداند؛ این آدرس باید یک عدد صحیح باشد.

prototype(callable)

یک تابع فراخوانی‌پذیر در C (یک تابع کال‌بک) از یک callable در پایتون ایجاد کنید.

prototype(func_spec[, paramflags])

یک تابع خارجی اکسپورتشده از یک کتابخانه اشتراکی را بازمی‌گرداند. func_spec باید یک تاپل دوتایی به صورت (name_or_ordinal, library) باشد. آیتم اول، نام تابع اکسپورتشده به صورت رشته، یا شماره ترتیبی تابع اکسپورتشده به صورت عدد صحیح کوچک است. آیتم دوم، نمونه‌ای از کتابخانه اشتراکی است.

prototype(vtbl_index, name[, paramflags[, iid]])

یک تابع خارجی را برمی‌گرداند که یک متد COM را فراخوانی خواهد کرد. vtbl_index اندیس در جدول توابع مجازی است، یک عدد صحیح نامنفی کوچک. name نام متد COM است. iid یک اشاره‌گر اختیاری به شناسه رابط است که در گزارش خطای گسترش‌یافته استفاده می‌شود.

اگر iid تعیین‌نشده باشد، در صورت شکست فراخوانی متد COM، استثنای OSError پرتاب می‌شود. اگر iid تعیین‌شده باشد، در عوض استثنای COMError پرتاب می‌شود.

متدهای COM از یک قرارداد فراخوانی خاص استفاده می‌کنند: آن‌ها علاوه بر پارامترهایی که در تاپل argtypes مشخص شده‌اند، به یک اشاره‌گر به رابط COM به‌عنوان اولین آرگومان نیاز دارند.

پارامتر اختیاری paramflags پوششی‌های تابع خارجی را با امکانات بسیار بیشتری نسبت به قابلیت‌های توصیف‌شده در بالا ایجاد می‌کند.

paramflags باید یک تاپلهم‌طول با argtypes باشد.

هر آیتم در این تاپل شامل اطلاعات بیشتری درباره یک پارامتر است و باید یک تاپل شامل یک، دو یا سه آیتم باشد.

اولین آیتم یک عدد صحیح است که شامل ترکیبی از پرچم‌های جهت برای پارامتر است:

1

یک پارامتر ورودی برای تابع مشخص می‌کند.

2

پارامتر خروجی. تابع خارجی مقداری را پر می‌کند.

4

پارامتر ورودی که مقدار پیش‌فرض آن عدد صحیح صفر است.

آیتم دوم که اختیاری است، نام پارامتر به‌صورت رشته است. اگر این مورد تعیین شود، می‌توان تابع خارجی را با پارامترهای نام‌دار فراخوانی کرد.

آیتم سوم اختیاری، مقدار پیش‌فرض این پارامتر است.

مثال زیر نشان می‌دهد که چگونه می‌توان تابع MessageBoxW ویندوز را پوشش داد تا از پارامترهای پیش‌فرض و آرگومان‌های نام‌دار پشتیبانی کند. اعلان C از پرونده‌ی سرآیند ویندوز به این صورت است:

WINUSERAPI int WINAPI
MessageBoxW(
    HWND hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT uType);

در اینجا پوششی با ctypes آمده است:

>>> from ctypes import c_int, WINFUNCTYPE, windll
>>> from ctypes.wintypes import HWND, LPCWSTR, UINT
>>> prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT)
>>> paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", "Hello from ctypes"), (1, "flags", 0)
>>> MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)

اکنون می‌توان تابع خارجی MessageBox را به این روش‌ها فراخوانی کرد:

>>> MessageBox()
>>> MessageBox(text="Spam, spam, spam")
>>> MessageBox(flags=2, text="foo bar")

مثال دوم پارامترهای خروجی را نشان می‌دهد. تابع GetWindowRect در win32، ابعاد یک پنجره مشخص را با کپی کردن آن‌ها در ساختار RECT که فراخواننده باید آن را فراهم کند، به دست می‌آورد. اعلان C به صورت زیر است:

WINUSERAPI BOOL WINAPI
GetWindowRect(
     HWND hWnd,
     LPRECT lpRect);

در اینجا پوششی با ctypes آمده است:

>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError
>>> from ctypes.wintypes import BOOL, HWND, RECT
>>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
>>> paramflags = (1, "hwnd"), (2, "lprect")
>>> GetWindowRect = prototype(("GetWindowRect", windll.user32), paramflags)
>>>

توابعی که پارامترهای خروجی دارند، به‌طور خودکار اگر فقط یک پارامتر خروجی وجود داشته باشد مقدار آن را برمی‌گردانند و اگر بیش از یکی وجود داشته باشد، تاپلی شامل مقادیر پارامترهای خروجی را برمی‌گردانند؛ بنابراین تابع GetWindowRect اکنون هنگام فراخوانی یک نمونه از RECT برمی‌گرداند.

می‌توان پارامترهای خروجی را با پروتکل errcheck ترکیب کرد تا پردازش بیشتر خروجی و بررسی خطا انجام شود. تابع API مربوط به win32 به نام GetWindowRect یک BOOL برمی‌گرداند تا موفقیت یا شکست را نشان دهد، بنابراین این تابع می‌تواند بررسی خطا را انجام دهد و در صورت ناموفق بودن فراخوانی API، یک استثنا پرتاب کند:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     return args
...
>>> GetWindowRect.errcheck = errcheck
>>>

اگر تابع errcheck تاپل آرگومان‌هایی را که دریافت می‌کند بدون تغییر برگرداند، ctypes به پردازش عادی خود روی پارامترهای خروجی ادامه می‌دهد. اگر بخواهید به‌جای یک نمونه RECT، یک تاپل از مختصات پنجره برگردانید، می‌توانید فیلدها را در تابع بازیابی کنید و آن‌ها را به‌جای آن برگردانید؛ پردازش عادی دیگر انجام نخواهد شد:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     rc = args[1]
...     return rc.left, rc.top, rc.bottom, rc.right
...
>>> GetWindowRect.errcheck = errcheck
>>>

توابع کاربردی

ctypes.addressof(obj)

نشانی بافر حافظه را به‌صورت عدد صحیح برمی‌گرداند. obj باید نمونه‌ای از یک نوع ctypes باشد.

یک رویداد حسابرسی ctypes.addressof را با آرگومان obj پرتاب می‌کند.

ctypes.alignment(obj_or_type)

الزامات هم‌ترازی یک نوع ctypes را برمی‌گرداند. obj_or_type باید یک نوع یا نمونه از ctypes باشد.

ctypes.byref(obj[, offset])

یک اشاره‌گر سبک‌وزن به obj برمی‌گرداند، که باید نمونه‌ای از یک نوع ctypes باشد. offset به‌طور پیش‌فرض صفر است و باید یک عدد صحیح باشد که به مقدار اشاره‌گر داخلی افزوده می‌شود.

byref(obj, offset) معادل این کد C است:

(((char *)&obj) + offset)

شیء برگردانده‌شده تنها می‌تواند به‌عنوان پارامتر فراخوانی تابع خارجی استفاده شود. رفتار آن مشابه pointer(obj) است، اما ساخت آن بسیار سریع‌تر است.

ctypes.CopyComPointer(src, dst)

یک اشاره‌گر COM را از src به dst کپی می‌کند و مقدار HRESULT مختص ویندوز را برمی‌گرداند.

اگر src برابر NULL نباشد، متد AddRef آن فراخوانی می‌شود و شمار ارجاع را افزایش می‌دهد.

در مقابل، شمار ارجاع dst پیش از انتساب مقدار جدید کاهش نمی‌یابد. مگر اینکه dst برابر NULL باشد، فراخواننده مسئول کاهش شمار ارجاع با فراخوانی متد Release آن در صورت لزوم است.

اضافه شده در نسخه‌ی 3.14.

ctypes.cast(obj, type)

این تابع مشابه عملگر تبدیل نوع (cast) در C است. این تابع نمونه‌ی جدیدی از type را بازمی‌گرداند که به همان بلوک حافظه‌ای اشاره می‌کند که obj به آن اشاره دارد. type باید یک نوع اشاره‌گر باشد و obj باید شیء‌ای باشد که بتوان آن را به‌عنوان یک اشاره‌گر تفسیر کرد.

ctypes.create_string_buffer(init, size=None)
ctypes.create_string_buffer(size)

این تابع یک بافر نویسه‌ای تغییرپذیر ایجاد می‌کند. شیء برگردانده‌شده یک آرایه ctypes از c_char است.

اگر size داده شود (و None نباشد)، باید یک int باشد. این مقدار، اندازه‌ی آرایه‌ی بازگشتی را مشخص می‌کند.

اگر آرگومان init داده شود، باید از نوع bytes باشد. از آن برای مقداردهی اولیه آیتم‌های آرایه استفاده می‌شود. بایت‌هایی که به این روش مقداردهی اولیه نشده‌اند، روی صفر (NUL) تنظیم می‌شوند.

اگر size داده نشود (یا اگر None باشد)، بافر به اندازه یک عنصر بزرگ‌تر از init ساخته می‌شود و عملاً یک پایان‌گر NUL به آن اضافه می‌شود.

اگر هر دو آرگومان داده شده باشند، size نباید کمتر از len(init) باشد.

هشدار

اگر size برابر با len(init) باشد، یک پایان‌دهنده‌ی NUL افزوده نمی‌شود. با چنین بافری به‌عنوان یک رشته‌ی C رفتار نکنید.

برای مثال:

>>> bytes(create_string_buffer(2))
b'\x00\x00'
>>> bytes(create_string_buffer(b'ab'))
b'ab\x00'
>>> bytes(create_string_buffer(b'ab', 2))
b'ab'
>>> bytes(create_string_buffer(b'ab', 4))
b'ab\x00\x00'
>>> bytes(create_string_buffer(b'abcdef', 2))
Traceback (most recent call last):
   ...
ValueError: byte string too long

یک رویداد حسابرسی ctypes.create_string_buffer را با آرگومان‌های init و size پرتاب می‌کند.

ctypes.create_unicode_buffer(init, size=None)
ctypes.create_unicode_buffer(size)

این تابع یک بافر تغییرپذیر از نویسه‌های یونیکد ایجاد می‌کند. شیء برگردانده‌شده یک آرایه ctypes از c_wchar است.

این تابع همان آرگومان‌هایی را می‌پذیرد که create_string_buffer() می‌پذیرد، به‌جز اینکه init باید یک رشته باشد و size تعداد c_wchar را می‌شمارد.

یک رویداد حسابرسی ctypes.create_unicode_buffer را با آرگومان‌های init و size پرتاب می‌کند.

ctypes.DllCanUnloadNow()

این تابع یک قلاباست که امکان پیاده‌سازی سرورهای COM درون‌فرایندی با ctypes را فراهم می‌کند. این تابع از تابع DllCanUnloadNow فراخوانی می‌شود که dll افزونه‌ای _ctypes آن را اکسپورت می‌کند.

ctypes.DllGetClassObject()

این تابع یک قلاباست که امکان پیاده‌سازی سرورهای COM درون‌فرایندی را با ctypes فراهم می‌کند. این تابع از تابع DllGetClassObject فراخوانی می‌شود که dll افزونه _ctypes آن را اکسپورت می‌کند.

ctypes.util.find_library(name)

تلاش می‌کند کتابخانه‌ای را بیابد و یک مسیر نام برگرداند. name نام کتابخانه بدون هیچ پیشوندی مانند lib، پسوندی مانند .so یا .dylib، یا شماره‌ی نسخه است (این قالب برای گزینه‌ی پیونددهنده‌ی POSIX یعنی -l به کار می‌رود). اگر هیچ کتابخانه‌ای یافت نشود، None برگردانده می‌شود.

عملکرد دقیق، وابسته به سیستم است.

برای مستندات کامل، یافتن کتابخانه‌های مشترک را ببینید.

ctypes.util.find_msvcrt()

نام پرونده کتابخانه ران‌تایم VC مورد استفاده پایتون و ماژول‌های توسعه را برمی‌گرداند. اگر نام کتابخانه قابل تعیین نباشد، None برگردانده می‌شود.

اگر نیاز دارید حافظه‌ای را آزاد کنید، برای مثال حافظه‌ای که توسط یک ماژول توسعه تخصیص داده شده است، با فراخوانی free(void *)، مهم است که از تابع در همان کتابخانه‌ای استفاده کنید که آن حافظه را تخصیص داده است.

ctypes.util.dllist()

تلاش می‌کند فهرستی از مسیرهای کتابخانه‌های اشتراکی بارگذاری‌شده در فرایند جاری را ارائه کند. این مسیرها به هیچ وجه به‌هنجار یا پردازش نمی‌شوند. اگر APIهای پلتفرم زیرین شکست بخورند، این تابع ممکن است OSError را پرتاب کند. عملکرد دقیق آن وابسته به سیستم است.

در بیشتر پلتفرم‌ها، اولین عنصر فهرست نمایانگر پرونده اجرایی جاری است. ممکن است یک رشته خالی باشد.

دسترس‌پذیری: Windows, macOS, iOS, glibc, BSD libc, musl

اضافه شده در نسخه‌ی 3.14.

ctypes.FormatError([code])

یک شرح متنی از کد خطای code را برمی‌گرداند. اگر کد خطایی مشخص ن‌شده باشد، با فراخوانی تابع API ویندوز GetLastError() از آخرین کد خطا استفاده می‌شود.

ctypes.GetLastError()

آخرین کد خطایی که ویندوز در نخ فراخواننده تنظیم کرده است را بازمی‌گرداند. این تابع مستقیماً تابع GetLastError() ویندوز را فراخوانی می‌کند و نسخه‌ی خصوصی ctypes از کد خطا را بازنمی‌گرداند.

ctypes.get_errno()

مقدار فعلی نسخه‌ی خصوصی ctypes از متغیر سیستمی errno را در نخ فراخوان برمی‌گرداند.

یک رویداد حسابرسی ctypes.get_errno را بدون آرگومان پرتاب می‌کند.

ctypes.get_last_error()

مقدار فعلی نسخه‌ی خصوصی ctypes از متغیر سیستمی LastError را در نخ فراخوان برمی‌گرداند.

یک رویداد حسابرسی ctypes.get_last_error را بدون آرگومان پرتاب می‌کند.

ctypes.memmove(dst, src, count)

مشابه تابع memmove در کتابخانه استاندارد C: count بایت را از src به dst کپی می‌کند. dst و src باید اعداد صحیح یا نمونه‌های ctypes باشند که بتوان آن‌ها را به اشاره‌گرها تبدیل کرد.

ctypes.memset(dst, c, count)

مشابه تابع کتابخانه‌ای استاندارد memset در C: بلوک حافظه در نشانی dst را با count بایت به مقدار c پر می‌کند. dst باید یک عدد صحیح مشخص‌کننده یک نشانی، یا نمونه‌ای از ctypes باشد.

ctypes.POINTER(type, /)

یک نوع اشاره‌گر ctypes را ایجاد می‌کند یا بازمی‌گرداند. انواع اشاره‌گر به‌صورت داخلی در نهانگاه ذخیره و دوباره استفاده می‌شوند، بنابراین فراخوانی مکرر این تابع کم‌هزینه است. type باید یک نوع ctypes باشد.

نوع اشاره‌گر حاصل در ویژگی __pointer_type__ از type به‌عنوان نهانگاه ذخیره می‌شود. می‌توان این ویژگی را پیش از نخستین فراخوانی POINTER تنظیم کرد تا یک نوع اشاره‌گر سفارشی تنظیم شود. با این حال، انجام این کار توصیه نمی‌شود: ایجاد دستی یک نوع اشاره‌گر مناسب بدون تکیه بر جزئیات پیاده‌سازی که ممکن است در نسخه‌های آینده پایتون تغییر کنند، دشوار است.

ctypes.pointer(obj, /)

یک نمونه اشاره‌گر جدید، که به obj اشاره دارد، ایجاد کنید. شیء برگردانده‌شده از نوع POINTER(type(obj)) است.

نکته: اگر فقط می‌خواهید یک اشاره‌گر به یک شیء را به یک فراخوانی تابع خارجی ارسال کنید، باید از byref(obj) استفاده کنید که بسیار سریع‌تر است.

ctypes.resize(obj, size)

این تابع بافر حافظه‌ی داخلی obj را تغییر اندازه می‌دهد؛ obj باید نمونه‌ای از یک نوع ctypes باشد. کوچک کردن بافر به کمتر از اندازه‌ی بومی نوع شیء، همان‌طور که با sizeof(type(obj)) مشخص می‌شود، امکان‌پذیر نیست، اما بزرگ کردن بافر امکان‌پذیر است.

ctypes.set_errno(value)

مقدار فعلی نسخه‌ی خصوصی ctypes از متغیر سیستمی errno در نخ فراخواننده را برابر value قرار می‌دهد و مقدار قبلی را برمی‌گرداند.

یک رویداد حسابرسی ctypes.set_errno را با آرگومان errno پرتاب می‌کند.

ctypes.set_last_error(value)

مقدار فعلی نسخه‌ی خصوصی ctypes از متغیر سیستمی LastError را در نخ فراخوان به value تنظیم می‌کند و مقدار قبلی را برمی‌گرداند.

یک رویداد حسابرسی ctypes.set_last_error را با آرگومان error پرتاب می‌کند.

ctypes.sizeof(obj_or_type)

اندازه‌ی یک نوع ctypes یا بافر حافظه‌ی یک نمونه را بر حسب بایت برمی‌گرداند. همانند عملگر sizeof در C عمل می‌کند.

ctypes.string_at(ptr, size=-1)

رشته بایتی در void *ptr را برمی‌گرداند. اگر size مشخص شده باشد، به‌عنوان اندازه استفاده می‌شود، در غیر این صورت فرض می‌شود که رشته با صفر خاتمه یافته است.

یک رویداد حسابرسی ctypes.string_at را با آرگومان‌های ptr و size پرتاب می‌کند.

ctypes.WinError(code=None, descr=None)

یک نمونه از OSError ایجاد می‌کند. اگر code مشخص نشده باشد، برای تعیین کد خطا GetLastError() فراخوانی می‌شود. اگر descr مشخص نشده باشد، برای دریافت شرح متنی خطا FormatError() فراخوانی می‌شود.

تغییر یافته در نسخه‌ی 3.3: پیش‌تر نمونه‌ای از WindowsError ایجاد می‌شد، که اکنون نام مستعاری برای OSError است.

ctypes.wstring_at(ptr, size=-1)

رشته نویسه‌های پهن در void *ptr را برمی‌گرداند. اگر size تعیین شده باشد، از آن به عنوان تعداد نویسه‌های رشته استفاده می‌شود، در غیر این صورت فرض می‌شود که رشته با صفر پایان می‌یابد.

یک رویداد حسابرسی ctypes.wstring_at را با آرگومان‌های ptr و size پرتاب می‌کند.

ctypes.memoryview_at(ptr, size, readonly=False)

یک شیء memoryview به طول size برمی‌گرداند که به حافظه‌ای که از void *ptr شروع می‌شود ارجاع دارد.

اگر readonly مقدار true باشد، نمی‌توان از شیء memoryview برگردانده‌شده برای تغییر حافظه‌ی زیرین استفاده کرد. (تغییراتی که به روش‌های دیگر ایجاد شوند، همچنان در شیء برگردانده‌شده بازتاب خواهند داشت.)

این تابع مشابه string_at() است، با این تفاوت کلیدی که کپی‌ای از حافظه‌ی مشخص‌شده نمی‌سازد. این تابع جایگزینی هم‌ارز از نظر معنایی (اما کارآمدتر) برای memoryview((c_byte * size).from_address(ptr)) است. (در حالی که from_address() فقط اعداد صحیح را می‌پذیرد، ptr همچنین می‌تواند به‌عنوان یک شیء ctypes.POINTER یا byref() نیز داده شود.)

رویداد حسابرسی ctypes.memoryview_at را با آرگومان‌های address، size و readonly پرتاب می‌کند.

اضافه شده در نسخه‌ی 3.14.

انواع داده

class ctypes._CData

این کلاس غیرعمومی، کلاس پایه مشترک تمام انواع داده ctypes است. در میان سایر موارد، تمام نمونه‌های نوع ctypes شامل یک بلوک حافظه هستند که داده‌های سازگار با C را نگه می‌دارد؛ آدرس بلوک حافظه توسط تابع کمکی addressof() برگردانده می‌شود. متغیر نمونه دیگری نیز به‌عنوان _objects در معرض قرار گرفته است؛ این متغیر شامل اشیاء پایتون دیگری است که در صورتی که بلوک حافظه حاوی اشاره‌گرها باشد، باید زنده نگه داشته شوند.

متدهای مشترک انواع داده ctypes، همگی متدهای کلاس هستند (به‌طور دقیق، آن‌ها متدهای فراکلاس هستند):

from_buffer(source[, offset])

این متد یک نمونه ctypes برمی‌گرداند که بافر شیء source را به اشتراک می‌گذارد. شیء source باید از رابط بافر قابل‌نوشتن پشتیبانی کند. پارامتر اختیاری offset یک آفست درون بافر source را بر حسب بایت مشخص می‌کند؛ مقدار پیش‌فرض ۰ است. اگر بافر source به‌قدر کافی بزرگ نباشد، یک ValueError پرتاب می‌شود.

یک رویداد حسابرسی ctypes.cdata/buffer را با آرگومان‌های pointer، size، offset پرتاب می‌کند.

from_buffer_copy(source[, offset])

این متد یک نمونه ctypes ایجاد می‌کند و بافر را از بافر شیء source کپی می‌کند؛ بافر شیء source باید خواندنی باشد. پارامتر اختیاری offset، یک آفست در بافر منبع را بر حسب بایت مشخص می‌کند؛ مقدار پیش‌فرض ۰ است. اگر بافر منبع به‌اندازه کافی بزرگ نباشد، یک ValueError پرتاب می‌شود.

یک رویداد حسابرسی ctypes.cdata/buffer را با آرگومان‌های pointer، size، offset پرتاب می‌کند.

from_address(address)

این متد یک نمونه‌ی نوع ctypes را با استفاده از حافظه‌ی مشخص‌شده توسط address برمی‌گرداند؛ address باید یک عدد صحیح باشد.

این متد، و سایر متدهایی که به‌طور غیرمستقیم این متد را فراخوانی می‌کنند، یک رویداد حسابرسی ctypes.cdata را با آرگومان address پرتاب می‌کنند.

from_param(obj)

این متد obj را با یک نوع ctypes تطبیق می‌دهد. هنگامی که نوع در تاپل argtypes تابع خارجی وجود داشته باشد، این متد با شیء واقعی استفاده‌شده در یک فراخوانی تابع خارجی فراخوانی می‌شود؛ این متد باید یک شیء برگرداند که بتوان از آن به‌عنوان پارامتر فراخوانی تابع استفاده کرد.

تمام انواع داده ctypes یک پیاده‌سازی پیش‌فرض از این classmethod دارند که به‌طور معمول obj را، در صورتی که نمونه‌ای از آن نوع باشد، برمی‌گرداند. برخی از انواع، اشیاء دیگر را نیز می‌پذیرند.

in_dll(library, name)

این متد یک نمونه از نوع ctypes را بازمی‌گرداند که توسط یک کتابخانه مشترک اکسپورت شده است. name نام نمادی است که داده‌ها را اکسپورت می‌کند، و library کتابخانه مشترک بارگذاری‌شده است.

متغیرهای کلاس مشترک انواع داده ctypes:

__pointer_type__

نوع اشاره‌گری که با فراخوانی POINTER() برای نوع داده‌ی متناظر در ctypes ایجاد شده است. اگر نوع اشاره‌گر هنوز ایجاد نشده باشد، این ویژگی وجود ندارد.

اضافه شده در نسخه‌ی 3.14.

متغیرهای نمونه مشترک انواع داده ctypes:

_b_base_

گاهی نمونه‌های داده ctypes مالک بلوک حافظه‌ای که در بر دارند، نیستند؛ بلکه بخشی از بلوک حافظه یک شیء پایه را به اشتراک می‌گذارند. عضو فقط‌خواندنی _b_base_ شیء ریشه ctypes است که مالک بلوک حافظه می‌باشد.

_b_needsfree_

این متغیر فقط‌خواندنی زمانی درست است که نمونه داده ctypes خودش بلوک حافظه را تخصیص داده باشد، در غیر این صورت نادرست است.

_objects

این عضو یا None است یا یک دیکشنری حاوی اشیای پایتون که باید زنده نگه داشته شوند تا محتویات بلوک حافظه معتبر باقی بماند. این شیء فقط برای اشکال‌زدایی در دسترس است؛ هرگز محتویات این دیکشنری را تغییر ندهید.

انواع داده بنیادی

class ctypes._SimpleCData

این کلاس غیرعمومی، کلاس پایه‌ی همه‌ی انواع داده‌ی بنیادی ctypes است. این کلاس در اینجا ذکر شده است زیرا شامل ویژگی‌های مشترک انواع داده‌ی بنیادی ctypes است. _SimpleCData یک زیرکلاس از _CData است، بنابراین متدها و ویژگی‌های آن‌ها را به ارث می‌برد. اکنون می‌توان انواع داده‌ی ctypes را که اشاره‌گر نیستند و شامل اشاره‌گرها نمی‌شوند، پیکل کرد.

نمونه‌ها یک ویژگی دارند:

value

این ویژگی حاوی مقدار واقعی نمونه است. برای انواع عدد صحیح و اشاره‌گر، این مقدار یک عدد صحیح است، برای انواع نویسه، یک شیء bytes یا رشته‌ی تک‌نویسه‌ای است، و برای انواع اشاره‌گر به نویسه، یک شیء bytes یا رشته‌ی پایتون است.

هنگامی که ویژگی value از یک نمونه ctypes بازیابی می‌شود، معمولاً هر بار یک شیء جدید برگردانده می‌شود. ctypes بازگشت شیء اصلی را پیاده‌سازی نمی‌کند، بلکه همیشه یک شیء جدید ساخته می‌شود. همین موضوع برای تمام نمونه‌های دیگر اشیاء ctypes نیز صادق است.

هر زیرکلاس دارای یک ویژگی کلاس است:

_type_

ویژگی کلاس که حاوی یک کد نوع داخلی به‌صورت یک رشته تک‌نویسه‌ای است. برای یک خلاصه، به انواع داده بنیادی مراجعه کنید.

انواعی که در خلاصه با * علامت‌گذاری شده‌اند ممکن است (یا همواره) نام‌های مستعار یک زیرکلاس دیگر از _SimpleCData باشند و لزوماً از کد نوع فهرست‌شده استفاده نخواهند کرد. برای مثال، اگر انواع Cِ long، long long و time_t در سکو یکسان باشند، آنگاه c_long، c_longlong و c_time_t همگی به یک کلاس واحد، یعنی c_long، ارجاع می‌دهند که کد _type_ آن 'l' است. کد 'L' استفاده نخواهد شد.

همچنین ملاحظه نمائید

ماژول‌های array و struct، و همچنین ماژول‌های شخص ثالثی مانند numpy، از کدهای نوع مشابه — اما اندکی متفاوت — استفاده می‌کنند.

انواع داده بنیادی، هنگامی که به‌عنوان نتایج فراخوانی تابع خارجی برگردانده می‌شوند، یا مثلاً هنگام بازیابی اعضای فیلدهای ساختار یا آیتم‌های آرایه، به‌طور شفاف به انواع بومی پایتون تبدیل می‌شوند. به عبارت دیگر، اگر یک تابع خارجی دارای restype برابر با c_char_p باشد، همیشه یک شیء bytes پایتون دریافت خواهید کرد، نه یک نمونه c_char_p.

زیرکلاس‌های انواع داده بنیادی این رفتار را به ارث نمی‌برند. بنابراین، اگر restype یک تابع خارجی زیرکلاسی از c_void_p باشد، از فراخوانی تابع یک نمونه از این زیرکلاس دریافت خواهید کرد. البته می‌توانید با دسترسی به ویژگی value مقدار اشاره‌گر را به دست آورید.

این‌ها انواع داده‌ی بنیادی ctypes هستند:

class ctypes.c_byte

نوع داده‌ی signed char در C را نشان می‌دهد و مقدار را به‌عنوان عدد صحیح کوچک تفسیر می‌کند. سازنده یک مقدار اولیه‌ی اختیاری از نوع عدد صحیح می‌پذیرد؛ هیچ‌گونه بررسی سرریزی انجام نمی‌شود.

class ctypes.c_char

نشان‌دهنده‌ی نوع داده‌ی char در C است و مقدار را به‌عنوان یک نویسه تفسیر می‌کند. سازنده یک مقدارده اولیه‌ی رشته‌ای اختیاری را می‌پذیرد، طول رشته باید دقیقاً ۱ نویسه باشد.

class ctypes.c_char_p

نمایانگر نوع داده‌ی C char* است، زمانی که به یک رشته‌ی پایان‌یافته با صفر اشاره می‌کند. برای یک اشاره‌گر نویسه‌ای عمومی که ممکن است به داده‌های دودویی نیز اشاره کند، باید از POINTER(c_char) استفاده شود. سازنده یک نشانی به‌صورت عدد صحیح یا یک شیء bytes را می‌پذیرد.

class ctypes.c_double

نشان‌دهنده‌ی نوع داده‌ی double در C است. سازنده یک مقدار اولیه‌ی float اختیاری را می‌پذیرد.

class ctypes.c_longdouble

نمایانگر نوع داده‌ی C long double است. سازنده یک مقدار اولیه float اختیاری را می‌پذیرد. در سکوهایی که sizeof(long double) == sizeof(double) باشد، این نام مستعاری برای c_double است.

class ctypes.c_float

نوع داده‌ی float در C را نشان می‌دهد. سازنده یک مقدار اولیه‌ی اختیاری از نوع float را می‌پذیرد.

class ctypes.c_double_complex

در صورت موجود بودن، نشان‌دهنده‌ی نوع داده‌ی double complex در C است. سازنده یک مقداردهی اولیه‌ی اختیاری از complex را می‌پذیرد.

اضافه شده در نسخه‌ی 3.14.

class ctypes.c_float_complex

نوع داده‌ی float complex در C را در صورت موجود بودن نشان می‌دهد. سازنده، یک مقدارده اولیه اختیاری از نوع complex را می‌پذیرد.

اضافه شده در نسخه‌ی 3.14.

class ctypes.c_longdouble_complex

نشان‌دهنده‌ی نوع داده‌ی long double complex در C است، در صورت موجود بودن. سازنده یک مقداردهی اولیه‌ی اختیاری complex را می‌پذیرد.

اضافه شده در نسخه‌ی 3.14.

class ctypes.c_int

نشان‌دهنده‌ی نوع داده‌ی signed int در C است. سازنده یک مقدار اولیه‌ی اختیاری عدد صحیح را می‌پذیرد؛ هیچ بررسی سرریز انجام نمی‌شود. در سکوهایی که sizeof(int) == sizeof(long) باشد، این یک نام مستعار برای c_long است.

class ctypes.c_int8

نشان‌دهنده‌ی نوع داده‌ی signed int ۸ بیتی C است. این یک نام مستعار برای c_byte است.

class ctypes.c_int16

نشان‌دهنده‌ی نوع داده‌ی signed int ۱۶ بیتی در C است. معمولاً نام مستعاری برای c_short است.

class ctypes.c_int32

نشان‌دهنده‌ی نوع داده‌ی signed int ۳۲ بیتی C است. معمولاً نام مستعاری برای c_int است.

class ctypes.c_int64

نشان‌دهنده‌ی نوع داده‌ی signed int ۶۴ بیتی C است. معمولاً نام مستعاری برای c_longlong است.

class ctypes.c_long

نشان‌دهنده نوع داده signed long در C است. سازنده یک مقدار اولیه عدد صحیح اختیاری را می‌پذیرد؛ هیچ بررسی سرریز انجام نمی‌شود.

class ctypes.c_longlong

نشان‌دهنده‌ی نوع داده‌ی C signed long long است. سازنده‌ی آن یک مقدار اولیه‌ی اختیاری از نوع عدد صحیح را می‌پذیرد؛ هیچ بررسی سرریز انجام نمی‌شود. در سکوهایی که در آن‌ها sizeof(long long) == sizeof(long) باشد، این نوع نام مستعاری برای c_long است.

class ctypes.c_short

نشان‌دهنده‌ی نوع داده‌ی signed short در C است. سازنده یک مقداردهی اولیه‌ی اختیاری از نوع عدد صحیح را می‌پذیرد؛ هیچ بررسی‌ای برای سرریز انجام نمی‌شود.

class ctypes.c_size_t

نشان‌دهنده‌ی نوع داده‌ی size_t در C است. معمولاً نام مستعاری برای نوع دیگری از عدد صحیح بدون علامت است.

class ctypes.c_ssize_t

نشان‌دهنده‌ی نوع داده‌ی Py_ssize_t است. این یک نسخه‌ی علامت‌دار از size_t است؛ یعنی نوع ssize_t در POSIX. معمولاً نام مستعاری برای یک نوع عدد صحیح دیگر است.

اضافه شده در نسخه‌ی 3.2.

class ctypes.c_time_t

نشان‌دهنده‌ی نوع داده‌ی time_t در C است. معمولاً نام مستعاری برای یک نوع عدد صحیح دیگر است.

اضافه شده در نسخه‌ی 3.12.

class ctypes.c_ubyte

نشان‌دهنده‌ی نوع داده‌ی unsigned char در C است و مقدار را به‌عنوان عدد صحیح کوچک تفسیر می‌کند. سازنده یک مقدارده اولیه‌ی اختیاری از نوع عدد صحیح را می‌پذیرد؛ هیچ بررسی‌ای برای سرریز انجام نمی‌شود.

class ctypes.c_uint

نشان‌دهنده‌ی نوع داده‌ی unsigned int در C است. سازنده یک مقدار اولیه‌ی اختیاری از نوع عدد صحیح می‌پذیرد؛ هیچ بررسی سرریزی انجام نمی‌شود. در سکوهایی که sizeof(int) == sizeof(long) باشد، نام مستعاری برای c_ulong است.

class ctypes.c_uint8

نشان‌دهنده‌ی نوع داده‌ی ۸‌بیتی unsigned int در C است. این یک نام مستعار برای c_ubyte است.

class ctypes.c_uint16

نشان‌دهنده‌ی نوع داده‌ی unsigned int ۱۶ بیتی در C است. معمولاً نام مستعاری برای c_ushort است.

class ctypes.c_uint32

نشان‌دهنده‌ی نوع داده‌ی ۳۲ بیتی unsigned int در C است. معمولاً نام مستعاری برای c_uint است.

class ctypes.c_uint64

نشان‌دهنده‌ی نوع داده‌ی unsigned int ۶۴ بیتی در C است. معمولاً نام مستعاری برای c_ulonglong است.

class ctypes.c_ulong

نوع داده‌ی unsigned long در C را نشان می‌دهد. سازنده یک مقدار اولیه‌ی اختیاری از نوع عدد صحیح را می‌پذیرد؛ هیچ بررسی‌ای برای سرریز انجام نمی‌شود.

class ctypes.c_ulonglong

نشان‌دهنده‌ی نوع داده‌ی C unsigned long long است. سازنده یک مقدار اولیه‌ی عدد صحیح اختیاری را می‌پذیرد؛ هیچ بررسی سرریزی انجام نمی‌شود. در سکوهای که sizeof(long long) == sizeof(long) باشد، نام مستعاری برای c_long است.

class ctypes.c_ushort

نوع داده‌ی unsigned short در C را نشان می‌دهد. سازنده یک مقدار اولیه‌ی اختیاری از نوع عدد صحیح می‌پذیرد؛ هیچ بررسی سرریزی انجام نمی‌شود.

class ctypes.c_void_p

نشان‌دهنده‌ی نوع void* در C است. مقدار به‌صورت عدد صحیح نشان داده می‌شود. سازنده یک مقدار اولیه‌ی اختیاری عدد صحیح را می‌پذیرد.

class ctypes.c_wchar

نشان‌دهنده‌ی نوع داده‌ی wchar_t در C است و مقدار را به‌عنوان یک رشته‌ی یونیکد تک‌نویسه‌ای تفسیر می‌کند. سازنده یک مقدار اولیه‌ی رشته‌ای اختیاری می‌پذیرد؛ طول رشته باید دقیقاً ۱ نویسه باشد.

class ctypes.c_wchar_p

نشان‌دهنده‌ی نوع داده‌ی C wchar_t* است، که باید اشاره‌گری به رشته‌ای از نویسه‌های پهن خاتمه‌یافته با صفر باشد. سازنده یک نشانی عدد صحیحی یا یک رشته را می‌پذیرد.

class ctypes.c_bool

نشان‌دهنده‌ی نوع داده‌ی bool در C (دقیق‌تر، _Bool از C99) است. مقدار آن می‌تواند True یا False باشد و سازنده‌ی آن هر شیءای را که مقدار درستی داشته باشد می‌پذیرد.

class ctypes.HRESULT

بیانگر یک مقدار HRESULT است، که حاوی اطلاعات موفقیت یا خطا برای فراخوانی یک تابع یا متد است.

class ctypes.py_object

نشان‌دهنده‌ی نوع داده‌ی PyObject* در C است. فراخوانی این بدون آرگومان، یک اشاره‌گر NULL از نوع PyObject* ایجاد می‌کند.

تغییر یافته در نسخه‌ی 3.14: py_object اکنون یک generic type است.

ماژول ctypes.wintypes تعداد زیادی از انواع داده دیگر مختص ویندوز را فراهم می‌کند، برای مثال HWND، WPARAM، VARIANT_BOOL یا DWORD. برخی ساختارهای مفید مانند MSG یا RECT نیز تعریف شده‌اند.

انواع داده ساختاریافته

class ctypes.Union(*args, **kw)

کلاس پایه انتزاعی برای اجتماع‌ها (unions) در ترتیب بایت بومی.

اجتماع‌ها (Unions) ویژگی‌ها و رفتار مشترکی با ساختارها دارند؛ برای جزئیات، مستندات Structure را ببینید.

class ctypes.BigEndianUnion(*args, **kw)

کلاس پایه انتزاعی برای اجتماع‌ها با ترتیب بایت بزرگ‌اندیان.

اضافه شده در نسخه‌ی 3.11.

class ctypes.LittleEndianUnion(*args, **kw)

کلاس پایه انتزاعی برای اجتماع‌ها (union) با ترتیب بایتی کوچک‌اندیان (little endian).

اضافه شده در نسخه‌ی 3.11.

class ctypes.BigEndianStructure(*args, **kw)

کلاس پایه‌ی انتزاعی برای ساختارهایی با ترتیب بایت بزرگ‌اندیان (big endian).

class ctypes.LittleEndianStructure(*args, **kw)

کلاس پایه انتزاعی برای ساختارهایی با ترتیب بایت little endian.

ساختارها و اجتماع‌هایی که ترتیب بایت غیربومی دارند، نمی‌توانند شامل فیلدهایی از نوع اشاره‌گر یا هر نوع داده دیگری باشند که حاوی فیلدهایی از نوع اشاره‌گر باشد.

class ctypes.Structure(*args, **kw)

کلاس پایه انتزاعی برای ساختارها با ترتیب بایت بومی.

انواع عینی ساختار و اجتماع (union) باید با زیرکلاس‌سازی از یکی از این انواع ایجاد شوند، و حداقل یک متغیر کلاس _fields_ را تعریف کنند. ctypes توصیف‌گرها (descriptors) را ایجاد می‌کند که امکان خواندن و نوشتن فیلدها را از طریق دسترسی مستقیم به ویژگی‌ها فراهم می‌کنند. این‌ها

_fields_

دنباله‌ای که فیلدهای ساختار را تعریف می‌کند. آیتم‌ها باید تاپل‌های دوتایی یا سه‌تایی باشند. اولین آیتم، نام فیلد است؛ دومین آیتم، نوع فیلد را مشخص می‌کند و می‌تواند هر نوع داده‌ای از ctypes باشد.

برای فیلدهایی از نوع عدد صحیح مانند c_int، می‌توان یک آیتم سوم اختیاری نیز ارائه کرد. این آیتم باید یک عدد صحیح مثبت کوچک باشد که عرض بیت فیلد را تعریف می‌کند.

نام فیلدها باید در یک ساختار یا union یکتا باشند. این موضوع بررسی نمی‌شود؛ وقتی نام‌ها تکراری باشند، تنها به یک فیلد می‌توان دسترسی پیدا کرد.

می‌توانید متغیر کلاس _fields_ را پس از دستور class که زیرکلاس Structure را تعریف می‌کند، تعریف کنید؛ این کار امکان ایجاد انواع داده‌ای را فراهم می‌کند که به‌طور مستقیم یا غیرمستقیم به خودشان ارجاع می‌دهند:

class List(Structure):
    pass
List._fields_ = [("pnext", POINTER(List)),
                 ...
                ]

متغیر کلاس _fields_ را فقط می‌توان یک‌بار تنظیم کرد. انتساب‌های بعدی یک AttributeError را پرتاب خواهند کرد.

علاوه بر این، متغیر کلاس _fields_ باید پیش از اولین استفاده از نوع ساختار یا اجتماع (union) تعریف شود: ایجاد یک نمونه یا زیرکلاس، فراخوانی sizeof() روی آن، و غیره. انتساب‌های بعدی به _fields_ باعث پرتاب یک AttributeError می‌شوند. اگر _fields_ پیش از چنین استفاده‌ای تنظیم نشده باشد، ساختار یا اجتماع هیچ فیلد مختص به خود نخواهد داشت، گویی _fields_ خالی بوده است.

زیرزیرکلاس‌های انواع ساختاری، فیلدهای کلاس پایه به‌همراه _fields_ تعریف‌شده در زیرزیرکلاس را، در صورت وجود، به ارث می‌برند.

_pack_

یک عدد صحیح کوچک اختیاری که امکان بازنویسی هم‌ترازی فیلدهای ساختار در نمونه را فراهم می‌کند.

این فقط برای چیدمان حافظه سازگار با MSVC پیاده‌سازی شده است (به _layout_ مراجعه کنید).

تنظیم _pack_ به ۰ مانند این است که اصلاً آن را تنظیم نکرده باشید. در غیر این صورت، مقدار باید توان مثبتی از ۲ باشد. این اثر معادل #pragma pack(N) در C است، با این تفاوت که ctypes ممکن است n بزرگ‌تری را نسبت به آنچه کامپایلر می‌پذیرد مجاز بداند.

هنگامی که _fields_ انتساب می‌یابد، _pack_ باید از پیش تعریف شده باشد، در غیر این صورت هیچ اثری نخواهد داشت.

منسوخ شده از نسخه‌ی 3.14, در نسخه‌ی 3.19 حذف خواهد شد: به دلایل تاریخی، اگر _pack_ غیرصفر باشد، چیدمان سازگار با MSVC به‌طور پیش‌فرض استفاده خواهد شد. در سکوهای غیرویندوزی، این پیش‌فرض منسوخ شده است و قرار است در پایتون 3.19 به یک خطا تبدیل شود. اگر این حالت مورد نظر است، _layout_ را به‌صراحت روی 'ms' تنظیم کنید.

_align_

یک عدد صحیح کوچک اختیاری که امکان افزایش هم‌ترازی ساختار را هنگام بسته‌بندی یا واگشایی به/از حافظه فراهم می‌کند.

مقدار نباید منفی باشد. اثر آن معادل __attribute__((aligned(N))) در GCC یا #pragma align(N) در MSVC است، با این تفاوت که ctypes ممکن است مقادیری را بپذیرد که کامپایلر آن‌ها را رد می‌کند.

_align_ فقط می‌تواند الزامات هم‌ترازی یک ساختار را افزایش دهد. تنظیم آن روی ۰ یا ۱ هیچ تأثیری ندارد.

استفاده از مقادیری که توان‌های ۲ نیستند، توصیه نمی‌شود و ممکن است به رفتار غیرمنتظره‌ای منجر شود.

_align_ باید هنگامی که _fields_ انتساب داده می‌شود از قبل تعریف‌شده باشد، در غیر این صورت بی‌اثر خواهد بود.

اضافه شده در نسخه‌ی 3.13.

_layout_

یک رشته اختیاری برای نام‌گذاری چیدمان struct/union. در حال حاضر می‌توان آن را به‌صورت زیر تنظیم کرد:

  • "ms": چیدمان استفاده‌شده توسط کامپایلر مایکروسافت (MSVC). در GCC و Clang، این چیدمان را می‌توان با __attribute__((ms_struct)) انتخاب کرد.

  • "gcc-sysv": چیدمان استفاده‌شده توسط GCC با مدل داده System V یا «شبیه SysV»، همان‌طور که در Linux و macOS استفاده می‌شود. در این چیدمان، _pack_ باید تنظیم‌نشده یا صفر باشد.

اگر به‌صراحت تنظیم نشده باشد، ctypes از پیش‌فرضی استفاده خواهد کرد که با قراردادهای پلتفرم مطابقت دارد. این پیش‌فرض ممکن است در نسخه‌های آینده پایتون تغییر کند (برای مثال، وقتی پشتیبانی رسمی از یک پلتفرم جدید اضافه شود، یا وقتی تفاوتی میان پلتفرم‌های مشابه یافت شود). در حال حاضر پیش‌فرض به این صورت خواهد بود:

  • در ویندوز: "ms"

  • هنگامی که _pack_ تعیین شده باشد: "ms". (این مورد منسوخ شده است؛ مستندات _pack_ را ببینید.)

  • در غیر این صورت: "gcc-sysv"

هنگامی که _fields_ انتساب می‌یابد، _layout_ باید از قبل تعریف شده باشد، در غیر این صورت هیچ اثری نخواهد داشت.

اضافه شده در نسخه‌ی 3.14.

_anonymous_

یک دنباله اختیاری که نام‌های فیلدهای بی‌نام (ناشناس) را فهرست می‌کند. _anonymous_ باید هنگامی که _fields_ مقداردهی می‌شود، از پیش تعریف شده باشد، در غیر این صورت هیچ اثری نخواهد داشت.

فیلدهای فهرست‌شده در این متغیر باید فیلدهایی با نوع ساختار یا union باشند. ctypes توصیف‌گرهایی را در نوع ساختار ایجاد می‌کند که امکان دسترسی مستقیم به فیلدهای تودرتو را بدون نیاز به ایجاد فیلد ساختار یا union فراهم می‌کنند.

در اینجا یک نمونه نوع (ویندوز):

class _U(Union):
    _fields_ = [("lptdesc", POINTER(TYPEDESC)),
                ("lpadesc", POINTER(ARRAYDESC)),
                ("hreftype", HREFTYPE)]

class TYPEDESC(Structure):
    _anonymous_ = ("u",)
    _fields_ = [("u", _U),
                ("vt", VARTYPE)]

ساختار TYPEDESC یک نوع داده COM را توصیف می‌کند، فیلد vt مشخص می‌کند که کدام‌یک از فیلدهای union معتبر است. از آن‌جا که فیلد u به‌عنوان فیلد ناشناس تعریف شده است، اکنون امکان دسترسی مستقیم به اعضا از روی نمونه TYPEDESC وجود دارد. td.lptdesc و td.u.lptdesc معادل هستند، اما مورد اول سریع‌تر است، زیرا نیازی به ایجاد یک نمونه union موقت ندارد:

td = TYPEDESC()
td.vt = VT_PTR
td.lptdesc = POINTER(some_type)
td.u.lptdesc = POINTER(some_type)

می‌توان زیرزیرکلاس‌هایی از ساختارها تعریف کرد؛ آن‌ها فیلدهای کلاس پایه را به ارث می‌برند. اگر تعریف زیرکلاس دارای یک متغیر _fields_ جداگانه باشد، فیلدهای مشخص‌شده در آن به فیلدهای کلاس پایه افزوده می‌شوند.

سازنده‌های ساختار و اجتماع، هم آرگومان‌های جایگاهی و هم آرگومان‌های کلیدواژه‌ای را می‌پذیرند. آرگومان‌های جایگاهی برای مقداردهی اولیه‌ی فیلدهای عضو به همان ترتیبی که در _fields_ ظاهر می‌شوند، استفاده می‌شوند. آرگومان‌های کلیدواژه‌ای در سازنده به‌عنوان انتساب ویژگی تفسیر می‌شوند، بنابراین فیلدهای _fields_ را با همان نام مقداردهی اولیه می‌کنند، یا برای نام‌هایی که در _fields_ وجود ندارند، ویژگی‌های جدیدی ایجاد می‌کنند.

class ctypes.CField(*args, **kw)

توصیف‌گر فیلدهای Structure و Union. برای مثال:

>>> class Color(Structure):
...     _fields_ = (
...         ('red', c_uint8),
...         ('green', c_uint8),
...         ('blue', c_uint8),
...         ('intense', c_bool, 1),
...         ('blinking', c_bool, 1),
...    )
...
>>> Color.red
<ctypes.CField 'red' type=c_ubyte, ofs=0, size=1>
>>> Color.green.type
<class 'ctypes.c_ubyte'>
>>> Color.blue.byte_offset
2
>>> Color.intense
<ctypes.CField 'intense' type=c_bool, ofs=3, bit_size=1, bit_offset=0>
>>> Color.blinking.bit_offset
1

تمام ویژگی‌ها فقط‌خواندنی هستند.

اشیای CField از طریق _fields_ ایجاد می‌شوند؛ این کلاس را مستقیماً نمونه‌سازی نکنید.

اضافه شده در نسخه‌ی 3.14: پیش‌تر، توصیف‌گرها فقط ویژگی‌های offset و size و یک نمایش رشته‌ای خوانا داشتند؛ کلاس CField به‌طور مستقیم در دسترس نبود.

name

نام فیلد، به‌صورت یک رشته.

type

نوع فیلد، به‌صورت یک کلاس ctypes.

offset
byte_offset

آفست فیلد، بر حسب بایت.

برای فیلدهای بیتی (bitfields)، این آفست واحد ذخیره‌سازی زیربنایی هم‌تراز با بایت است؛ به bit_offset مراجعه کنید.

byte_size

اندازه‌ی فیلد، بر حسب بایت.

برای بیت‌فیلدها، این اندازه‌ی واحد ذخیره‌سازی زیربنایی است. معمولاً هم‌اندازه‌ی نوع بیت‌فیلد است.

size

برای فیلدهای غیربیتی، معادل byte_size است.

برای فیلدهای بیتی (bitfields)، این شامل یک مقدار بسته‌بندی‌شده بیتی سازگار با نسخه‌های پیشین است که bit_size و bit_offset را ترکیب می‌کند. ترجیحاً به‌جای آن از ویژگی‌های صریح استفاده کنید.

is_bitfield

اگر این یک فیلد بیتی (bitfield) باشد، True است.

bit_offset
bit_size

موقعیت یک فیلد بیتی (bitfield) در واحد ذخیره‌سازی آن، یعنی در byte_size بایت از حافظه که از byte_offset آغاز می‌شود.

برای به‌دست آوردن مقدار فیلد، واحد ذخیره‌سازی را به‌عنوان یک عدد صحیح بخوانید، آن را به اندازه‌ی bit_offset شیفت چپ دهید و bit_size بیت کم‌اهمیت را بگیرید.

برای فیلدهای غیربیتی، bit_offset صفر است و bit_size برابر است با byte_size * 8.

is_anonymous

True اگر این فیلد ناشناس باشد، یعنی شامل زیرفیلدهای تودرتویی است که باید در ساختار یا union حاوی آن ادغام شوند.

آرایه‌ها و اشاره‌گرها

class ctypes.Array(*args)

کلاس پایه انتزاعی برای آرایه‌ها.

راه توصیه‌شده برای ایجاد انواع آرایه‌ی مشخص، ضرب کردن هر نوع داده‌ای از ctypes در یک عدد صحیح نامنفی است. به‌عنوان جایگزین، می‌توانید یک زیرکلاس از این نوع بسازید و متغیرهای کلاس _length_ و _type_ را تعریف کنید. عناصر آرایه را می‌توان با استفاده از دسترسی‌های استاندارد زیرنویسی و اسلایسی خواند و نوشت؛ در خواندن‌های اسلایسی، شیء حاصل، خود یک Array نیست.

آرایه‌ها نسبت به نوع عناصر خود عام هستند.

_length_

یک عدد صحیح مثبت که تعداد عناصر آرایه را مشخص می‌کند. زیرنویس‌های خارج از محدوده منجر به IndexError می‌شوند. توسط len() برگردانده می‌شود.

_type_

نوع هر عنصر در آرایه را مشخص می‌کند.

سازنده‌های زیرکلاس آرایه، آرگومان‌های جایگاهی را می‌پذیرند که برای مقداردهی اولیه‌ی عناصر به‌ترتیب استفاده می‌شوند.

ctypes.ARRAY(type, length)

یک آرایه ایجاد می‌کند. معادل type * length است، که در آن type یک نوع داده از ctypes و length یک عدد صحیح است.

منسوخ‌سازی نرم <Soft deprecated> از نسخه‌ی 3.14: به نفع ضرب.

class ctypes._Pointer

کلاس پایه خصوصی و انتزاعی برای اشاره‌گرها.

انواع اشاره‌گر مشخص با فراخوانی POINTER() با نوعی که به آن اشاره خواهد شد ایجاد می‌شوند؛ این کار به‌طور خودکار توسط pointer() انجام می‌شود.

اگر یک اشاره‌گر به یک آرایه اشاره کند، می‌توان عناصر آن را با استفاده از دسترسی‌های استاندارد زیرنویسی و اسلایسی خواند و نوشت. اشیاء اشاره‌گر اندازه‌ای ندارند، بنابراین len() TypeError را پرتاب خواهد کرد. زیرنویس‌های منفی حافظه‌ی پیش از اشاره‌گر را می‌خوانند (همان‌طور که در C نیز چنین است)، و اندیس‌های خارج از محدوده احتمالاً با نقض دسترسی (access violation) دچار فروپاشی خواهند شد (اگر خوش‌شانس باشید).

_type_

نوعی را که به آن اشاره شده است مشخص می‌کند.

contents

شیءای را که اشاره‌گر به آن اشاره می‌کند، برمی‌گرداند. انتساب به این ویژگی، اشاره‌گر را طوری تغییر می‌دهد که به شیء انتساب‌یافته اشاره کند.

استثناها

exception ctypes.ArgumentError

این استثنا زمانی پرتاب می‌شود که یک فراخوانی تابع خارجی نتواند یکی از آرگومان‌های ارسال‌شده را تبدیل کند.

exception ctypes.COMError(hresult, text, details)

این استثنا زمانی پرتاب می‌شود که فراخوانی یک متد COM ناموفق باشد.

hresult

مقدار عدد صحیحی که کد خطا را نشان می‌دهد.

text

پیام خطا.

details

۵-تاییِ (descr, source, helpfile, helpcontext, progid).

descr توضیح متنی است. source ProgID وابسته به زبان برای کلاس یا برنامه‌ای است که خطا را پرتاب کرده است. helpfile مسیر پرونده راهنما است. helpcontext شناسه زمینه راهنما است. progid ProgID رابطی است که خطا را تعریف کرده است.

اضافه شده در نسخه‌ی 3.14.