python与C语言调用模块 ctypes的详解
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);
return str;
}
float add(float a, float b)
{
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.pyhello ctypes
hello ctypes
2.5
ctypes数据类型和C数据类型对照表
查找动态链接库
>>> 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\'
函数返回类型
函数默认返回 C int 类型,如果需要返回其他类型,需要设置函数的 restype 属性。
>>> from ctypes import *>>> from ctypes.util import find_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.pyfrom ctypes import *
_cb = CFUNCTYPE(None, c_int)
def pr(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_p
for i in range(10):
showNumber(i, cb)
执行:
$ python2.7 callback.pythis is : 0
this is : 1
this is : 2
this is : 3
this is : 4
this is : 5
this is : 6
this is : 7
this is : 8
this is : 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 *class Point(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:", point1
class Rect(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.pypoint1: x=10, y=20
rect1: upperleft:[x=1, y=2], lowerright:[x=3, y=4]
数组
数组定义很简单,比如:定义一个有10个Point元素的数组,TenPointsArrayType = Point * 10
。
初始化和使用数组:
from ctypes import *TenIntegersArrayType = c_int * 10
array1 = TenIntegersArrayType(*range(1, 11))print array1
for i in array1:
print i
运行:
$ python2.7 array.py<__main__.c_int_Array_10 object at 0x7fad0d7394d0>
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 0x7f413081d560>
>>> 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 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\'
>>>
赋值给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 is unchanged
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
#include
char *get(void)
{
char *buf = "Hello World";
char *new_buf = strdup(buf);
printf("allocated address: %p\n", new_buf);
return new_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
:
class c_char_p_sub(c_char_p):pass
lib.get.restype = c_char_p_sub
value
属性会返回字符串。在这个例子中,可以把freeme
的参数改为更通用的c_void_p
,它接受任何指针类型或整型地址。
2:如何把含有‘\0’的char*转换成python字符串
How do you convert a char * with 0-value bytes into a python string?
参考资料
- python ctypes库中动态链接库加载方式
- 用python ctypes调用动态链接库
- ctypes 使用方法与说明
- C语言union(联合体 共用体)
- Python ctypes: how to free memory? Getting invalid pointer error
以上是 python与C语言调用模块 ctypes的详解 的全部内容, 来源链接: utcz.com/z/386495.html