`
butland
  • 浏览: 15830 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
最近访客 更多访客>>
社区版块
存档分类
最新评论

python中ctypes模块应用中文帮助文档

阅读更多
翻译者:butland,翻译于2010.1.2——2010.1.3,个人博客:http://butlandblog.appspot.com/

内容:
.加载动态链接库
.从已加载的dll中引用函数
.调用函数1
.基本的数据类型
.调用函数2
.用自己的数据类型调用函数
.确认需要的参数类型(函数原型)
.返回值
.传递指针
.结构和联合
.结构或联合的对齐方式和字节的顺序
.结构和联合中的位
.数组
.指针
.类型转换
.未完成的类型
.回调函数
.访问dlls导出的值
.可变长度的数据类型
.bugs 将要做的和没有做的事情

注意:本文中的代码例子使用doctest确保他们能够实际工作。一些代码例子在linux和windows以及苹果机上执行有一定的差别

注意:一些代码引用了ctypes的c_int类型。它是c_long在32位机子上的别名,你不应该变得迷惑,如果你期望
的是c_int类型,实事上打印的是c_long,它们实事上是相同的类型。

加载动态链接库
ctypes加载动态链接库,导出cdll和在windows上同样也导出windll和oledll对象。
加载动态链接库后,你可以像使用对象的属性一样使用它们。cdll加载使用标准的cdecl调用约定的链接库,
而windll库使用stdcall调用约定,oledll也使用stdcall调用约定,同时确保函数返回一个windows HRESULT错误代码。这错误
代码自动的升为WindowsError Python exceptions,当这些函数调用失败时。
这有一些windows例子,msvcrt是微软的c标准库,包含大部分的标准c函数,同时使用cdecl调用约定。

注:cdecl和stdcall的区别请见 http://butlandblog.appspot.com/log-20.html

>>> from ctypes import *
>>> print windll.kernel32 # doctest: +WINDOWS
<WinDLL 'kernel32', handle ... at ...>
>>> print cdll.msvcrt # doctest: +WINDOWS
<CDLL 'msvcrt', handle ... at ...>
>>> libc = cdll.msvcrt # doctest: +WINDOWS
>>>


windows自动添加常用的.dll文件后缀名
在linux上,需要使用包含扩展名的文件名来加载一个库,因此属性操作不能正常使用。 或者使用dll加载器的
LoadLibrary方法,或者通过CDLL构造函数创建一个CDLL的一个实例

>>> cdll.LoadLibrary("libc.so.6") # doctest: +LINUX
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")     # doctest: +LINUX
>>> libc                         # doctest: +LINUX
<CDLL 'libc.so.6', handle ... at ...>
>>>


加载dll使用其中函数
使用函数就像对象的属性

>>> from ctypes import *
>>> libc.printf
<_FuncPtr object at 0x...>
>>> print windll.kernel32.GetModuleHandleA # doctest: +WINDOWS
<_FuncPtr object at 0x...>
>>> print windll.kernel32.MyOwnFunction # doctest: +WINDOWS
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
>>>00

注意 win32系统dll像kernel32和user32大部分导出ANSI和UNICODE版本函数,UNICODE版本以一个W结尾导出
而ANSI版本则以一个A结尾导出的。win32 GetModuleHandle函数,返回一个指定的module名字的module句柄
下面c原型,GetModuleHandle的macro,依赖于它是不是UNICODE。
/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll不会神奇的自已去选择一个,你必须显式确认GetModuleHandleA或者GetModuleHandleW去使用它们
去处理一般字符串或unicode字符串。
有时候,dll导出函数名,python无法识别 ,像"??2@YAPAXI@Z". 在这情况下,你必须使用getattr去使用这些函数

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

在windows上,一些dllS不是导出函数名,而是以顺序,这些函数可以将这些数字当作dll对象的索引来使用
这些函数。

>>> cdll.kernel32[1] # doctest: +WINDOWS
<_FuncPtr object at 0x...>
>>> cdll.kernel32[0] # doctest: +WINDOWS
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
>>>

调用函数
你可调用这些函数,像其它的python函数一样,下面的例子使用time()函数,它以秒为单位返回从unix新纪元的系统时间
,GetModuleHandleA()函数,返回一个win32模块句柄。

下面的例子用空指针调用函数(None作为空指针)

>>> print libc.time(None) # doctest: +SKIP
1150640792
>>> print hex(windll.kernel32.GetModuleHandleA(None)) # doctest: +WINDOWS
0x1d000000
>>>

ctypes尝试阻止你以错误的参数数量或调用约定来调用函数。不幸的是,这些仅仅能在windows上工作
它在函数返回后不会去检查这栈,尽管在调用函数后有错误发生。

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


产生了同样的exception,当你用cdecl调用约定去使用一个stdcall函数,反之亦然。

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

>>> windll.msvcrt.printf("spam") # doctest: +WINDOWS
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结构exception处理一般的保护错误阻止crashes,当调用无效的参数值

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

然而有很多种可能性会发生crash,你使用python的ctypes,你应该足够的小心。

None,integers,longs,byte strings 和unicode string是python内置对象,可以直接的作为这些函数的参数
,None作为一个空指针,byte string和unicode string 当作一个内存块,它包含这些数据(char* 或者 wchar_t*)
。python的int和long长整形作为c int类型,它们的值对应着c类型。

在调用带有参数的函数之前,我们必须学习更多的关于ctypes数据类型。

基础的数据类型

  ctypes定义了许多主要的c兼容有数据类型

数据类型请见http://butlandblog.appspot.com/log-21.html


使用它们和一个可选的正确的值去创建所有的这些类型
>>> c_int()
c_long(0)
>>> c_char_p("Hello, World")
c_char_p('Hello, World')
>>> c_ushort(-3)
c_ushort(65533)
>>>

因此这些类型是可变的,他们的值在后面也是可以改变的

分配一个新值到这些指针类型c_char_p, c_wchar_p, 和 c_void_p的一个实例上,改变它们指向的
内存位置,而不是这些内存块的内容(当然,因为python的string是不可改变的)

>>> s = "Hello, World"
>>> c_s = c_char_p(s)
>>> print c_s
c_char_p('Hello, World')
>>> c_s.value = "Hi, there"
>>> print c_s
c_char_p('Hi, there')
>>> print s                 # first string is unchanged
Hello, World
>>>


你应该注意,不要期望传给函数指针是指向可变的内存。如果你需要可变的内存块,ctypes有一个
create_string_buffer 函数。这内存块内容通过这raw属性来访问或改变,如果你想访问一个以null结束
的字符串,使用这string属性

>>> from ctypes import *
>>> p = create_string_buffer(3)      # create a 3 byte buffer, initialized to NUL bytes
>>> print sizeof(p), repr(p.raw)
3 '\x00\x00\x00'
>>> p = create_string_buffer("Hello")      # create a buffer containing a NUL terminated string
>>> print sizeof(p), repr(p.raw)
6 'Hello\x00'
>>> print repr(p.value)
'Hello'
>>> p = create_string_buffer("Hello", 10)  # create a 10 byte buffer
>>> print sizeof(p), repr(p.raw)
10 'Hello\x00\x00\x00\x00\x00'
>>> p.value = "Hi"
>>> print sizeof(p), repr(p.raw)
10 'Hi\x00lo\x00\x00\x00\x00\x00'
>>>


create_string_buffer代替c_buffer(这个到目前是以别名存在的)函数,就像早期版本ctypes的c_string
函数。create_unicode_buffe函数创建一个包含c类型wchar_t的unicode字符的可变的内存块

调用函数2
注意这printf打印到实际的标准的输出管道,而不是sys.stdout,所以这些例子仅仅工作在console下,而不能在
idle或pythonwin下工作。

>>> printf = libc.printf
>>> printf("Hello, %s\n", "World!")
Hello, World!
14
>>> printf("Hello, %S", u"World!")
Hello, World!
13
>>> printf("%d bottles of beer\n", 42)
42 bottles of beer
19
>>> printf("%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类型除了integer,string,unicode string以外必须以一个合适的ctypes类型包装,
这样它们才能转换成需要的c数据类型。

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

使用你自定义的数据类型调用函数
你可以自定义ctypes参数,来让你自己的classes的实例作为函数的参数。ctypes寻找_as_parameter_ 属性
作为函数的参数。当然,它必须是整形,字符串,或unicode

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

如果你不想保存实例的数据在_as_parameter_ 实例的变量中,你应该定义一个属性使这个数据有效。

定义需要的参数类型(函数原型)
可以通过设置argtypes属性,定义从dll中导出的需要的参数类型。argtypes必须是c数据类型的一个列表
(这里的printf可能不是很好的例子,因为它是一个取决于format字符串的可变数目的,需要不同类型的参数
,使用这种特性是一种不错的特性)

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

定义一个format阻止了不兼容的参数类型(仅仅是一个c函数的原型),尝试转换这些参数到合适的类型。

>>> printf("%d %d %d", 1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ArgumentError: argument 2: exceptions.TypeError: wrong type
>>> printf("%s %d %f", "X", 2, 3)
X 2 3.00000012
12
>>>

如果你定义了你自己的类去传给调用函数,你必须使用from_param类方法,这样就可以在argtypes列表中使用它们。
from_param类方法接收传给函数的Python对象,它做一个类型检测,或者确保这个对象是可接受的就返回这个对象本身
,它的_as_parameter_属性,或者你想作为参数传递给c函数的任何东西。这结果必须是一个整数,字符串,unicode,一个
ctypes的实例,或者任何有_as_parameter_属性的对象。

返回值
主要的函数都返回c int类型,其它的返回类型可以通过设置function对象的restype属性
这有一个更好的例子,它使用strchr函数,它期望一个string指针和一个char,同时返回一个指向string的
指针。
>>> strchr = libc.strchr
>>> strchr("abcdef", ord("d")) # doctest: +SKIP
8059983
>>> strchr.restype = c_char_p # c_char_p is a pointer to a string
>>> strchr("abcdef", ord("d"))
'def'
>>> print strchr("abcdef", ord("x"))
None
>>>

如果你想避免上面的ord("x")调用,你可以设置argtypes属性,这第二个参数会从一个python字符到一个c char字符

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

你可以使用一个可以调用的python对象(一个函数或者一个class)作为这restype属性的值,如果外部函数
返回的是一个整数,这个可调用对象将调用这c函数返回的整数,这个可调用对象的结果将作为这个函数调用的结果,这个对于检查
返回值,同时产生一个错误很有帮助。

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

WinError是一个函数,它调用Windows的FormatMessage() API函数去获取一个错误码的描述,同时返回一个exception
。WinError采取一个可选择的错误代码参数,如果没有,它调用GetLastError()。

请注意大量的错误检测机制是可行的,通过errcheck属性,更多的细节请参考手册。

传递指针和传递引用

有时候,一个c API函数期望一个指向某个数据类型的指针作为参数,可能要写入一些数据到相应的位置,如果这数据
太大而不能传值。这同样适用于通过引用传递参数。

ctypes导出这byref函数,它用作通过引用传递参数。使用指针可以达到相同的效果,尽管指针可以做更多的事情
,因为它初始化了一个指针对象,它比使用 byref要快,但在python中你不需要指针对象

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

结构和联合
Structures和unions必须继承Structure和Union基础类,它们都在ctypes模块中定义,每一个子类必须定义一个
_fields_属性,_fields_是一个2维tuples的列表,包含这field的name和field的type

这field类型必须是一个ctypes类型,像c_int,或者任何其它的继承ctypes的类型,structure,union,array,指针。

这里有一个简单的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
>>>


你可以创建很多更复杂的结构。结构可以通过包含一个结构类型的field来包含其它的结构。
下面是一外RECT结构,包含两个POINT名为upperleft和lowerright。

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

Field描述可以从类中找到,这在调试中是很有用的,它可以提供非常有用的信息。

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

Structure/union对齐和byte顺序
默认Structure/union的field对齐和c编译器的方式相同。通过在子类中定义一个_pack_ class
属性可以覆盖这个行为。这必须设置一个正数和为fields对齐设置一个最大值。这就是 #pragma pack(n)在
MSVC中的作用。

ctypes使用结构和联合原始的byte顺序。使用一个非原始的byte顺序创建结构,你可以使用BigEndianStructure,
LittleEndianStructure,BigEndianUnion,LittleEndianUnion中的一个作为基础类。这些类不包含pointer的field。


Structure/union的byte field

它是可以创建包含byte field的structures 和 unions byte field仅仅可以适合整型field,这bit的width通过
这 _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>
>>>


Array
Array是对列,包含一个固定数目的相同类型的实例。
被推荐的方法去创建array类型是对过一个数据类型与一个正数相乘。

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

使用一般的方法创建实例,通过调用class

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


这上面打印了一系列的0,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,
...
1 2 3 4 5 6 7 8 9 10
>>>


指针
通过调用ctypes类型的指针函数来创建指针实例
>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)
>>>

指针实例有一个contents属性,返回这个指针指向的对象,上面的i对象。
>>> pi.contents
c_long(42)
>>>

注意 这ctypes不包含面向对象,它每次查看一个属性时就创建一个全新的等价的对象。
>>> pi.contents is i
False
>>> pi.contents is pi.contents
False
>>>

分配另外一个c_int的实例到pointer的内容属性,将导致指针指向存储它本身的内存位置。

>>> 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里面,你可以访访问或武断改变内存位置
,一般而言,当你从一个c函数接收一个指针,同时你知道这个指针实例指向的是一个array而不是单个项。

在这种场景下,这指针函数比简单的创建一个指针实例要困难多,它首先必须通过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检查NULL非引用指针(非引用非关联指针会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
>>>

类型转换
一般而言,ctypes进行严格的类型检查,如果在函数参数列表中有一个POINT(c_int)类型或者
在作为结构定义中一个成员field,仅仅接收这相同类型的实例。有一些exception应用在这些规则上
当ctypes接收其它类型的对象。例如你传递一个兼容的array实例,代替指针类型。这样对于POINTER(c_int)
,ctypes可以接收c_int型array

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

设置一个指针类型为空,你可以分配None:
>>> bar.values = None
>>>


有时候,你有一些不兼容类型的实例。在C中,你可以强制转换一个类型到另一个类型。ctypes提供了一个有相同作用的转换
函数。这Bar structure定义了一个POINTER(c_int)指针,或者c_int的array的field,但是没有其它类型。

>>> 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的值域
>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print bar.values[0]
0
>>>


未完成的类型

完成的类型包括成员还没有定义的structure或union,array.在c,他们是在前面声明 ,而在后面定义的:
struct cell; /* forward declaration */

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


直接转换为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类,同时在类声明中设置_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,
...     p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>

回调函数
ctypes可以从python可调用对象中创建一个c可调用的函数指针。这些通常被称为回调函数。

首先,你需要为回调函数创建一个类,这类知道这调用约定,返回类型,以及这函数需要接收的参数个数。
CFUNCTYPE工厂函数使用正常的cdecl调用约定创建回调函数的类型,同时在windows上,WINFUNCTYPE工厂函数
使用stdcall调用给定为回调函数创建类型。

这两个工厂函数的结果类型作为第一个参数,这些期望的参数类型回调函数作为这剩下的参数。

这而使用标准的c库qsort函数,它使用一个回调函数来帮助排序items。qsort将使用来排序一个整数array.
>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
>>> qsort = libc.qsort
>>> qsort.restype = None
>>>

必须接收一个指向数据的指针来调用qsort函数排序,同时一个比较函数的指针,也就是回调函数。这回调函数
接收两个items的指针,如果第一个小于第二个它返回一个负数,如果相等,返回0,其它的就返回一个正数。

我们的回调函数接收整型指针,同时必须返回一个整数。首先我们创建这回调函数的类型。
>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>>

第一次使用这回调函数,我们简单的打印我们获得的参数,然后返回一个0.

>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a, b
...     return 0
...
>>>

创建c可调用的回调函数
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

现在准备的差不多了
>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +WINDOWS
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
>>>

我们知道如何访问指针的内容,我们重新定义回调函数:
>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a[0], b[0]
...     return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

这是我们在windows上得到的结果:
>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +WINDOWS
py_cmp_func 7 1
py_cmp_func 33 1
py_cmp_func 99 1
py_cmp_func 5 1
py_cmp_func 7 5
py_cmp_func 33 5
py_cmp_func 99 5
py_cmp_func 7 99
py_cmp_func 33 99
py_cmp_func 7 33
>>>

在linux上这sort函数看起来工作的更有效,它进行了更少的对比,这很有趣。


>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +LINUX
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
>>>

我们做的差不多了,这最后一步是实际的比较这两个items,并返回一个有用的结果。

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

在windows上运行的最终结果

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +WINDOWS
py_cmp_func 33 7
py_cmp_func 99 33
py_cmp_func 5 99
py_cmp_func 1 99
py_cmp_func 33 7
py_cmp_func 1 33
py_cmp_func 5 33
py_cmp_func 5 7
py_cmp_func 1 7
py_cmp_func 5 1
>>>

Linux:
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +LINUX
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
>>>

在window上qsort函数需要比linux上进行更多的比较。

我们进行简单检查,我们的已排序的array:
>>> for i in ia: print i,
...
1 5 7 33 99
>>>

确保你对CFUNCTYPE对象的引用和使用c代码在同一空间下,ctypes不会处理,同时如果你不去处理,这将被回收,
当它调用时python会崩溃。

访问dll的导出值
有时候一个dll不仅仅导出函数,它也导出变量。一个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), 如果是-OO 将会打印 c_long(2)
一个扩展的例子,示范使用指针访问python导出的PyImport_FrozenModules指针。
引用python的docs:这指针初始化来指向一个struct _frozen记录array,终止于成员都是NULL或0.第三方代码
将提供一个动态创建一个frozen模块的集合。

操纵这些指针更加有效。限制这个例子折大小,仅仅展示如何使用ctypes来读取table.

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

已经定义了struct _frozen数据类型,这样我们可以得到这table的指针。

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

因为table是一个指向struct_frozen记录array的指针,我们可以迭代,但是我们必须确保我们的loop正确的终止,
因为指针没有大小。过早或过晚,它因为访问违例或其它,会产生crash。所以最好是是在它遇到一个NULL入口就结束

>>> for item in table:
...    print item.name, item.size
...    if item.name is None:
...        break
...
__hello__ 104
__phello__ -104
__phello__.spam 104
None 0
>>>

这事实是,标准的python有一个frozen模块,和一个frozen包,它们都不为人所知,它仅仅用于测试。导入__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对象内部的buffer。执行rc.a = temp0,拷贝这temp0的buffer内容到rc的
buffer.按顺序改变这temp1的内容。因此,这最后一个赋值rc.b = temp1,没有期望的影响。



另个一个例子:
>>> s = c_char_p()
>>> s.value = "abc def ghi"
>>> s.value
'abc def ghi'
>>> s.value is s.value
False
>>>

为什么打印False呢,ctypes实例包含一个内存块加上一个描述访问这内存内容的描述。在内存块中保存python对象
并不保存对象本身,而不是保存对象的内容。每次访问这些内容,将产生一个新的python。

可变的数据类型
ctypes提供一些关于可变大小array的支持和结构。
resize函数可以重定义一个已经存在的ctypes对象内存buffer的大小,函数将对象作为第一个参数,这要求的大小作为第二个参数。这内存块不能小于这原来内存块,这将产生一个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
>>>

相当不错,但是如何访问array中额外的元素呢?因为这类型至今只知道4个元素,当访问其它元素时会产生错误 。

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


另外一种使用ctypes的变长数据类型是使用python的动态特性,在知道需要的数据长度后,按照具体情况重新定义数据类型。

bugs,将要做的和未做的事情


Enumeration类型没有做,你可轻松的来完成,使用c_int作为这基础类型

long double没有做

翻译者:butland,翻译于2010.1.2——2010.1.3,个人博客:http://butlandblog.appspot.com/
4
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics