使用C语言扩展Python3
使用Python3调用C语言函数
语言导引
语言导引部分,包括python3和c语言的知识。
python3基础知识
这里假设读者已经具备python3的基础编程知识。包括导入包(import
),调用包中的函数等等。
C语言基础知识
对于C语言,希望读者已经了解类型、函数调用以及参数传递等知识。我们这里还需要对函数指针做一些说明。
顾名思义,函数指针是指指向函数的指针。这个特性是C语言指针的特性之一,也是C语言本身的中要特性。它的作用之一就是可以实现函数的动态指定。也就是说,在函数参数列表不变的前提下,变换函数的地址,使之在状态切换中,完成不同种类的任务,是“多态”实现的主要手段。
下面是一个函数指针的例子。下面的代码中列出了两个函数指针的声明:
int add(int a, int b);
int mul(int a, int b);
我们假定第一个函数实现的功能是将两个数相加并返回和的功能,第二个函数是将两个数相乘并返回积的功能。那么如何根据不同条件情况,在传入参数相同的情况下,执行同一个函数名代表的函数而返回不同结果呢?即
a = SOME_INT1;
b = SOME_INT2;
if (condition1) {
res = oper(a, b); /* res_add是相加结果 */
}
else if (condition2) {
res = oper(a, b); /* res_mul是相乘结果 */
}
那么这里就需要用到函数指针的概念了。
typedef int (*func_ptr)(int a, int b);
上述代码定义了一个类型func_ptr
。它指向了一个接收两个整型参数并且返回值为整型的函数。我们声明一个这样类型的变量,并初始化其为NULL:
func_ptr oper = NULL;
当满足条件condition1
时,我们将其指定为add
;满足条件condition2
时,我们将其指定为mul
,即:
if (condition1) {
oper = add;
/* ... */
} else if (condition2) {
oper = mul;
/* ... */
}
/* do the work */
全部的代码为:
/* declaration of the original functions */
int add(int a, int b);
int mul(int a, int b);
a = SOME_INT1;
b = SOME_INT2;
int calc(a, b) {
func_ptr oper = NULL;
int res;
/* ... */
if (condition1) {
oper = add;
} else if (condition2) {
oper = mul;
}
res = oper(a, b); /* res是相加结果 */
return res;
}
准备工作
基础C函数
首先我们引入一个c函数,它的作用是求解两个数的最大公约数。它是欧几里得提出的历史悠久的算法。有读者对证明过程感兴趣,可以参阅相关材料。下面是实现的代码:
#include <math.h>
int gcd(int x, int y) {
int g = y;
while(x > 0) {
g = x;
x = y % x;
y = g;
}
return g;
}
如果在Linux环境下使用gcc编译并调用这个函数,则它会返回两个数的最大公约数。下面我们主要来研究如何在Python3环境下调用这个函数。
编译并生成库
对于上述函数,我们设置好相应的选项,并利用gcc进行编译。编译命令为:
gcc -shared -lm -o libgcd.so gcd.c
其中gcc是编译器命令,-shared
表示生成共享库,-lm
表示使用数学函数库,-o libgcd.so
表示输出libgcd.so的共享库文件,gcd.c
是输入文件。
编译通过后,在本文件夹下生成对应的libgcd.so的一个共享库文件。
使用ctypes
初步尝试
ctypes是一个外部函数调用库。它提供了C兼容数据类型,并且允许调用共享库中的函数。可以借助ctypes这个库使用纯Python3语言对C函数进行封装。因而使用ctypes可以将上面得到的共享库文件载入。经过恰当的设置,我们就可以对这个函数进行调用。
# sample.py
import ctypes
import os
_file = 'libgcd.so'
_path = os.path.join(os.path.dirname(__file__), _file)
_mod = ctypes.cdll.LoadLibrary(_path)
gcd = _mod.gcd
gcd.argtypes = (ctypes.c_int, ctypes.c_int)
gcd.restype = ctypes.c_int
其中os.path.join()
的作用是获得libgcd.so的共享库的绝对路径。ctypes.cdll.LoadLibrary(_path)
是根据路径_path
载入动态库,并返回一个关于该库的实例对象。
下面的gcd
变量是根据_mod.gcd
得到的C语言函数指针(function pointer,其具体的含义如前所述)。gcd
的成员变量argtypes
接收一个由ctypes的类型构成的元组来指定该函数所接收的参数类型。而成员变量restype
指定函数返回值的ctypes的类型。对于函数gcd
来说,它可接受的参数为(c_int, c_int)
类型,返回的是一个c_int类型的值。
上述代码是我们对于底层c函数库的封装,使之能够以包的形式被导入且可调用。
下面我们就导入sample
的包并输出结果。在同目录下创建test.py文件,代码如下:
import sample
res_num = sample.gcd(56, 20)
print(res_num)
运行这个程序,显示结果为4。
通过上面的例子,我们可以这样理解ctypes库:它提供了一个对于动态库的框架,在这个框架中,通过合乎函数形式的设置,我们可以调用载入的动态库的函数。
下一篇我们将进一步深入对于C语言函数的调用。