ctypes - 初学者
我现在的任务是把一个C语言的库“包装”成一个Python类。可是文档写得很模糊,感觉他们只希望高级的Python用户来用ctypes。
如果能有一些一步一步的指导就太好了。
我现在有了我的C语言库,那我该怎么做呢?文件要放在哪里?怎么导入这个库?我听说可能有办法可以“自动包装”成Python?
顺便说一下,我在python.net上看了ctypes的教程,但没用。这意味着我觉得他们可能认为我应该能自己完成剩下的步骤。
实际上,我用他们的代码时出现了这个错误:
File "importtest.py", line 1
>>> from ctypes import *
SyntaxError: invalid syntax
我真的很需要一些一步一步的帮助!
3 个回答
首先,你在Python示例中看到的>>>
代码,是用来表示这是Python代码的一种方式。它的作用是把Python代码和输出结果分开。就像这样:
>>> 4+5
9
在这里,我们看到以>>>
开头的那一行是Python代码,而9是它的输出结果。这正是你启动Python解释器时的样子,所以才这样做。
你在.py
文件中永远不需要输入>>>
这一部分。
这样就解决了你的语法错误。
其次,ctypes只是多种封装Python库的方法之一。还有其他方法,比如SWIG,它会查看你的Python库,并生成一个Python C扩展模块,来暴露C语言的API。还有一种方法是使用Cython。
这些方法各有优缺点。
SWIG只会把你的C API暴露给Python。这意味着你不会得到任何对象或其他东西,你需要单独写一个Python文件来处理这些。不过,通常会有一个叫“wowza”的模块和一个叫“_wowza”的SWIG模块,后者是C API的封装。这是一种简单易行的方法。
Cython会生成一个C扩展文件。它的好处是你写的所有Python代码都会转成C,所以你写的对象也是C,这样可以提高性能。但你需要学习它如何与C语言接口,所以这需要额外花点时间去学习。
ctypes的好处在于不需要编译C代码,因此它非常适合用来封装别人写的标准库,并且这些库已经有Windows和OS X的二进制版本。
Chinmay Kanchi的回答非常棒,但我想给大家举个例子,说明一个函数是如何在C++代码中传递和返回变量/数组的。我觉得把这个例子放在这里,可能对其他人也有帮助。
传递和返回一个整数
下面是一个C++代码示例,这个函数接收一个整数,然后把这个整数加一后返回,
extern "C" int add_one(int i)
{
return i+1;
}
把它保存为文件 test.cpp
,注意需要加上 extern "C"(如果是C代码可以不加这个)。这个代码用g++编译,参数和Chinmay Kanchi的回答类似,
g++ -shared -o testlib.so -fPIC test.cpp
Python代码使用了 load_library
,来自 numpy.ctypeslib
,假设共享库和Python脚本在同一个目录下,
import numpy.ctypeslib as ctl
import ctypes
libname = 'testlib.so'
libdir = './'
lib=ctl.load_library(libname, libdir)
py_add_one = lib.add_one
py_add_one.argtypes = [ctypes.c_int]
value = 5
results = py_add_one(value)
print(results)
这样运行后会输出6,正如预期的那样。
传递和打印一个数组
你也可以像下面这样传递数组,C代码会打印数组中的元素,
extern "C" void print_array(double* array, int N)
{
for (int i=0; i<N; i++)
cout << i << " " << array[i] << endl;
}
这个代码的编译方式和之前一样,导入的方式也相同。额外的Python代码来使用这个函数如下,
import numpy as np
py_print_array = lib.print_array
py_print_array.argtypes = [ctl.ndpointer(np.float64,
flags='aligned, c_contiguous'),
ctypes.c_int]
A = np.array([1.4,2.6,3.0], dtype=np.float64)
py_print_array(A, 3)
在这里,我们指定数组,作为 print_array
的第一个参数,传入一个指向Numpy数组的指针,这个数组是对齐的、连续的64位浮点数,第二个参数是一个整数,用来告诉C代码Numpy数组中有多少个元素。然后C代码会这样打印出来,
1.4
2.6
3.0
这里有一个简单粗暴的ctypes教程。
首先,写一个C语言库。下面是一个简单的“你好,世界”的例子:
testlib.c
#include <stdio.h>
void myprint(void);
void myprint()
{
printf("hello world\n");
}
接下来,把它编译成一个共享库(这里有Mac的解决办法):
$ gcc -shared -Wl,-soname,testlib -o testlib.so -fPIC testlib.c
# or... for Mac OS X
$ gcc -shared -Wl,-install_name,testlib.so -o testlib.so -fPIC testlib.c
然后,使用ctypes写一个包装器:
testlibwrapper.py
import ctypes
testlib = ctypes.CDLL('/full/path/to/testlib.so')
testlib.myprint()
现在执行它:
$ python testlibwrapper.py
你应该能看到输出结果
Hello world
$
如果你已经有了想用的库,可以跳过教程中与Python无关的部分。确保ctypes能找到这个库,把它放在/usr/lib
或者其他标准目录下。如果这样做了,写包装器时就不需要指定完整路径。如果不这样做,你必须在调用ctypes.CDLL()
时提供库的完整路径。
这里不适合做更全面的教程,但如果你在这个网站上提出具体问题,我相信社区会帮助你。
PS:我假设你在使用Linux,因为你用了ctypes.CDLL('libc.so.6')
。如果你在其他操作系统上,情况可能会有些不同(或者变化很大)。