使用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语言函数的调用。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值