# 16.16. `ctypes` - Python的外部函数库

`ctypes`是Python的外部函数库。它提供C兼容的数据类型，并允许调用DLL或共享库中的函数。它可以用来将这些库封装在纯Python中。

## 16.16.1. ctypes 教程

注意：本教程中的代码示例使用`doctest`确保它们实际工作。由于一些代码示例在Linux，Windows或Mac OS X下表现不同，因此它们在注释中包含doctest指令。

注意：一些代码示例引用了ctypes `c_int`类型。在`sizeof（long） == sizeof（int）`的平台上，它是`c_long` 所以，如果您期望`c_int`打印`c_long` - 它们实际上是相同的类型，您不应该感到困惑。

### 16.16.1.1. 加载动态链接库

`ctypes`导出cdll对象，在Windows上导出windll和oledll对象，用于加载动态链接库。

加载的库作为这些（上述）对象的属性被访问。cdll加载使用标准`cdecl`调用约定导出函数的库，而windll库使用`stdcall`调用函数惯例。oledll也使用`stdcall`调用约定，并假定函数返回一个Windows `HRESULT`错误代码。错误代码用于在函数调用失败时自动引发`OSError`异常。

在版本3.3中已更改：之前Windows的错误通过`WindowsError`引发，现在是`OSError`的别名。

以下是Windows的一些示例。请注意，`msvcrt`是包含大多数标准C函数的MS标准C库，并使用cdecl调用约定：

    >>> from ctypes import *
    >>> print(windll.kernel32)
    <WinDLL 'kernel32', handle ... at ...>
    >>> print(cdll.msvcrt)
    <CDLL 'msvcrt', handle ... at ...>
    >>> libc = cdll.msvcrt
    >>>

Windows会自动附加通常的`.dll`文件后缀。

注意

通过`cdll.msvcrt`访问标准C库将使用该库的过时版本，该版本可能与Python正在使用的版本不兼容。在可能的情况下，使用本地Python功能，或者导入并使用`msvcrt`模块。

在Linux上，需要指定文件名（包括扩展名）以加载库，因此属性访问不能用于加载库。可以使用dll加载器的`LoadLibrary()`方法，或者通过调用构造函数创建CDLL的实例来加载库：

    >>> cdll.LoadLibrary("libc.so.6")
    <CDLL 'libc.so.6', handle ... at ...>
    >>> libc = CDLL("libc.so.6")
    >>> libc
    <CDLL 'libc.so.6', handle ... at ...>
    >>>

### 16.16.1.2. 从加载的dll中访问函数

函数作为dll对象的属性被访问：

    >>> from ctypes import *
    >>> 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 ?
      File "ctypes.py", line 239, in __getattr__
        func = _StdcallFuncPtr(name, self)
    AttributeError: function 'MyOwnFunction' not found
    >>>

请注意，像`kernel32`和`user32`这样的win32系统DLL通常会导出ANSI以及UNICODE版本的函数。UNICODE版本的名称附加了`W`，而ANSI版本的名称附有`A`。为给定模块名称返回一个模块句柄的win32 `GetModuleHandle`函数具有以下C原型，并且使用一个宏将其中一个暴露为`GetModuleHandle`取决于是否定义了UNICODE：

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

windll不会尝试通过magic选择其中的一个，您必须明确指定`GetModuleHandleA`或`GetModuleHandleW`来访问所需的版本，然后调用它分别与字节或字符串对象。

有时候，dll会导出名称不是有效Python标识符的函数，如`"??2@YAPAXI@Z"`。在这种情况下，您必须使用`getattr()`来检索函数：

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

在Windows上，一些dll导出函数不是按名称，而是按序号。这些函数可以通过使用序号索引dll对象来访问：

    >>> cdll.kernel32[1]
    <_FuncPtr object at 0x...>
    >>> cdll.kernel32[0]
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      File "ctypes.py", line 310, in __getitem__
        func = _StdcallFuncPtr(name, self)
    AttributeError: function ordinal 0 not found
    >>>

### 16.16.1.3. 调用函数

你可以像任何其他Python可调用一样调用这些函数。这个例子使用`time()`函数，它返回自Unix纪元以来的系统时间（秒）和`GetModuleHandleA()`函数，它返回一个win32模块句柄。

这个例子用NULL指针调用两个函数（`None`应该用作NULL指针）：

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

`ctypes`试图保护你免于调用具有错误数量的参数或错误的调用约定的函数。不幸的是，这只适用于Windows。它通过在函数返回后检查堆栈来实现，因此尽管出现错误，但函数已被调用：

    >>> windll.kernel32.GetModuleHandleA()
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    ValueError: Procedure probably called with not enough arguments (4 bytes missing)
    >>> windll.kernel32.GetModuleHandleA(0, 0)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    ValueError: Procedure probably called with too many arguments (4 bytes in excess)
    >>>

使用`cdecl`调用约定调用`stdcall`函数时会引发相同的异常，反之亦然：

    >>> cdll.kernel32.GetModuleHandleA(None)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    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 ?
    ValueError: Procedure probably called with too many arguments (4 bytes in excess)
    >>>

要找出正确的调用约定，必须查看C头文件或要调用的函数的文档。

在Windows上，`ctypes`使用win32结构化异常处理来防止在使用无效参数值调用函数时出现通用保护错误的崩溃：

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

然而，有足够的方法使Python与`ctypes`崩溃，所以你应该小心。`faulthandler`模块可以帮助您调试崩溃（例如，来自错误的C库调用产生的分割错误）。

`None`, integers, bytes objects and (unicode) strings are the only native Python objects that can directly be used as parameters in these function calls. `None` is passed as a C `NULL` pointer, bytes objects and strings are passed as pointer to the memory block that contains their data (`char *` or `wchar_t *`). Python整数作为平台默认的C `int`类型传递，它们的值被屏蔽以适合C类型。

在继续使用其他参数类型调用函数之前，我们必须了解更多关于`ctypes`数据类型。

### 16.16.1.4. 基本数据类型

`ctypes`定义了许多基本的C兼容数据类型：

ctypes类型 | C类型 | Python类型
--- | --- | ---
`c_bool` | `_Bool` | 布尔（1）
`c_char` | `char` | 1个字符的字节对象
`c_wchar` | `wchar_t` | 1个字符的字符串
`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`或`长 长` | INT
`c_ulonglong` | `无符号 __ int64`或`无符号 长 长 t6 > T3>` | INT
`c_size_t` | `size_t` | INT
`c_ssize_t` | `ssize_t`或`Py_ssize_t` | INT
`c_float` | `float` | 浮动
`c_double` | `double` | 浮动
`c_longdouble` | `long double` | 浮动
`c_char_p` | `char *`（NUL已终止） | 字节对象或`None`
`c_wchar_p` | `wchar_t *`（NUL已终止） | 字符串或`None`
`c_void_p` | `空 *` | int或`None`

1. 构造函数接受任何具有真值的对象。

所有这些类型都可以通过使用正确类型和值的可选初始值设定项来调用它们来创建：

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

由于这些类型是可变的，它们的值也可以在之后改变：

    >>> i = c_int(42)
    >>> print(i)
    c_long(42)
    >>> print(i.value)
    42
    >>> i.value = -99
    >>> print(i.value)
    -99
    >>>

为指针类型`c_char_p`，`c_wchar_p`和`c_void_p`的实例指定一个新值会改变它们指向的内存位置 to，不是内存块的内容（当然不是，因为Python字节对象是不可变的）：

    >>> s = "Hello, World"
    >>> c_s = c_wchar_p(s)
    >>> print(c_s)
    c_wchar_p('Hello, World')
    >>> c_s.value = "Hi, there"
    >>> print(c_s)
    c_wchar_p('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_string()（它用于早期的ctypes发行版）`。要创建包含C类型`wchar_t`的unicode字符的可变内存块，请使用`create_unicode_buffer()`函数。

### 16.16.1.5. 调用函数，继续

请注意，printf会打印到真正的标准输出通道而不是到`sys.stdout`，因此这些示例只能在控制台提示符下运行，而不能在IDLE < / t4>或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 ?
    ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2
    >>>

如前所述，除整数，字符串和字节对象之外的所有Python类型都必须用相应的`ctypes`类型包装，以便它们可以转换为所需的C数据类型：

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

### 16.16.1.6. 带有自定义类型的函数调用

您还可以自定义`ctypes`参数转换，以允许将您自己的类的实例用作函数参数。`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`，以便根据请求提供该属性。

### 16.16.1.7. 指定必须的参数类型 (函数原型)

通过设置`argtypes`属性，可以指定从DLL导出的函数所需的参数类型。

`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 ?
    ArgumentError: argument 2: exceptions.TypeError: wrong type
    >>> printf(b"%s %d %f\n", b"X", 2, 3)
    X 2 3.000000
    13
    >>>

如果你定义了你自己传递给函数调用的类，你必须实现一个`from_param()`类方法，以使它们能够在`argtypes`序列中使用它们。`from_param()`类方法接收传递给函数调用的Python对象，它应该执行typecheck或任何需要确保此对象是可被接受的工作，然后返回对象本身，其`_as_parameter_`属性，或者在这种情况下你想要作为C函数参数传递的任何东西。同样，结果应该是整数，字符串，字节，`ctypes`实例或具有`_as_parameter_`属性的对象。

### 16.16.1.8. 返回类型

默认情况下，函数被假定为返回C `int`类型。其它返回类型可以通过设置函数对象的`restype`属性来指定。

这是一个更高级的例子，它使用`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`属性，第二个参数将从单个字符Python字节对象成C字符：

    >>> strchr.restype = c_char_p
    >>> strchr.argtypes = [c_char_p, c_char]
    >>> strchr(b"abcdef", b"d")
    'def'
    >>> strchr(b"abcdef", b"def")
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    ArgumentError: argument 2: exceptions.TypeError: one character string expected
    >>> print(strchr(b"abcdef", b"x"))
    None
    >>> strchr(b"abcdef", b"d")
    'def'
    >>>

如果外部函数返回一个整数，您也可以使用可调用的Python对象（例如函数或类）作为`restype`属性。在C函数调用结束后会使用其返回的整数作为参数调用这个Py可调用对象,而其返回值作为函数调用的返回值.这对检查错误返回值并自动引发异常很有用：

    >>> 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 ?
      File "<stdin>", line 3, in ValidHandle
    OSError: [Errno 126] The specified module could not be found.
    >>>

`WinError`是一个函数，它将调用Windows `FormatMessage()` api来获取错误代码的字符串表示形式，并且返回一个异常。`WinError`接受一个可选的错误代码参数，如果没有人使用它，它会调用`GetLastError()`来检索它。

请注意，通过`errcheck`属性可以使用更强大的错误检查机制；详情请参阅参考手册。

### 16.16.1.9. 传递指针或通过引用传递参数

有时，一个C api函数期望一个指针作为参数的数据类型，可能想要写入相应的位置，或者因数据太大而无法通过值传递。这也被称为通过参考传递参数。

`ctypes`导出用于通过引用传递参数的`byref()`函数。使用`pointer()`函数可以实现相同的效果，虽然`pointer()`做了很多工作，因为它构造了一个真正的指针对象，因此如果你不需要Python中的指针对象本身，`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'
    >>>

### 16.16.1.10. 结构体和联合

结构和联合必须继承自`ctypes`模块中定义的`Structure`和`Union`基类。每个子类都必须定义一个`_fields_`属性。`_fields_`必须是包含字段名称和字段类型的2元组的列表。

字段类型必须是`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 ?
    ValueError: too many initializers
    >>>

但是，您可以构建更复杂的结构。通过使用结构作为字段类型，结构本身可以包含其他结构。

Here is a RECT structure which contains two POINTs named upperleft and 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，它们对调试很有用，因为它们可以提供有用的信息：

    >>> print(POINT.x)
    <Field type=c_long, ofs=0, size=4>
    >>> print(POINT.y)
    <Field type=c_long, ofs=4, size=4>
    >>>

警告

`ctypes` 不支持以传值方式传递 带位域的共用体或结构体 给函数。虽然这可能适用于32位x86，但图书馆无法保证在一般情况下工作。应该始终以指针的形式将带位域的共用体和结构体传递给函数。

### 16.16.1.11. 结构体/共用体的对齐方式和字节序

默认情况下，Structure和Union字段的对齐方式与C编译器所做的相同。可以在子类定义中指定`_pack_` class属性来覆盖此行为。这必须设置为正整数并指定字段的最大对齐方式。这与在MSVC中`#pragma pack（n）`功能一样

`ctypes`使用结构和联合的本地字节顺序。To build structures with non-native byte order, you can use one of the `BigEndianStructure`, `LittleEndianStructure`, `BigEndianUnion`, and `LittleEndianUnion` base classes. 这些类不能包含指针字段。

### 16.16.1.12. 在结构体和联合中的位域

可以创建包含位域的结构和联合。位字段仅适用于整数字段，位宽指定为`_fields_`元组中的第三项：

    >>> class Int(Structure):
    ...     _fields_ = [("first_16", c_int, 16),
    ...                 ("second_16", c_int, 16)]
    ...
    >>> print(Int.first_16)
    <Field type=c_long, ofs=0:0, bits=16>
    >>> print(Int.second_16)
    <Field type=c_long, ofs=0:16, bits=16>
    >>>

### 16.16.1.13. 数组

数组是序列，包含固定数量的相同类型的实例。

推荐的创建数组类型的方法是将数据类型乘以正整数：

    TenPointsArrayType = POINT * 10

下面是一个有点人为的数据类型的例子，它是一个包含4个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
    >>>

### 16.16.1.14. 指针

指针实例是通过调用`ctypes`类型的`pointer()`函数创建的：

    >>> 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`实例会导致指针指向存储它的内存位置：

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

也可以使用不同于0的索引，但是您必须知道自己在做什么，就像在C中一样：您可以访问或更改任意内存位置。Generally you only use this feature if you receive a pointer from a C function, and you know that the pointer actually points to an array instead of a single item.

在幕后，`pointer()`函数不仅仅是创建指针实例，它必须首先创建指针types。这是通过`POINTER()`函数完成的，该函数接受任何`ctypes`类型，并返回一个新类型：

    >>> PI = POINTER(c_int)
    >>> PI
    <class 'ctypes.LP_c_long'>
    >>> PI(42)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    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` checks for `NULL` when dereferencing pointers (but dereferencing invalid non-`NULL` pointers would crash Python):

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

### 16.16.1.15. 类型约定

通常，ctypes会进行严格的类型检查。这意味着，如果在函数的`argtypes`列表中有`POINTER(c_int)`，或者在结构体定义中作为成员字段的类型，则实参只能接受相同类型的实例。这个规则有一些例外，其中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
    >>>

另外，如果函数参数在`argtypes`中显式声明为指针类型（例如`POINTER(c_int)`），则指向类型的对象（`c_int`）可以传递给函数。在这种情况下，ctypes将自动应用所需的`byref()`转换。

要将POINTER类型字段设置为`NULL`，您可以分配`None`：

    >>> bar.values = None
    >>>

有时你有不兼容类型的实例。在C中，您可以将一种类型转换为另一种类型。`ctypes`提供了一个可以以相同方式使用的`cast()`函数。上面定义的`Bar`结构为其`values`字段接受`POINTER(c_int)`指针或`c_int`数组，但不接受实例其他类型：

    >>> bar.values = (c_byte * 4)()
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    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()`指定`Bar`的`values`字段的结构：

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

### 16.16.1.16. 函数回调

不完全类型是其成员尚未指定的结构，联合或数组。在C中，它们由后面定义的前向声明指定：

    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 ?
      File "<stdin>", line 2, in cell
    NameError: name 'cell' is not defined
    >>>

因为新的`类 cell`在类语句本身中不可用。在`ctypes`中，我们可以定义`cell`类，并在稍后的class声明之后设置`_fields_`属性：

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

让我们尝试一下。我们创建了`cell`的两个实例，让它们指向对方，最后跟随指针链几次：

    >>> c1 = cell()
    >>> c1.name = "foo"
    >>> c2 = cell()
    >>> c2.name = "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
    >>>

### 16.16.1.17. 函数回调

`ctypes`允许从Python可调用对象中创建C可调用函数指针。这些有时称为回调函数。

首先，您必须为回调函数创建一个类。该类知道调用约定，返回类型以及此函数将接收的参数的数量和类型。

`CFUNCTYPE()`工厂函数使用`cdecl`调用约定为回调函数创建类型。在Windows上，`WINFUNCTYPE()`工厂函数使用`stdcall`调用约定为回调函数创建类型。

这两个工厂函数都是以结果类型作为第一个参数来调用的，而回调函数期望的参数类型作为剩余的参数。

我将在这里展示一个使用标准C库的`qsort()`函数的示例，该函数用于通过回调函数对项目进行排序。`qsort()` will be used to sort an array of integers:

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

`qsort()` must be called with a pointer to the data to sort, the number of items in the data array, the size of one item, and a pointer to the comparison function, the callback. 这个回调函数将被两个指向项目的指针调用，如果第一个项目小于第二个项目，它必须返回一个负整数，如果它们相等则返回一个零，否则返回一个正整数。

所以我们的回调函数接收指向整数的指针，并且必须返回一个整数。首先我们为回调函数创建`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
    >>>

注意

只要从C代码中使用它们，确保保持对`CFUNCTYPE()`对象的引用。`ctypes`不会，如果你不这样做，它们可能会被垃圾收集，在回调时崩溃你的程序。

另外，请注意，如果在Python控制之外创建的线程中调用回调函数（例如，通过调用回调的外部代码），ctypes在每次调用时创建一个新的虚拟Python线程。This behavior is correct for most purposes, but it means that values stored with `threading.local` will not survive across different callbacks, even when those calls are made from the same C thread.

### 16.16.1.18. 对dlls导出值的访问

一些共享库不仅可以导出函数，还可以导出变量。Python库本身的例子是`Py_OptimizeFlag`，一个整数设置为0,1或2，取决于`-O`或`-OO`

`ctypes`可以使用类型的`in_dll()`类方法访问像这样的值。pythonapi是一个预定义的符号，可以访问Python C api：

    >>> opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
    >>> print(opt_flag)
    c_long(0)
    >>>

如果解释器启动带有 `-O`, 这个例子会打印`c_long(1)`, 或者打印 `c_long(2)` 如果指定/(带有) `-OO` 。

一个扩展的例子也演示了如何使用指针来访问由Python导出的`PyImport_FrozenModules`指针。

引用该价值的文档：

> This pointer is initialized to point to an array of `struct _frozen` records, terminated by one whose members are all NULL or zero. When a frozen module is imported, it is searched in this table. 第三方代码可以利用此技巧提供一个动态创建的静态模块集合.

所以操纵这个指针甚至可以证明是有用的。要限制示例大小，我们只显示如何使用`ctypes`读取此表：

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

我们已经定义了`struct _frozen`数据类型，所以我们可以得到指向表的指针：

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

由于`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
    __hello__ 161
    __phello__ -161
    __phello__.spam 161
    >>>

标准Python有一个冻结模块和一个冻结包（由负大小成员表示）的事实并不为人所熟知，它仅用于测试。例如，试用`import __ hello __`。

### 16.16.1.19. 意外

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

嗯。We certainly expected the last statement to print `3 4 1 2`. 发生了什么？Here are the steps of the `rc.a, rc.b = rc.b, rc.a` line above:

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

Note that `temp0` and `temp1` are objects still using the internal buffer of the `rc` object above. So executing `rc.a = temp0` copies the buffer contents of `temp0` into `rc` ‘s buffer. 这又反过来改变了`temp1`的内容。所以，最后一个分配`rc.b = temp1`，没有预期的效果。

请记住，从Structure，Unions和Arrays中检索子对象不会复制子对象，而是检索访问根对象的基础缓冲区的包装对象。

另一个可能与预期不同的例子是：

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

为什么要打印`False`？ctypes实例是包含一个内存块和一些访问内存内容的descriptor的对象。在存储器块中存储Python对象不会存储对象本身，而是存储对象的`contents`。每次访问内容都会构造一个新的Python对象！

### 16.16.1.20. 可变大小的数据类型

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

这很好，但是如何访问此数组中包含的其他元素？由于type仍然只知道大约4个元素，我们得到访问其他元素的错误：

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

另一种使用`ctypes`的可变大小的数据类型的方法是使用Python的动态特性，并且在需要的大小已知之后（重新）定义数据类型，以个案为基础。

## 16.16.2. ctypes参考

### 16.16.2.1. 查找共享库

以编译语言编程时，在编译/链接程序和程序运行时访问共享库。

`find_library()`函数的用途是以类似于编译器的方式来定位库（在具有多个版本的共享库的平台上最近应该加载），而ctypes库加载程序就像程序运行时一样，直接调用运行时加载程序。

`ctypes.util`模块提供了一个函数，可以帮助确定要加载的库。

`ctypes.util.``find_library`(name)

  尝试找到一个库并返回一个路径名。name是不带任何前缀（如lib），库后缀`.so`，`.dylib`或版本号这是用于posix链接器选项`-l` 如果没有找到库，则返回`None`。

确切的功能依赖于系统。

在Linux上，`find_library()`尝试运行外部程序（`/sbin/ldconfig`，`gcc`和`objdump`）找到库文件。它返回库文件的文件名。这里有些例子：

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

On OS X, `find_library()` tries several predefined naming schemes and paths to locate the library, and returns a full pathname if successful:

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

On Windows, `find_library()` searches along the system search path, and returns the full pathname, but since there is no predefined naming scheme a call like `find_library("c")` will fail and return `None`.

If wrapping a shared library with `ctypes`, it may be better to determine the shared library name at development time, and hardcode that into the wrapper module instead of using `find_library()` to locate the library at runtime.

### 16.16.2.2. 加载共享库

有几种方法可以将共享库加载到Python进程中。一种方法是实例化以下类之一：

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

  这个类的实例代表加载的共享库。这些库中的函数使用标准的C调用约定，并假定返回`int`。

class `ctypes.``OleDLL`(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

  仅限Windows：此类的实例表示已加载的共享库，这些库中的函数使用`stdcall`调用约定，并假定返回特定于窗口的`HRESULT`代码。`HRESULT` values contain information specifying whether the function call failed or succeeded, together with additional error code. 如果返回值表示失败，则会自动产生`OSError`。

  在版本3.3中更改： `WindowsError`曾被提及。

class `ctypes.``WinDLL`(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

  仅Windows：此类的实例表示已加载的共享库，这些库中的函数使用`stdcall`调用约定，并且默认情况下会假定返回`int`。

  在Windows CE上，只使用标准调用约定，为了方便，`WinDLL`和`OleDLL`在此平台上使用标准调用约定。

在调用这些库导出的任何函数之前释放Python global interpreter lock，然后重新获取。

class `ctypes.``PyDLL`(name, mode=DEFAULT_MODE, handle=None)

  Instances of this class behave like `CDLL` instances, except that the Python GIL is not released during the function call, and after the function execution the Python error flag is checked. 如果设置了错误标志，则会引发Python异常。

  因此，这仅用于直接调用Python C api函数。

所有这些类都可以通过调用至少一个参数（共享库的路径名）来实例化。如果您已经有一个已经加载的共享库的句柄，它可以作为`handle`命名参数传递，否则底层平台`dlopen`或`LoadLibrary函数用于将库加载到进程中，并获取它的句柄。`

mode参数可用于指定库的加载方式。有关详细信息，请参阅Windows上的dlopen（3）联机帮助页，mode将被忽略。

use_errno参数设置为True时，将启用一个ctypes机制，以允许以安全的方式访问系统`errno`错误编号。`ctypes` maintains a thread-local copy of the systems `errno` variable; if you call foreign functions created with `use_errno=True` then the `errno` value before the function call is swapped with the ctypes private copy, the same happens immediately after the function call.

函数`ctypes.get_errno()`返回ctypes专用副本的值，函数`ctypes.set_errno()`将ctypes专用副本更改为新值并返回前值。

The use_last_error parameter, when set to True, enables the same mechanism for the Windows error code which is managed by the `GetLastError()` and `SetLastError()` Windows API functions; `ctypes.get_last_error()` and `ctypes.set_last_error()` are used to request and change the ctypes private copy of the windows error code.

`ctypes.``RTLD_GLOBAL`

  标记用作模式参数。在该标志不可用的平台上，它被定义为整数零。

`ctypes.``RTLD_LOCAL`

  标记用作模式参数。在不可用的平台上，它与RTLD_GLOBAL相同。

`ctypes.``DEFAULT_MODE`

  用于加载共享库的默认模式。在OSX 10.3上，这是RTLD_GLOBAL，否则它与RTLD_LOCAL相同。

这些类的实例没有公共方法。共享库导出的函数可以作为属性或索引进行访问。请注意，通过属性访问函数会缓存结果，因此每次重复访问它都会返回相同的对象。另一方面，通过索引访问它每次都会返回一个新对象：

    >>> libc.time == libc.time
    True
    >>> libc['time'] == libc['time']
    False

以下公共属性可用，它们的名称以下划线开头，不与导出的函数名称冲突：

`PyDLL.``_handle`

  用于访问库的系统句柄。

`PyDLL.``_name`

  在构造函数中传递的库的名称。

共享库也可以通过使用作为`LibraryLoader`类的实例的预制对象之一加载，或者通过调用`LoadLibrary()`方法或通过检索库作为加载器实例的属性。

class `ctypes.``LibraryLoader`(dlltype)

  加载共享库的类。dlltype应该是`CDLL`，`PyDLL`，`WinDLL`或`OleDLL`类型之一。

  `__getattr__()`具有特殊的行为：它允许通过访问共享库作为库加载器实例的属性来加载共享库。结果被缓存，所以重复的属性访问每次返回相同的库。

  `LoadLibrary`(name)

    将共享库加载到进程中并返回。此方法始终返回库的新实例。

下面的预制库加载器可以利用：

`ctypes.``cdll`

  创建`CDLL`实例。

`ctypes.``windll`

  仅限Windows：创建`WinDLL`实例。

`ctypes.``oledll`

  仅限Windows：创建`OleDLL`实例。

`ctypes.``pydll`

  创建`PyDLL`实例。

为了直接访问C Python api，可以使用一个随时可用的Python共享库对象：

`ctypes.``pythonapi`

  一个`PyDLL`实例，它将Python C API函数公开为属性。请注意，所有这些函数都假定返回C `int`，这当然不总是事实，所以您必须分配正确的`restype`属性来使用这些函数。

### 16.16.2.3. 外部函数

如前一节所述，外部函数可以作为加载共享库的属性来访问。默认以这种方式创建的函数对象接受任意数量的参数，接受任何ctypes数据实例作为参数，并返回由库加载器指定的默认结果类型。他们是一个私人课堂的例子：

class `ctypes.``_FuncPtr`

  C可调用外部函数的基类。

  外部函数的实例也是C兼容的数据类型；它们代表C函数指针。

  这种行为可以通过分配给外部函数对象的特殊属性来定制。

  `restype`

    指定一个ctypes类型来指定外部函数的结果类型。对`void`使用`None`，该函数不返回任何内容。

    可以分配一个不是ctypes类型的可调用Python对象，在这种情况下，该函数被假定为返回一个C `int`，并且可调用对象将被调用该整数，从而允许进一步处理或错误检查。不推荐使用这种方式，为了更灵活的后处理或错误检查，请使用ctypes数据类型作为`restype`，并为`errcheck`属性指定一个可调用对象。

  `argtypes`

    分配一个ctypes类型的元组来指定该函数接受的参数类型。使用`stdcall`调用约定的函数只能使用与该元组的长度相同数量的参数进行调用；使用C调用约定的函数也接受附加的、未指定的参数。

    当调用外部函数时，每个实际参数都会传递给`argtypes`元组中项目的`from_param()`类方法，该方法允许将实际参数调整为该外部函数接受的对象。例如，`argtypes`元组中的`c_char_p`项将使用ctypes转换规则将作为参数传递的字符串转换为字节对象。

    新增功能：现在可以将项目放入非ctypes类型的argtypes中，但每个项目必须有一个返回可用作参数（整数，字符串，ctypes实例）的值的`from_param()`方法。这允许定义可以将自定义对象作为函数参数的适配器。

  `errcheck`

    为此属性分配一个Python函数或另一个可调用对象。可调用函数将被调用三个或更多参数：

    `callable`(result, func, arguments)

      结果是外部函数返回的结果，由`restype`属性指定。

      func是外部函数对象本身，这允许重用相同的可调用对象来检查或后处理几个函数的结果。

      arguments是一个包含最初传递给函数调用的参数的元组，这允许专门化所使用参数的行为。

    该函数返回的对象将从外部函数调用中返回，但它也可以检查结果值并在外部函数调用失败时引发异常。

exception `ctypes.``ArgumentError`

  当外部函数调用不能转换传递的参数之一时引发此异常。

### 16.16.2.4. 函数原型

外部函数也可以通过实例化函数原型来创建。函数原型与C中的函数原型相似；他们描述了一个函数（返回类型，参数类型，调用约定），而没有定义实现。必须使用所需的结果类型和函数的参数类型来调用工厂函数。

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

  返回的函数原型创建使用标准C调用约定的函数。该功能将在通话过程中释放GIL。If use_errno is set to True, the ctypes private copy of the system `errno` variable is exchanged with the real `errno` value before and after the call; use_last_error does the same for the Windows error code.

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

  仅Windows：返回的函数原型创建使用`stdcall`调用约定的函数，除了在Windows CE上，`WINFUNCTYPE()`与`CFUNCTYPE()`该功能将在通话过程中释放GIL。use_errno和use_last_error具有与上述相同的含义。

`ctypes.``PYFUNCTYPE`(restype, *argtypes)

  返回的函数原型创建使用Python调用约定的函数。在通话过程中，功能不会释放GIL。

这些工厂函数创建的函数原型可以以不同的方式实例化，具体取决于调用中参数的类型和数量：

> `prototype`(address)

>   在指定的地址（address）返回外部函数，返回类型为整形

> `prototype`(callable)

>   Create a C callable function (a callback function) from a Python callable.

> `prototype`(func_spec[, paramflags])

>   Returns a foreign function exported by a shared library. func_spec must be a 2-tuple `(name_or_ordinal, library)`. The first item is the name of the exported function as string, or the ordinal of the exported function as small integer. The second item is the shared library instance.

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

>   Returns a foreign function that will call a COM method. vtbl_index is the index into the virtual function table, a small non-negative integer. name is name of the COM method. iid is an optional pointer to the interface identifier which is used in extended error reporting.

>   COM methods use a special calling convention: They require a pointer to the COM interface as first argument, in addition to those parameters that are specified in the `argtypes` tuple.

> The optional paramflags parameter creates foreign function wrappers with much more functionality than the features described above.

> paramflags must be a tuple of the same length as `argtypes`.

> Each item in this tuple contains further information about a parameter, it must be a tuple containing one, two, or three items.

> The first item is an integer containing a combination of direction flags for the parameter:

> > 1
> >   Specifies an input parameter to the function.
> > 2
> >   Output parameter. The foreign function fills in a value.
> > 4
> >   Input parameter which defaults to the integer zero.

> The optional second item is the parameter name as string. If this is specified, the foreign function can be called with named parameters.

> The optional third item is the default value for this parameter.

此示例演示如何包装Windows `MessageBoxA`函数，以便它支持默认参数和命名参数。从Windows头文件的C声明是这样的：

    WINUSERAPI int WINAPI
    MessageBoxA(
        HWND hWnd,
        LPCSTR lpText,
        LPCSTR lpCaption,
        UINT uType);

这是用`ctypes`打包的：

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

现在可以通过以下方式调用MessageBox外部函数：

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

第二个示例演示输出参数。win32 `GetWindowRect`函数通过将指定窗口的维复制到调用者必须提供的`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`协议结合使用，以进行进一步的输出处理和错误检查。win32 `GetWindowRect` api函数返回一个`BOOL`来表示成功或失败，所以这个函数可以进行错误检查，并在api调用失败时引发异常：

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

如果`errcheck`函数返回参数元组，它将保持不变，`ctypes`将继续其在输出参数上执行的正常处理。If you want to return a tuple of window coordinates instead of a `RECT` instance, you can retrieve the fields in the function and return them instead, the normal processing will no longer take place:

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

### 16.16.2.5. 工具函数

`ctypes.``addressof`(obj)

  以整数形式返回内存缓冲区的地址。obj必须是一个ctypes类型的实例。

`ctypes.``alignment`(obj_or_type)

  返回ctypes类型的对齐要求。obj_or_type必须是ctypes类型或实例。

`ctypes.``byref`(obj[, offset])

  返回指向obj的轻量级指针，该指针必须是ctypes类型的实例。偏移量默认为零，并且必须是一个将被添加到内部指针值的整数。

  `byref（obj， offset）`对应于这个C代码：

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

  返回的对象只能用作外部函数调用参数。它的行为与`pointer(obj)`类似，但构造速度更快。

`ctypes.``cast`(obj, type)

  该函数与C中的cast操作符类似。它返回一个指向与obj相同的内存块的type新实例。type必须是指针类型，obj必须是可以解释为指针的对象。

`ctypes.``create_string_buffer`(init_or_size, size=None)

  这个函数创建一个可变的字符缓冲区。返回的对象是`c_char`的ctypes数组。

  init_or_size must be an integer which specifies the size of the array, or a bytes object which will be used to initialize the array items.

  如果一个字节对象被指定为第一个参数，那么缓冲区会比其长度大一个项目，以便数组中的最后一个元素是一个NUL终止字符。一个整数可以作为第二个参数传递，它允许指定数组的大小，如果不应该使用字节的长度。

`ctypes.``create_unicode_buffer`(init_or_size, size=None)

  这个函数创建一个可变的unicode字符缓冲区。返回的对象是`c_wchar`的ctypes数组。

  init_or_size必须是一个整数，它指定数组的大小，或一个将用于初始化数组项的字符串。

  如果一个字符串被指定为第一个参数，那么缓冲区会比字符串的长度大一个项目，以便数组中的最后一个元素是一个NUL终止字符。一个整数可以作为第二个参数传递，它允许指定数组的大小，如果不应该使用字符串的长度。

`ctypes.``DllCanUnloadNow`()

  仅Windows：这个函数是一个钩子，它允许实现具有ctypes的进程内COM服务器。它是从DllCanUnloadNow函数调用的_ctypes扩展dll导出的。

`ctypes.``DllGetClassObject`()

  仅Windows：这个函数是一个钩子，它允许实现具有ctypes的进程内COM服务器。它是从DllGetClassObject函数调用的`_ctypes`扩展名dll导出的。

`ctypes.util.``find_library`(name)

  尝试找到一个库并返回一个路径名。name是没有任何前缀（如`lib`），库后缀`.so`，`.dylib`或版本号这是用于posix链接器选项`-l` 如果没有找到库，则返回`None`。

  确切的功能依赖于系统。

`ctypes.util.``find_msvcrt`()

  仅Windows：返回Python使用的VC运行时库的文件名，以及扩展模块。如果无法确定库的名称，则返回`None`。

  如果你需要释放内存，例如，由扩展模块通过调用`free（void *）`来分配内存，这很重要您在分配内存的同一个库中使用该函数。

`ctypes.``FormatError`([code])

  仅限Windows：返回错误代码代码的文本描述。如果未指定错误代码，则通过调用Windows API函数GetLastError来使用上一个错误代码。

`ctypes.``GetLastError`()

  仅Windows：返回调用线程中由Windows设置的最后一个错误代码。该函数直接调用Windows GetLastError()函数，它不会返回错误代码的ctypes-private副本。

`ctypes.``get_errno`()

  在调用线程中返回系统`errno`变量​​的ctypes-private副本的当前值。

`ctypes.``get_last_error`()

  仅Windows：在调用线程中返回系统`LastError`变量​​的ctypes-private副本的当前值。

`ctypes.``memmove`(dst, src, count)

  与标准C memmove库函数相同：将count个字节从src复制到dst。dst和src必须是可以转换为指针的整数或ctypes实例。

`ctypes.``memset`(dst, c, count)

  与标准C memset库函数相同：使用值c的count字节填充地址dst处的存储器块。 dst必须是指定地址的整数，或ctypes实例。

`ctypes.``POINTER`(type)

  这个工厂函数创建并返回一个新的ctypes指针类型。指针类型被缓存并在内部重用，所以重复调用这个函数很便宜。类型必须是ctypes类型。

`ctypes.``pointer`(obj)

  这个函数创建一个新的指针实例，指向obj。返回的对象是`POINTER(type(obj))`类型。

  注意：如果你只是想把对象的指针传递给外部函数调用，你应该使用`byref(obj)`，这个速度要快得多。

`ctypes.``resize`(obj, size)

  此函数调整obj的内部缓冲区，该缓冲区必须是ctypes类型的实例。如`sizeof(type(obj))`所示，不可能使缓冲区小于对象类型的本地大小，但可以放大缓冲区。

`ctypes.``set_errno`(value)

  将调用线程中system `errno`变量​​的ctypes-private副本的当前值设置为值并返回以前的值。

`ctypes.``set_last_error`(value)

  仅Windows：将调用线程中系统`LastError`变量​​的ctypes-private副本的当前值设置为值并返回以前的值。

`ctypes.``sizeof`(obj_or_type)

  返回ctypes类型或实例内存缓冲区的大小（以字节为单位）。与C `sizeof`运算符相同。

`ctypes.``string_at`(address, size=-1)

  此函数返回从内存地址address开始的C字符串作为字节对象。如果指定了大小，则将其用作大小，否则将假定该字符串为零终止。

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

  仅Windows：这个函数可能是ctypes中命名最差的东西。它创建了一个OSError实例。如果未指定code，则调用`GetLastError`来确定错误代码。如果descr没有被指定, `FormatError()`被调用来获取错误（error）的文本描述。

  在版本3.3中更改：曾经创建`WindowsError`的实例。

`ctypes.``wstring_at`(address, size=-1)

  该函数以字符串的形式返回从内存地址地址开始的宽字符串。如果指定了size，则将其用作字符串的字符数，否则将假定字符串为零终止。

### 16.16.2.6. 数据类型

class `ctypes.``_CData`

  这个非公共类是所有ctypes数据类型的公共基类。除此之外，所有ctypes类型实例都包含一个保存C兼容数据的内存块；内存块的地址由`addressof()`辅助函数返回。另一个实例变量暴露为`_objects`；这包含其他Python对象，在内存块包含指针的情况下需要保持活动状态。

  ctypes数据类型的通用方法，这些都是类方法（确切地说，它们是metaclass的方法）：

  `from_buffer`(source[, offset])

    此方法返回共享source对象的缓冲区的ctypes实例。source对象必须支持可写缓冲区接口。可选的offset参数以字节为单位指定源缓冲区的偏移量；默认值为零。如果源缓冲区不够大，则会引发`ValueError`。

  `from_buffer_copy`(source[, offset])

    此方法创建一个ctypes实例，从必须可读的source对象缓冲区中复制缓冲区。可选的offset参数以字节为单位指定源缓冲区的偏移量；默认值为零。如果源缓冲区不够大，则会引发`ValueError`。

  `from_address`(address)

    此方法使用由address指定的内存返回一个ctypes类型实例，该内存必须是一个整数。

  `from_param`(obj)

    此方法将obj调整为ctypes类型。当类型出现在外部函数的`argtypes`元组中时，它将与外部函数调用中使用的实际对象一起调用；它必须返回一个可以用作函数调用参数的对象。

    所有的ctypes数据类型都有这个classmethod的默认实现，通常返回obj，如果这是一个类型的实例。有些类型也接受其他对象。

  `in_dll`(library, name)

    此方法返回由共享库导出的ctypes类型实例。name是导出数据的符号的名称，library是加载的共享库。

  ctypes数据类型的通用实例变量：

  `_b_base_`

    有时ctypes数据实例不拥有它们包含的内存块，而是共享基础对象的部分内存块。`_b_base_`只读成员是拥有内存块的root ctypes对象。

  `_b_needsfree_`

    此只读变量在ctypes数据实例已分配内存块本身时为true，否则为false。

  `_objects`

    该成员要么是`None`，要么是包含需要保持活动状态的Python对象的字典，以便内存块内容保持有效。该对象仅用于调试；永远不要修改这本词典的内容。

### 16.16.2.7. 基本数据类型

class `ctypes.``_SimpleCData`

  这个非公共类是所有基本ctypes数据类型的基类。这里提到它是因为它包含基本ctypes数据类型的通用属性。`_SimpleCData`是`_CData`的子类，所以它继承了它们的方法和属性。现在可以对不包含指针的ctypes数据类型进行酸洗。

  实例具有单个属性：

  `value`

    该属性包含实例的实际值。对于整数和指针类型，它是一个整数，对于字符类型，它是单字节字节对象或字符串，对于字符指针类型，它是一个Python字节对象或字符串。

    当从ctypes实例中检索`value`属性时，通常每次都会返回一个新对象。`ctypes` does not implement original object return, always a new object is constructed. 所有其他ctypes对象实例也是如此。

基本数据类型作为外部函数调用结果返回时，或者例如通过检索结构体字段成员或数组项，将被透明地转换为本机Python类型。换句话说，如果外部函数有`c_char_p`的`restype`，您将总是收到一个Python字节对象，而不是 一个 `c_char_p`实例。

基本数据类型的子类不会继承此行为。因此，如果外部函数`restype`是`c_void_p`的子类，那么您将从函数调用中接收此子类的实例。当然，您可以通过访问`value`属性来获取指针的值。

这些是基本的ctypes数据类型：

class `ctypes.``c_byte`

  表示C `有符号 char`数据类型，并将该值解释为小整数。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_char`

  表示C `char`数据类型，并将该值解释为单个字符。构造函数接受一个可选的字符串初始值设定项，字符串的长度必须恰好为一个字符。

class `ctypes.``c_char_p`

  当它指向以零结尾的字符串时，表示C `char *`数据类型。对于也可能指向二进制数据的通用字符指针，必须使用`POINTER(c_char)`。该构造函数接受一个整数地址或一个字节对象。

class `ctypes.``c_double`

  表示C `double`数据类型。构造函数接受一个可选的浮点初始值设定项。

class `ctypes.``c_longdouble`

  表示C `long double`数据类型。构造函数接受一个可选的浮点初始值设定项。在`sizeof（long double） == sizeof（double）`是`c_double`的别名。

class `ctypes.``c_float`

  表示C `float`数据类型。构造函数接受一个可选的浮点初始值设定项。

class `ctypes.``c_int`

  表示C `有符号的 int`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。在`sizeof（int） == sizeof（long）`的平台上，它是`c_long`

class `ctypes.``c_int8`

  表示C 8位`有符号 int`数据类型。通常是`c_byte`的别名。

class `ctypes.``c_int16`

  表示C 16位`有符号 int`数据类型。通常是`c_short`的别名。

class `ctypes.``c_int32`

  表示C 32位`有符号 int`数据类型。通常是`c_int`的别名。

class `ctypes.``c_int64`

  表示C 64位`有符号 int`数据类型。通常是`c_longlong`的别名。

class `ctypes.``c_long`

  表示C `signed long`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_longlong`

  表示C `有符号 长 长`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_short`

  表示C `signed short`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_size_t`

  表示C `size_t`数据类型。

class `ctypes.``c_ssize_t`

  表示C `ssize_t`数据类型。

  版本3.2中的新功能。

class `ctypes.``c_ubyte`

  表示C `无符号 char`数据类型，它将该值解释为小整数。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_uint`

  表示C `无符号 int`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。在`sizeof（int） == sizeof（long）`的平台上，它是`c_ulong`

class `ctypes.``c_uint8`

  Represents the C 8-bit `unsigned int` datatype. 通常是`c_ubyte`的别名。

class `ctypes.``c_uint16`

  表示C 16位`无符号 int`数据类型。通常是`c_ushort`的别名。

class `ctypes.``c_uint32`

  表示C 32位`无符号 int`数据类型。通常是`c_uint`的别名。

class `ctypes.``c_uint64`

  表示C 64位`无符号 int`数据类型。通常是`c_ulonglong`的别名。

class `ctypes.``c_ulong`

  表示C `无符号 long`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_ulonglong`

  表示C `无符号 long long`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_ushort`

  表示C `无符号 short`数据类型。构造函数接受一个可选的整数初始值设定项；没有溢出检查完成。

class `ctypes.``c_void_p`

  表示C `void *`类型。该值表示为整数。构造函数接受一个可选的整数初始值设定项。

class `ctypes.``c_wchar`

  表示C `wchar_t`数据类型，并将该值解释为单个字符的unicode字符串。构造函数接受一个可选的字符串初始值设定项，字符串的长度必须恰好为一个字符。

class `ctypes.``c_wchar_p`

  表示C `wchar_t *`数据类型，它必须是指向零终止的宽字符字符串的指针。构造函数接受一个整数地址或一个字符串。

class `ctypes.``c_bool`

  表示C `bool`数据类型（更准确地说，来自C99的`_Bool`）。它的值可以是`True`或`False`，并且构造函数接受任何具有真值的对象。

class `ctypes.``HRESULT`

  仅限Windows：表示一个`HRESULT`值，其中包含函数或方法调用的成功或错误信息。

class `ctypes.``py_object`

  表示C `PyObject *`数据类型。不带参数调用它会创建一个`NULL` `PyObject *`指针。

`ctypes.wintypes`模块提供了一些其他特定于Windows的数据类型，例如`HWND`，`WPARAM`或`DWORD` 。还定义了一些有用的结构，如`MSG`或`RECT`。

### 16.16.2.8. 结构化数据类型

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

  本地字节顺序的联合的抽象基类。

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

  用于big endian字节顺序的结构的抽象基类。

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

  小端字节顺序的结构的抽象基类。

具有非本地字节顺序的结构不能包含指针类型字段或包含指针类型字段的任何其他数据类型。

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

  native字节顺序的结构的抽象基类。

  具体结构和联合类型必须通过继承这些类型之一来创建，并至少定义一个`_fields_`类变量。`ctypes`将创建descriptor，允许通过直接属性访问来读写字段。这些是

  `_fields_`

    定义结构字段的序列。这些项目必须是2元组或3元组。第一个项目是字段的名称，第二个项目指定字段的类型；它可以是任何ctypes数据类型。

    对于像`c_int`这样的整数类型字段，可以给出第三个可选项。它必须是一个定义字段位宽的小正整数。

    一个结构或联合中的字段名称必须是唯一的。这不检查，名称重复时只能访问一个字段。

    可以在定义Structure子类的类语句之后定义`_fields_`类变量，这允许直接或间接引用它们自己的数据类型：

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

    The `_fields_` class variable must, however, be defined before the type is first used (an instance is created, `sizeof()` is called on it, and so on). 稍后分配给`_fields_`类变量将引发一个AttributeError。

    可以定义结构类型的子子类，它们继承基类的字段加上在子子类中定义的`_fields_`（如果有的话）。

  `_pack_`

    一个可选的小整数，它允许覆盖实例中结构字段的对齐方式。`_fields_`分配时必须已经定义`_pack_`，否则它将不起作用。

  `_anonymous_`

    列出未命名（匿名）字段名称的可选序列。`_anonymous_` must be already defined when `_fields_` is assigned, otherwise it will have no effect.

    此变量中列出的字段必须是结构或联合类型字段。`ctypes`将在结构类型中创建描述符，允许直接访问嵌套字段，而不需要创建结构或联合字段。

    这是一个示例类型（Windows）：

        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`字段指定哪个联合字段是有效的。由于`u`字段被定义为匿名字段，现在可以直接访问TYPEDESC实例之外的成员。`td.lptdesc` and `td.u.lptdesc` are equivalent, but the former is faster since it does not need to create a temporary union instance:

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

  可以定义结构的子子类，它们继承基类的字段。如果子类定义具有单独的`_fields_`变量​​，则在此指定的字段将附加到基类的字段中。

  结构和联合构造函数接受位置和关键字参数。位置参数用于按照出现在`_fields_`中的顺序初始化成员字段。构造函数中的关键字参数被解释为属性赋值，所以它们将初始化具有相同名称的`_fields_`，或为`_fields_`中不存在的名称创建新属性。

### 16.16.2.9. 数组和指针

class `ctypes.``Array`(*args)

  数组的抽象基类。

  推荐的创建具体数组类型的方法是将任何`ctypes`数据类型与正整数相乘。或者，您可以继承此类型并定义`_length_`和`_type_`类变量。数组元素可以使用标准的下标和切片访问来读写；对于片段读取，结果对象本身不是本身就是`Array`。

  `_length_`

    指定数组中元素数量的正整数。超出范围的下标导致`IndexError`。将由`len()`返回。

  `_type_`

    指定数组中每个元素的类型。

  数组子类的构造函数接受位置参数，用于按顺序初始化元素。

class `ctypes.``_Pointer`

  指针的私有抽象基类。

  具体的指针类型通过调用带有指向类型的`POINTER()`来创建；这是通过`pointer()`自动完成的。

  如果指针指向数组，则可以使用标准下标和切片访问来读取和写入其元素。指针对象没有大小，所以`len()`会引发`TypeError`。负下标将从内存读取之前 T0>指针（如C）中，和外的范围的下标将可能与访问冲突崩溃（如果你幸运的话）。

  `_type_`

    指定指向的类型。

  `contents`

    返回要指向点的对象。分配给该属性会将指针更改为指向指定的对象。