ctypes - 初学者

138

我有一个任务,需要将一个c库封装成一个python类。但文档对此事讲解极其模糊。似乎他们只期望高级Python用户才会实现ctypes。

希望能得到一些详细的步骤说明。

所以我有了我的c库。我该怎么做?我应该把哪些文件放在哪里?如何导入库?我看到可能有一种“自动封装”到Python的方法?

(顺便说一下,我做了python.net上的ctypes教程,但它不起作用。这意味着我认为他们认为我应该能够填写其余的步骤。)

事实上,这是我使用他们的代码时得到的错误:

File "importtest.py", line 1
   >>> from ctypes import *
   SyntaxError: invalid syntax

我真的需要一些逐步的帮助!


11
你的 importtest.py 文件里有没有 >>> 符号? 如果人们发布的代码每行都有 >>>,那就代表这些代码是在交互式Shell中运行的。 如果要从文件中运行它,请删除任何出现 >>>(三个大于号和一个空格)的地方。 - Chinmay Kanchi
5
请勿输入>>>符号。这些符号是交互式 Shell 打印的,应该在源代码文件中省略。 - nmichaels
2
第三行不是第一行。这可能意味着第三行有一个不同的语法错误。 - Chinmay Kanchi
1
有一个东西说:“把你的库放在这里,然后执行这个操作来使用它的函数”,那就太好了。 - spentak
3
如果你请求帮助,请提供充足的信息。至少展示你所谈论的代码的最新版本。例如,“第三行”是什么? - Francesco
显示剩余5条评论
3个回答

269

这是一个快速而简单的 ctypes 教程。

首先,编写你的 C 库。下面是一个简单的 Hello world 示例:

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()时必须提供库的完整路径。

这里并不是一个更全面的教程,但如果你在这个网站上寻求特定问题的帮助,我相信社区会帮助你。

附注:我假设你使用的是Linux系统,因为你使用了ctypes.CDLL('libc.so.6')。如果你使用的是其他操作系统,可能会有一些变化(也可能很大)。


2
@ Chinmay:我能否获得一个类似的Windows代码,而且不是C语言,你可以提供一个Visual C++的例子吗?我能够加载我的库,但是我无法从.dll文件中访问我的函数。它总是会显示“找不到函数'xyz'”。你能否建议一种解决方法?干杯。 - Neophile
我对Windows开发不是很了解,但看起来Windows做了一些奇怪的事情,也许它使用了不同的调用约定?也许你可以查阅使用“extern C”导出C++函数的方法? - Chinmay Kanchi
是的,我确实做了那个,但到目前为止没有运气。 - Neophile
只有在使用 extern "C" { void myprint(void); } 时才对我有效。 - decadenza

71

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代码使用numpy.ctypeslib中的load_library,假设共享库与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语言的顺序存储的。第二个参数是一个整数值,告诉C代码Numpy数组中元素的数量。然后,C代码会按如下方式打印出这些内容:

1.4
2.6
3.0

同样地,包含“print_array()”的.cpp文件需要“#include <iostream>”和“using namespace std;”。否则它将无法找到“cout”或“endl”。 - Harry

18

首先:你在Python示例中看到的>>>代码是一种表示它是Python代码的方法。它用于将Python代码与输出分开。像这样:

>>> 4+5
9

这里我们可以看到以>>>开头的一行是Python代码,而9就是它的结果。如果你启动一个Python解释器,它正是这样显示的,这也是为什么要这样做的原因。

你永远不会在.py文件中输入>>>部分。

这就解决了语法错误的问题。

其次,ctypes只是包装Python库的几种方法之一。其他方式包括SWIG,它将查看你的Python库并生成公开C API的Python C扩展模块。另一种方法是使用Cython

它们都有优缺点。

SWIG只会将您的C API公开给Python。这意味着您不会获得任何对象等内容,您需要编写单独的Python文件来完成这些操作。但是通常会有一个名为"wowza"的模块和一个名为"_wowza"的SWIG模块,它是C API的包装器。这是一种不错而简单的方法。

Cython生成一个C扩展文件。它的好处是您编写的所有Python代码都变成了C,因此您编写的对象也是C,这可能会提高性能。但是您需要学习如何与C进行接口,因此需要额外的工作学习如何使用它。

ctypes的好处在于没有要编译的C代码,因此非常适合用于包装由他人编写的标准库,并且已经存在Windows和OS X的二进制版本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接