Ctypes¶
日期 | 2011-11-14(上次修改),2006-05-05(创建) |
---|
简介¶
ctypes 是 Python 2.3 及更高版本的高级外部函数接口包。它包含在 Python 2.5 的标准库中。
ctypes 允许调用从 DLL/共享库中公开的函数,并具有广泛的功能来在 Python 中创建、访问和操作简单和复杂的 C 数据类型——换句话说:用纯 Python 包装库。甚至可以在纯 Python 中实现 C 回调函数。
ctypes 还包含一个代码生成工具链,允许从 C 头文件自动创建库包装器。ctypes 在 Windows、Mac OS X、Linux、Solaris、FreeBSD、OpenBSD 和其他系统上运行。
确保您至少拥有 ctypes 1.0.1 或更高版本。
在 python 中调用或运行 C 代码的其他可能性包括:SWIG、Cython、Weave、cffi 等。
Ctypes 入门¶
有关 ctypes 入门的详细信息,请参阅 ctypes 教程 和 Python 的 ctypes 文档。
假设您构建了一个名为 `foo.dll` 或 `libfoo.so` 的库,其中包含一个名为 `bar` 的函数,该函数接受指向双精度缓冲区的指针和一个 int 作为参数,并返回一个 int,以下代码应该可以帮助您入门。以下部分介绍了一些可能的构建脚本、C 代码和 Python 代码。
如果您想使用 distutils 构建您的 DLL/共享库,请查看 OOF2 中包含的 !SharedLibrary distutils 扩展。这可能应该在某个时候包含在 numpy.distutils 中。
Nmake Makefile (Windows)¶
在 Visual Studio 命令提示符中运行 nmake,使用以下文件进行构建。
无论用于编译 Python 的编译器版本如何,您都应该能够使用任何版本的 Visual Studio 编译器构建 DLL。请记住,您不应该跨不同的调试/发布和单线程/多线程运行时分配/释放内存,也不应该对来自不同运行时的 FILE* 进行操作。
CXX = cl.exe
LINK = link.exe
CPPFLAGS = -D_WIN32 -D_USRDLL -DFOO_DLL -DFOO_EXPORTS
CXXFLAGSALL = -nologo -EHsc -GS -W3 -Wp64 $(CPPFLAGS)
CXXFLAGSDBG = -MDd -Od -Z7 -RTCcsu
CXXFLAGSOPT = -MD -O2
#CXXFLAGS = $(CXXFLAGSALL) $(CXXFLAGSDBG)
CXXFLAGS = $(CXXFLAGSALL) $(CXXFLAGSOPT)
LINKFLAGSALL = /nologo /DLL
LINKFLAGSDBG = /DEBUG
LINKFLAGSOPT =
#LINKFLAGS = $(LINKFLAGSALL) $(LINKFLAGSDBG)
LINKFLAGS = $(LINKFLAGSALL) $(LINKFLAGSOPT)
all: foo.dll
foo.dll: foo.obj
$(LINK) $(LINKFLAGS) foo.obj /OUT:foo.dll
svm.obj: svm.cpp svm.h
$(CXX) $(CXXFLAGS) -c foo.cpp
clean:
-erase /Q *.obj *.dll *.exp *.lib
env = Environment()
env.Replace(CFLAGS=['-O2','-Wall','-ansi','-pedantic'])
env.SharedLibrary('foo', ['foo.cpp'])
foo.cpp¶
#include <stdio.h>
#ifdef FOO_DLL
#ifdef FOO_EXPORTS
#define FOO_API __declspec(dllexport)
#else
#define FOO_API __declspec(dllimport)
#endif /* FOO_EXPORTS */
#else
#define FOO_API extern /* XXX confirm this */
#endif /* FOO_DLL */
#ifdef __cplusplus
extern "C" {
#endif
extern FOO_API int bar(double* data, int len) {
int i;
printf("data = %p\n", (void*) data);
for (i = 0; i < len; i++) {
printf("data[%d] = %f\n", i, data[i]);
}
printf("len = %d\n", len);
return len + 1;
}
#ifdef __cplusplus
}
#endif
在 Windows 上构建 foo 的 DLL 时,定义 `FOO_DLL` 和 `FOO_EXPORTS`(这是您在构建用于 ctypes 的 DLL 时想要做的)。在链接到 DLL 时,定义 `FOO_DLL`。在链接到包含 foo 的静态库时,或者在将 foo 包含在可执行文件中时,不要定义任何内容。
如果您不清楚这是什么,请阅读 C++ dlopen 迷你 HOWTO 的第 3 节。这允许您在许多 C++ 类之上编写具有 C 链接的函数包装器,以便您可以将它们与 ctypes 一起使用。或者,您可能更喜欢编写 C 代码。
foo.py¶
import numpy as N
import ctypes as C
_foo = N.ctypeslib.load_library('libfoo', '.')
_foo.bar.restype = C.c_int
_foo.bar.argtypes = [C.POINTER(C.c_double), C.c_int]
def bar(x):
return _foo.bar(x.ctypes.data_as(C.POINTER(C.c_double)), len(x))
x = N.random.randn(10)
n = bar(x)
NumPy 数组的 ctypes 属性¶
最近在 NumPy 数组中添加了一个 ctypes 属性
In [18]: x = N.random.randn(2,3,4)
In [19]: x.ctypes.data
Out[19]: c_void_p(14394256)
In [21]: x.ctypes.data_as(ctypes.POINTER(c_double))
In [24]: x.ctypes.shape
Out[24]: <ctypes._endian.c_long_Array_3 object at 0x00DEF2B0>
In [25]: x.ctypes.shape[:3]
Out[25]: [2, 3, 4]
In [26]: x.ctypes.strides
Out[26]: <ctypes._endian.c_long_Array_3 object at 0x00DEF300>
In [27]: x.ctypes.strides[:3]
Out[27]: [96, 32, 8]
通常,C 函数可能会接受指向数组数据的指针,一个表示数组维数的整数(在此处传递 ndim 属性的值),以及两个指向形状和步长信息的 int 指针。
如果您的 C 函数假设连续存储,您可能希望使用一个 Python 函数对其进行包装,该函数对所有输入数组调用 !NumPy 的 `ascontiguousarray` 函数。
NumPy 的 ndpointer 与 ctypes argtypes¶
从 ctypes 0.9.9.9 开始,任何实现 from_param 方法的类都可以在函数的 argtypes 列表中使用。在 ctypes 调用 C 函数之前,它使用 argtypes 列表来检查每个参数。
使用 !NumPy 的 ndpointer 函数,可以构建一些非常有用的 argtypes 类,例如
from numpy.ctypeslib import ndpointer
arg1 = ndpointer(dtype='<f4')
arg2 = ndpointer(ndim=2)
arg3 = ndpointer(shape=(10,10))
arg4 = ndpointer(flags='CONTIGUOUS,ALIGNED')
# or any combination of the above
arg5 = ndpointer(dtype='>i4', flags='CONTIGUOUS')
func.argtypes = [arg1,arg2,arg3,arg4,arg5]
现在,如果参数不满足要求,就会引发 !TypeError。这允许确保传递给 C 函数的数组是函数可以处理的形式。
另请参阅邮件列表线程 ctypes 和 ndpointer。
通过回调进行动态分配¶
ctypes 支持 回调 的概念,允许 C 代码通过函数指针回调到 Python。这是可能的,因为 ctypes 在调用 C 函数之前会释放 Python 全局解释器锁 (GIL)。
我们可以使用此功能在需要缓冲区供 C 代码操作时分配 !NumPy 数组。这可以避免在某些情况下复制数据。您也不必担心在完成操作后释放 C 数据。通过将缓冲区分配为 !NumPy 数组,Python 垃圾收集器可以处理此问题。
Python 代码
from ctypes import *
ALLOCATOR = CFUNCTYPE(c_long, c_int, POINTER(c_int))
# load your library as lib
lib.baz.restype = None
lib.baz.argtypes = [c_float, c_int, ALLOCATOR]
这不是定义分配器最漂亮的方式(我也不确定 c_long 是否是正确的返回类型),但 ctypes 中有一些错误似乎使这成为目前唯一的方法。最终,我们希望像这样编写分配器(但它目前还不能工作)
from numpy.ctypeslib import ndpointer
ALLOCATOR = CFUNCTYPE(ndpointer('f4'), c_int, POINTER(c_int))
以下内容似乎也会导致问题
ALLOCATOR = CFUNCTYPE(POINTER(c_float), c_int, POINTER(c_int))
ALLOCATOR = CFUNCTYPE(c_void_p, c_int, POINTER(c_int))
ALLOCATOR = CFUNCTYPE(None, c_int, POINTER(c_int), POINTER(c_void_p))
可能的故障包括引发 !SystemError 异常、解释器崩溃或解释器挂起。查看这些邮件列表线程以了解更多详细信息:* 在回调中分配时指针到指针未更改 * 回调返回 POINTER(c_float) 时挂起 * 回调函数和 as_parameter 与 NumPy ndpointer 结合使用时的错误
现在举个例子。示例的 C 代码
#ifndef CSPKREC_H
#define CSPKREC_H
#ifdef FOO_DLL
#ifdef FOO_EXPORTS
#define FOO_API __declspec(dllexport)
#else
#define FOO_API __declspec(dllimport)
#endif
#else
#define FOO_API
#endif
#endif
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void*(*allocator_t)(int, int*);
extern FOO_API void foo(allocator_t allocator) {
int dim = 2;
int shape[] = {2, 3};
float* data = NULL;
int i, j;
printf("foo calling allocator\n");
data = (float*) allocator(dim, shape);
printf("allocator returned in foo\n");
printf("data = 0x%p\n", data);
for (i = 0; i < shape[0]; i++) {
for (j = 0; j < shape[1]; j++) {
*data++ = (i + 1) * (j + 1);
}
}
}
#ifdef __cplusplus
}
#endif
如果您不熟悉 C 或 C++ 中的函数指针,请查看 函数指针教程。以及 Python 代码
from ctypes import *
import numpy as N
allocated_arrays = []
def allocate(dim, shape):
print 'allocate called'
x = N.zeros(shape[:dim], 'f4')
allocated_arrays.append(x)
ptr = x.ctypes.data_as(c_void_p).value
print hex(ptr)
print 'allocate returning'
return ptr
lib = cdll['callback.dll']
lib.foo.restype = None
ALLOCATOR = CFUNCTYPE(c_long, c_int, POINTER(c_int))
lib.foo.argtypes = [ALLOCATOR]
print 'calling foo'
lib.foo(ALLOCATOR(allocate))
print 'foo returned'
print allocated_arrays[0]
allocate 函数创建一个新的 !NumPy 数组并将其放入列表中,以便我们在回调函数返回后保留对它的引用。预期输出
calling foo
foo calling allocator
allocate called
0xaf5778
allocate returning
allocator returned in foo
data = 0x00AF5778
foo returned
[[ 1. 2. 3.]
[ 2. 4. 6.]]
这是另一个用于管理此类事物的 Allocator 类的想法。除了维度和形状之外,此分配器函数还接受一个字符,指示要分配的数组类型。您可以从 ndarrayobject.h 头文件中的 `NPY_TYPECHAR` 枚举中获取这些类型代码。
from ctypes import *
import numpy as N
class Allocator:
CFUNCTYPE = CFUNCTYPE(c_long, c_int, POINTER(c_int), c_char)
def __init__(self):
self.allocated_arrays = []
def __call__(self, dims, shape, dtype):
x = N.empty(shape[:dims], N.dtype(dtype))
self.allocated_arrays.append(x)
return x.ctypes.data_as(c_void_p).value
def getcfunc(self):
return self.CFUNCTYPE(self)
cfunc = property(getcfunc)
在 Python 中像这样使用它
lib.func.argtypes = [..., Allocator.CFUNCTYPE]
def func():
alloc = Allocator()
lib.func(..., alloc.cfunc)
return tuple(alloc.allocated_arrays[:3])
相应的 C 代码
typedef void*(*allocator_t)(int, int*, char);
void func(..., allocator_t allocator) {
/* ... */
int dims[] = {2, 3, 4};
double* data = (double*) allocator(3, dims, 'd');
/* allocate more arrays here */
}
上面介绍的所有分配器都不是线程安全的。如果您有多个 Python 线程调用调用回调的 C 代码,您将不得不采取更智能的方法。
更多有用的代码片段¶
假设您有一个像下面这样的 C 函数,它操作指针到指针的数据结构。
void foo(float** data, int len) {
float** x = data;
for (int i = 0; i < len; i++, x++) {
/* do something with *x */
}
}
您可以使用以下代码从现有的二维 !NumPy 数组创建必要的结构
x = N.array([[10,20,30], [40,50,60], [80,90,100]], 'f4')
f4ptr = POINTER(c_float)
data = (f4ptr*len(x))(*[row.ctypes.data_as(f4ptr) for row in x])
`f4ptr*len(x)` 创建一个 ctypes 数组类型,它的大小足以包含指向数组每一行的指针。
异构类型示例¶
以下是在使用异构 dtype(记录数组)时的简单示例。
但是,请注意,NumPy recarrays 和 C 中的相应结构 **可能不** 相容。
此外,结构在不同平台上没有标准化...换句话说,**“注意填充问题!”**
sample.c
#include <stdio.h>
typedef struct Weather_t {
int timestamp;
char desc[12];
} Weather;
void print_weather(Weather* w, int nelems)
{
int i;
for (i=0;i<nelems;++i) {
printf("timestamp: %d\ndescription: %s\n\n", w[i].timestamp, w[i].desc);
}
}
SConstruct
env = Environment()
env.Replace(CFLAGS=['-O2','-Wall','-ansi','-pedantic'])
env.SharedLibrary('sample', ['sample.c'])
sample.py
import numpy as N
import ctypes as C
dat = [[1126877361,'sunny'], [1126877371,'rain'], [1126877385,'damn nasty'], [1126877387,'sunny']]
dat_dtype = N.dtype([('timestamp','i4'),('desc','|S12')])
arr = N.rec.fromrecords(dat,dtype=dat_dtype)
_sample = N.ctypeslib.load_library('libsample','.')
_sample.print_weather.restype = None
_sample.print_weather.argtypes = [N.ctypeslib.ndpointer(dat_dtype, flags='aligned, contiguous'), C.c_int]
def print_weather(x):
_sample.print_weather(x, x.size)
if __name__=='__main__':
print_weather(arr)
斐波那契示例(使用 NumPy 数组、C 和 Scons)¶
以下测试通过,并在 Windows(使用 MinGW)和 GNU/Linux 32 位操作系统(上次测试于 2009 年 8 月 13 日)上运行。将所有三个文件复制到同一个目录。
C 代码(它递归地计算斐波那契数)
/*
Filename: fibonacci.c
To be used with fibonacci.py, as an imported library. Use Scons to compile,
simply type 'scons' in the same directory as this file (see www.scons.org).
*/
/* Function prototypes */
int fib(int a);
void fibseries(int *a, int elements, int *series);
void fibmatrix(int *a, int rows, int columns, int *matrix);
int fib(int a)
{
if (a <= 0) /* Error -- wrong input will return -1. */
return -1;
else if (a==1)
return 0;
else if ((a==2)||(a==3))
return 1;
else
return fib(a - 2) + fib(a - 1);
}
void fibseries(int *a, int elements, int *series)
{
int i;
for (i=0; i < elements; i++)
{
series[i] = fib(a[i]);
}
}
void fibmatrix(int *a, int rows, int columns, int *matrix)
{
int i, j;
for (i=0; i<rows; i++)
for (j=0; j<columns; j++)
{
matrix[i * columns + j] = fib(a[i * columns + j]);
}
}
Python 代码
"""
Filename: fibonacci.py
Demonstrates the use of ctypes with three functions:
(1) fib(a)
(2) fibseries(b)
(3) fibmatrix(c)
"""
import numpy as nm
import ctypes as ct
# Load the library as _libfibonacci.
# Why the underscore (_) in front of _libfibonacci below?
# To mimimise namespace pollution -- see PEP 8 (www.python.org).
_libfibonacci = nm.ctypeslib.load_library('libfibonacci', '.')
_libfibonacci.fib.argtypes = [ct.c_int] # Declare arg type, same below.
_libfibonacci.fib.restype = ct.c_int # Declare result type, same below.
_libfibonacci.fibseries.argtypes = [nm.ctypeslib.ndpointer(dtype = nm.int),\
ct.c_int,\
nm.ctypeslib.ndpointer(dtype = nm.int)]
_libfibonacci.fibseries.restype = ct.c_void_p
_libfibonacci.fibmatrix.argtypes = [nm.ctypeslib.ndpointer(dtype = nm.int),\
ct.c_int, ct.c_int,\
nm.ctypeslib.ndpointer(dtype = nm.int)]
_libfibonacci.fibmatrix.restype = ct.c_void_p
def fib(a):
"""Compute the n'th Fibonacci number.
ARGUMENT(S):
An integer.
RESULT(S):
The n'th Fibonacci number.
EXAMPLE(S):
>>> fib(8)
13
>>> fib(23)
17711
>>> fib(0)
-1
"""
return _libfibonacci.fib(int(a))
def fibseries(b):
"""Compute an array containing the n'th Fibonacci number of each entry.
ARGUMENT(S):
A list or NumPy array (dim = 1) of integers.
RESULT(S):
NumPy array containing the n'th Fibonacci number of each entry.
EXAMPLE(S):
>>> fibseries([1,2,3,4,5,6,7,8])
array([ 0, 1, 1, 2, 3, 5, 8, 13])
>>> fibseries(range(1,12))
array([ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55])
"""
b = nm.asarray(b, dtype=nm.intc)
result = nm.empty(len(b), dtype=nm.intc)
_libfibonacci.fibseries(b, len(b), result)
return result
def fibmatrix(c):
"""Compute a matrix containing the n'th Fibonacci number of each entry.
ARGUMENT(S):
A nested list or NumPy array (dim = 2) of integers.
RESULT(S):
NumPy array containing the n'th Fibonacci number of each entry.
EXAMPLE(S):
>>> from numpy import array
>>> fibmatrix([[3,4],[5,6]])
array([[1, 2],
[3, 5]])
>>> fibmatrix(array([[1,2,3],[4,5,6],[7,8,9]]))
array([[ 0, 1, 1],
[ 2, 3, 5],
[ 8, 13, 21]])
"""
tmp = nm.asarray(c)
rows, cols = tmp.shape
c = tmp.astype(nm.intc)
result = nm.empty(c.shape, dtype=nm.intc)
_libfibonacci.fibmatrix(c, rows, cols, result)
return result
以下是 SConstruct 文件内容(文件名:SConstruct)
env = Environment()
env.Replace(CFLAGS=['-O2', '-Wall', '-ansi', '-pedantic'])
env.SharedLibrary('libfibonacci', ['fibonacci.c'])
在 Python 解释器(或您使用的任何工具)中,执行
>>> import fibonacci as fb
>>> fb.fib(8)
13
>>> fb.fibseries([5,13,2,6]
array([ 3, 144, 1, 5])
相关邮件列表主题¶
ctypes-users 邮件列表中的一些有用主题
- [http://aspn.activestate.com/ASPN/Mail/Message/ctypes-users/3119087 在 POINTER(POINTER(ctype)) 上索引时出现 IndexError]
- [http://aspn.activestate.com/ASPN/Mail/Message/ctypes-users/3118513 为 NumPy 添加 ctypes 支持]
- [http://aspn.activestate.com/ASPN/Mail/Message/ctypes-users/3118656 确定 ctype 是否为指针类型(原主题:为 NumPy 添加 ctypes 支持)]
- [http://aspn.activestate.com/ASPN/Mail/Message/ctypes-users/3117306 检查空指针,不使用 ValueError]
- [http://aspn.activestate.com/ASPN/Mail/Message/ctypes-users/3205951 从 C 到 Python 的回调问题]
- [http://thread.gmane.org/gmane.comp.python.numeric.general/7418\ ctypes 和 ndpointer]
- [http://thread.gmane.org/gmane.comp.python.ctypes/3116 64 位有符号整数问题]
Thomas Heller 的回答特别有见地。
文档¶
- [http://starship.python.net/crew/theller/ctypes/tutorial.html ctypes 教程]
- [https://docs.pythonlang.cn/dev/lib/module-ctypes.html 13.14 ctypes -- Python 的外部函数库。]
章节作者:AlbertStrasheim、GaelVaroquaux、StefanVanDerWalt、TravisOliphant、DavidLinke、Unknown[41]、AndrewStraw、Unknown[42]、WilliamHunter、mauro