python ctypes 详解_python与C语言调用模块 ctypes的详解

ctypes是python的一个函数库,提供和C语言兼容的数据类型,可以直接调用动态链接库中的导出函数。

为了使用ctypes,必须依次完成以下步骤:

加载动态链接库

将python对象转换成ctypes所能识别的参数

使用ctypes所能识别的参数调用动态链接库中的函数

动态链接库加载方式有三种:

cdll

windll

oledll

它们的不同之处在于:动态链接库中的函数所遵守的函数调用方式(calling convention)以及返回方式有所不同。

cdll用于加载遵循cdecl调用约定的动态链接库,windll用于加载遵循stdcall调用约定的动态链接库,oledll与windll完全相同,只是会默认其载入的函数统一返回一个Windows HRESULT错误编码。

函数调用约定:函数调用约定指的是函数参数入栈的顺序、哪些参数入栈、哪些通过寄存器传值、函数返回时栈帧的回收方式(是由调用者负责清理,还是被调用者清理)、函数名称的修饰方法等等。常见的调用约定有cdecl和stdcall两种。在《程序员的自我修养--链接、装载与库》一书的第10章有对函数调用约定的更详细介绍。

cdecl规定函数参数列表以从右到左的方式入栈,且由函数的调用者负责清除栈帧上的参数。stdcall的参数入栈方式与cdecl一致,但函数返回时是由被调用者自己负责清理栈帧。而且stdcall是Win32 API函数所使用的调用约定。

例子:

Linux下:

或者:

其他例子:

一个完整的例子:

1,编写动态链接库

//filename: foo.c

#include"stdio.h"

char* myprint(char *str)

{

puts(str);returnstr;

}float add(float a, floatb)

{return a +b;

}

将foo.c编译为动态链接库:

gcc -fPIC -shared foo.c -o foo.so

2.使用ctypes调用foo.so

#coding:utf8

#FILENAME:foo.py

from ctypes import *foo= CDLL('./foo.so')

myprint=foo.myprint

myprint.argtypes= [POINTER(c_char)] #参数类型为char指针

myprint.restype = c_char_p #返回类型为char指针

res = myprint('hello ctypes')print(res)

add=foo.add

add.argtypes= [c_float, c_float] #参数类型为两个float

add.restype = c_float #返回类型为float

print(add(1.3, 1.2))

执行:

[jingjiang@iZ255w0dc5eZ test]$ python2.6 foo.py

hello ctypes

hello ctypes

2.5

ctypes数据类型和C数据类型对照表

查找动态链接库

>>> from ctypes.util importfind_library>>> find_library("m")'libm.so.6'

>>> find_library("c")'libc.so.6'

>>> find_library("bz2")'libbz2.so.1.0'

函数返回类型

函数默认返回 C int 类型,如果需要返回其他类型,需要设置函数的 restype 属性。

>>> from ctypes import *

>>> from ctypes.util importfind_library>>> libc = cdll.LoadLibrary(find_library("c"))>>> strchr =libc.strchr>>> strchr("abcdef", ord("d"))-808023673

>>> strchr.restype =c_char_p>>> strchr("abcdef", ord("d"))'def'

>>> strchr("abcdef", ord("x"))

回调函数

定义回调函数类型,类似于c中的函数指针,比如:void (*callback)(void* arg1, void* arg2),定义为:callack = CFUNCTYPE(None, cvoidp, cvoidp)

None表示返回值是void,也可以是其他类型。剩余的两个参数与c中的回调参数一致。

定义python回调函数:

def_callback(arg1, arg2):#do sth

#...

#return sth

注册回调函数:

cb = callback(_callback)

另外,使用ctypes可以避免GIL的问题。

一个例子:

//callback.c

#include"stdio.h"

void showNumber(int n, void (*print)())

{

(*print)(n);

}

编译成动态链接库:

gcc -fPIC -shared -o callback.so callback.c

编写测试代码:

#FILENAME:callback.py

from ctypes import *_cb=CFUNCTYPE(None, c_int)defpr(n):print 'this is : %d' %n

cb=_cb(pr)

callback= CDLL("./callback.so")

showNumber=callback.showNumber

showNumber.argtypes=[c_int, c_void_p]

showNumber.restype=c_void_pfor i in range(10):

showNumber(i, cb)

执行:

$ python2.7callback.py

thisis: 0

thisis : 1thisis : 2thisis : 3thisis : 4thisis : 5thisis : 6thisis : 7thisis : 8thisis : 9

结构体和联合

union(联合体 共用体)

1、union中可以定义多个成员,union的大小由最大的成员的大小决定。

2、union成员共享同一块大小的内存,一次只能使用其中的一个成员。

3、对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,>比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)

4、联合体union的存放顺序是所有成员都从低地址开始存放的。

结构体和联合必须从Structure和Union继承,子类必须定义__fields__属性,__fields__属性必须是一个二元组的列表,包含field的名称和field的类型,field类型必须是一个ctypes的类型,例如:c_int, 或者其他继承自ctypes的类型,例如:结构体,联合,数组,指针。

from ctypes import *

classPoint(Structure):__fields__ = [ ("x", c_int),

("y", c_int),

]def __str__(self):return "x={0.x}, y={0.y}".format(self)

point1= Point(x=10, y=20)print "point1:", point1classRect(Structure):__fields__ =[

("upperleft", Point),

("lowerright", Point),

]def __str__(self):return "upperleft:[{0.upperleft}], lowerright:[{0.lowerright}]".format(self)

rect1= Rect(upperleft=Point(x=1, y=2), lowerright=Point(x=3, y=4))print "rect1:", rect1

运行:

python test.py

point1: x=10, y=20rect1: upperleft:[x=1, y=2], lowerright:[x=3, y=4]

数组

数组定义很简单,比如:定义一个有10个Point元素的数组,

TenPointsArrayType = Point * 10。

初始化和使用数组:

from ctypes import *TenIntegersArrayType= c_int * 10array1= TenIntegersArrayType(*range(1, 11))print array1for i inarray1:

print i

运行:

$ python2.7array.py<__main__.c_int_array_10 object at>

1

2

3

4

5

6

7

8

9

10

指针

pointer()可以创建一个指针,Pointer实例有一个contents属性,返回指针指向的内容。

>>> from ctypes import *

>>> i = c_int(42)>>> p =pointer(i)>>> p<__main__.lp_c_int object at>

>>>p.contents

c_int(42)>>>

可以改变指针指向的内容

>>> i = c_int(99)>>> p.contents =i>>>p.contents

c_int(99)

可以按数组的方式访问,并改变值

>>> p[0]99

>>> p[0] = 22

>>>i

c_int(22)

传递指针或引用

很多情况下,c函数需要传递指针或引用,ctypes也完美支持这一点。

byref()用来传递引用参数,pointer()也可以完成同样的工作,但是pointer会创建一个实际的指针对象,如果你不需要一个指针对象,用byref()会快很多。

>>> from ctypes import *

>>> i =c_int()>>> f =c_float()>>> s = create_string_buffer('\000' * 32) >>>print i.value, f.value, repr(s.value)0 0.0 ''

>>> libc = CDLL("libc.so.6")>>> 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'

可改变内容的字符串

如果需要可改变内容的字符串,需要使用 createstringbuffer()

>>> from ctypes import *

>>> p = create_string_buffer(3) # create a 3 bytebuffer, 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 bytebuffer>>> 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'

>>>

赋值给c_char_p,c_wchar_p,c_void_p

只改变他们指向的内存地址,而不是改变内存的内容

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

Hello, World>>>

数据都可以改变

>>> i = c_int(42)>>>print i

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

>>>print i.value-99

>>>

使用中遇到的一些问题

1:当动态库的导出函数返回char *的时候,如何释放内存

如果把restype设置为c_char_p,ctypes会返回一个常规的Python字符串对象。一种简单的方式就是使用void *和强制转换结果。

string.c:

#include

#include

#includechar *get(void)

{char *buf = "Hello World";char *new_buf =strdup(buf);

printf("allocated address: %p\n", new_buf);returnnew_buf;

}void freeme(char *ptr)

{

printf("freeing address: %p\n", ptr);

free(ptr);

}

Python使用:

from ctypes import *lib= cdll.LoadLibrary('./string.so')

lib.freeme.argtypes=c_void_p,

lib.freeme.restype=None

lib.get.argtypes =[]

lib.get.restype =c_void_p>>> ptr = lib.get()

allocated address:0x9facad8

>>>hex(ptr)'0x9facad8'

>>>cast(ptr, c_char_p).value'Hello World'

>>>lib.freeme(ptr)

freeing address:0x9facad8

也可以使用c_char_p的子类,因为ctypes不会对简单类型的子类调用getfunc:

classc_char_p_sub(c_char_p):

pass

lib.get.restype = c_char_p_sub

value属性会返回字符串。在这个例子中,可以把freeme的参数改为更通用的c_void_p,它接受任何指针类型或整型地址。

参考资料

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值