本文简单说明了如何使用python的ctpyes库,调用C程序。

ctypes是Python的外部函数库。它提供C兼容的数据类型,并允许在DLL或共享库中调用函数。它可以用于在纯Python中包装这些库。(本文基于python 3.7.2版本)

简答的例子

我们先写一个简单的C语言程序,其接受和返回的值均为int类型:

#include <stdio.h>

int add(int a, int b)
{
    printf("receive parameters from python\n");
    return a+b;
}

接着我们使用gcc生成动态库:

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

-f后面跟一些编译选项,PIC是其中一种,表示生成位置无关代码(Position Independent Code)。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。

我们在python中使用ctypes载入动态库。

>>> from ctypes import *
>>> lib= cdll.LoadLibrary("add.so")
>>> print(lib.add(1, 2))
receive parameters from python
3

如果参数是int型的变量,我们可以直接传入和接收C程序的参数,不需要做任何额外的处理,但当参数比较复杂时,就会出现问题。

字符串的传入和传出

我们现在对原始的C程序做简单的修改:

#include <stdio.h>
#include <string.h>

char *add(char *a, char *b)
{
    char *p;
    printf("receive parameters from python :\n");
    printf("%s\n%s\n", a, b);
    strcat(p, a);
    strcat(p, b);
    return p;
}

我们将python程序改为:

from ctypes import *
lib= cdll.LoadLibrary("add.so")
a = "abc"
b = "abc"
print(lib.add(a, b))

程序运行的结果如下:

receive parameters from python :
a
a
1063308456

结果显示程序存在以下问题:

  • 字符串仅仅传入第一个字符
  • 返回结果是一个很大的随机数,每次运行结果均不相同

由于C语言里没有string类型,所有的字符串实际都是以字符指针的形式传递,因此无法直接的传入和接收字符串。要想正确的传入数据,我们需要把字符串转化为2进制形式

>>> from ctypes import *
>>> lib= cdll.LoadLibrary("add.so")
>>> lib.add(b"zhf", b"sdf")
receive parameters from python :
zhf
sdf
118293016

我们可以看到,字符串此时能够成功的传入程序,如果字符串是中文。可以使用.encode('utf-8')来实现字符串的转化。

>>> lib.add("天天".encode('utf-8'), b"zhf")
receive parameters from python :
天天
zhf

由于程序默认返回的是int型变量,如果需要返回其他类型的变量,我们需要设定restype,修改后代码如下:

>>> lib.add.restype = c_char_p
>>> x = lib.add("天天".encode('utf-8'), "向上".encode('utf-8'))
>>> x.decode('utf-8')
天天向上

经过修改,我们能够程序能够顺利的接收和返回字符串。

其他类型

None,整数,长整数,字节字符串和unicode字符串是少数的可以直接由Python参数传递给C程序的类型。对于其他更多的变量,我们需要进行以下操作:

  • 将实参转换为ctypes内定义的格式
  • 设定c动态库的restype,确定程序的返回格式(必须设为ctypes支持的格式)
  • 接收c程序返回的参数,并将其转化为python的变量类型

ctypes定义的原始C兼容的数据类型:

所有这些类型都可以通过使用正确类型和值的可选初始化程序调用它们来创建,由于这些类型是可变的,因此它们的值也可以在以后更改:

>>> c_char_p("Hello, World")
c_char_p('Hello, World')
>>> i = c_int(42)
>>> print(i)
c_int(42) 
>>> print(i.value)
42
>>> i.value = -99
>>> print(i)
c_int(-99)

自定义格式

我们还可以自定义ctypes参数格式,将自己的类的实例用作函数参数。 ctypes查找_as_parameter_属性并将其用作函数参数。当然,它必须是整数,字符串或unicode之一:

class Bottles(object):
	def __init__(self, number):
		self._as_parameter_ = number

同时,ctypes支持传递指针、结构体、联合体、数组等C语言中支持的数据类型,本文不在此一一说明,感兴趣的话可以阅读ctypes的官方文档加以了解。