ctype-Python的外部函数库(一)(摘抄Python官方文档)

      ctype是Python的外部函数库。它提供了C兼容的数据类型,并允许在DLL或共享库中调用函数。ctype是Python封装的API函数库。(刚开始以为是针对windows系统函数库,后来发现这是一种c语言调用解决方案。可以调用所有c语言实现的函数库。)

    ctype教程

    注意:本教程中的代码示例使用doctest(Python的测试模块方法)来确保它们实际工作。由于某些代码示例在Linux、Windows或Mac OS X下表现不同,它们在注释中包含doctest(Python的测试模块方法)指令。

   注意:一些代码示例引用ctype c_int类型。在size of(Long)=size of(Int)的平台上,它是c_long的别名。因此,如果您希望c_int是打印的,那么您不应该对c_long感到困惑-它们实际上是相同的类型。

     加载动态链接库

    ctypes 导出cdll库,并在 Windows windll 和 oledll 对象上加载动态链接库。

    过访问ctypes对象的属性加载库。cdll 加载使用 cdecl 标准调用约定导出函数的库,而 windll  库使用 stdcall 调用约定调用函数。oledll 还使用 stdcall 调用约定,并假设函数返回一个Windows HRESULT  错误代码。错误代码用于在函数调用失败时自动引发OSError异常。

     在版本3.3中更改:用于引发WindowsError的Windows错误,该错误现在是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 ...>
>>>

    

    从加载的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 <module>
  File "ctypes.py", line 239, in __getattr__
    func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
>>>

    请注意,win32系统dll如kernel32和user32经常导出ANSI以及函数的UNICODE版本。 将导出UNICODE版本,并在名称后附加W,而ANSI版本将导出,并在名称后附加A. win32 GetModuleHandle函数返回给定模块名称的模块句柄,它具有以下C原型,并且宏用于将其中一个公开为GetModuleHandle,具体取决于是否定义了UNICODE:

    

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

  windll不会尝试通过魔术选择其中一个,您必须通过显式指定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 <module>
  File "ctypes.py", line 310, in __getitem__
    func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>

   调用函数

您可以像调用任何其他Python一样调用这些函数。 此示例使用time()函数,该函数返回自Unix纪元以来的系统时间(以秒为单位),以及返回win32模块句柄的GetModuleHandleA()函数。

  此示例使用NULL指针调用这两个函数(None应该用作NULL指针):

  

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

注意如果ctypes检测到传递了无效数量的参数,则在调用该函数后可能会引发ValueError。 不应该依赖这种行为。 它在3.6.2中已弃用,将在3.7中删除。

 使用cdecl调用约定调用stdcall函数时会引发ValueError,反之亦然:

>>> cdll.kernel32.GetModuleHandleA(None)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>>

>>> windll.msvcrt.printf(b"spam")  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
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 <module>
OSError: exception: access violation reading 0x00000020
>>>

但是,有足够的方法可以使用ctypes来破坏Python,所以无论如何你应该小心。 faulthandler模块可以帮助调试崩溃(例如,由错误的C库调用产生的分段错误)。

None, integers, bytes objects 和(unicode)字符串是在这些函数调用中可以直接用作参数的唯一原生Python对象。 None作为C NULL指针传递,bytes objects 和 strings 作为指针传递给包含其数据的内存块(char *或wchar_t *)。 Python整数作为平台默认的C int类型传递,它们的值被屏蔽以适合C类型。

在我们继续使用其他参数类型调用函数之前,我们必须了解有关ctypes数据类型的更多信息。

基本数据类型

ctypes定义了许多原始的C兼容数据类型:

ctypes typeC typePython type
c_bool_Boolbool (1)
c_charchar1-character bytes object
c_wcharwchar_t1-character string
c_bytecharint
c_ubyteunsigned charint
c_shortshortint
c_ushortunsigned shortint
c_intintint
c_uintunsigned intint
c_longlongint
c_ulongunsigned longint
c_longlong__int64 or long longint
c_ulonglongunsigned __int64 or unsigned long longint
c_size_tsize_tint
c_ssize_tssize_t or Py_ssize_tint
c_floatfloatfloat
c_doubledoublefloat
c_longdoublelong doublefloat
c_char_pchar * (NUL terminated)bytes object or None
c_wchar_pwchar_t * (NUL terminated)string or None
c_void_pvoid *int or None

构造函数接受具有真值的任何对象。
所有这些类型都可以通过使用正确类型和值的可选初始化程序调用它们来创建:

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

由于这些类型是可变的,因此它们的值也可以在以后更改:

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

为指针类型c_char_p,c_wchar_p和c_void_p的实例分配新值会更改它们指向的内存位置,而不是内存块的内容(当然不会,因为Python字节对象是不可变的):

>>> 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()函数(仍然可用作别名),以及早期ctypes版本中的c_string()函数。 要创建包含C类型wchar_t的unicode字符的可变内存块,请使用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>
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
>>>

使用您自己的自定义数据类型调用函数

您还可以自定义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_实例变量中,则可以定义一个属性,该属性可根据请求使该属性可用。

指定必需的参数类型(函数原型)

可以通过设置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 <module>
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对象,它应该执行类型检查或确保此对象可接受的任何操作,然后返回对象本身,其_as_parameter_属性或任何您想要传递的对象 在这种情况下作为C函数参数。 同样,结果应该是整数,字符串,字节,ctypes实例或具有_as_parameter_属性的对象。

返回类型

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

这是一个更高级的示例,它使用strchr函数,它需要一个字符串指针和一个char,并返回一个指向字符串的指针:

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

>>> 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 <module>
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函数返回的整数调用callable,并且此调用的结果将用作函数调用的结果。 这对于检查错误返回值并自动引发异常非常有用:

>>> 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是一个函数,它将调用Windows FormatMessage()api来获取错误代码的字符串表示,并返回异常。 WinError接受一个可选的错误代码参数,如果没有使用,它会调用GetLastError()来检索它。

请注意,errcheck属性提供了更强大的错误检查机制; 有关详细信息,请参阅参考手册

传递指针(或:通过引用传递参数)

有时,C api函数需要将指向数据类型的指针作为参数,可能要写入相应的位置,或者数据太大而无法通过值传递。 这也称为通过引用传递参数。

ctypes导出byref()函数,该函数用于通过引用传递参数。 使用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'
>>>

结构与联合(Structures and unions)

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

字段类型必须是ctypes类型,如c_int,或任何其他派生的ctypes类型:structure,union,array,pointer。

下面是一个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>
ValueError: too many initializers
>>>

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

这是一个RECT结构,它包含两个名为upperleft和lowerright的POINT:

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

可以从类中检索字段描述符,它们对调试很有用,因为它们可以提供有用的信息:

>>> 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,但是不能保证库在一般情况下工作。 具有位字段的联合和结构应始终通过指针传递给函数。

结构/联合对齐和字节顺序(Structure/union alignment and byte order)

默认情况下,Structure和Union字段的对齐方式与C编译器的方式相同。 可以通过在子类定义中指定_pack_ class属性来覆盖此行为。 必须将其设置为正整数,并指定字段的最大对齐方式。 这就是#pragma pack(n)在MSVC中的作用。

ctypes使用结构和联合的本机字节顺序。 要构建具有非本机字节顺序的结构,可以使用BigEndianStructure,LittleEndianStructure,BigEndianUnion和LittleEndianUnion基类之一。 这些类不能包含指针字段。

结构和联合中的位字段

可以创建包含位字段的结构和联合。 位字段仅适用于整数字段,位宽指定为_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>
>>>

数组》》》》》待续》》》》》。。。。。。。。。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

开软古剑楠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值