python调用ctypes接收指针_『Python CoolBook』使用ctypes访问C代码_下_demo进阶

这一次我们尝试一下略微复杂的c程序。

一、C程序

头文件:

#ifndef __SAMPLE_H__

#define __SAMPLE_H__

#include

#ifdef __cplusplus

extern "C" {

#endif

int gcd(int x, int y);

int in_mandel(double x0, double y0, int n);

int divide(int a, int b, int *remainder);

double avg(double *a, int n);

/* A C data structure */

typedef struct Point {

double x,y;

} Point;

double distance(Point *p1, Point *p2);

#ifdef __cplusplus

}

#endif

#endif

源程序:

divide() 函数是一个返回多个值的C函数例子,其中有一个是通过指针参数的方式。

avg() 函数通过一个C数组执行数据聚集操作。

Point 和 distance() 函数涉及到了C结构体。

/* sample.c */

#include "sample.h"

/* Compute the greatest common divisor */

int gcd(int x, int y) {

int g = y;

while (x > 0) {

g = x;

x = y % x;

y = g;

}

return g;

}

/* Test if (x0,y0) is in the Mandelbrot set or not */

int in_mandel(double x0, double y0, int n) {

double x=0,y=0,xtemp;

while (n > 0) {

xtemp = x*x - y*y + x0;

y = 2*x*y + y0;

x = xtemp;

n -= 1;

if (x*x + y*y > 4) return 0;

}

return 1;

}

/* Divide two numbers */

int divide(int a, int b, int *remainder) {

int quot = a / b;

*remainder = a % b;

return quot;

}

/* Average values in an array */

double avg(double *a, int n) {

int i;

double total = 0.0;

for (i = 0; i < n; i++) {

total += a[i];

}

return total / n;

}

/* Function involving a C data structure */

double distance(Point *p1, Point *p2) {

return hypot(p1->x - p2->x, p1->y - p2->y);

}

生成so文件后,我们尝试调用这些方法。

二、Python封装

.argtypes 属性是一个元组,包含了某个函数的输入按时, 而 .restype 就是相应的返回类型。

ctypes 定义了大量的类型对象(比如c_double, c_int, c_short, c_float等), 代表了对应的C数据类型。

如果你想让Python能够传递正确的参数类型并且正确的转换数据的话, 那么这些类型签名的绑定是很重要的一步。如果你没有这么做,不但代码不能正常运行, 还可能会导致整个解释器进程挂掉。

导入c库文件

import os

import ctypes

_mod = ctypes.cdll.LoadLibrary('./libsample.so')

简单数据类型函数封装

实际上由于这种函数的参数类型c语言和python语言中的类型是一一对应的,所以即使把.argtypes与.restype注释掉也可以正常运行,但建议进行转换

gcd:

原函数

/* Compute the greatest common divisor */

int gcd(int x, int y) {

int g = y;

while (x > 0) {

g = x;

x = y % x;

y = g;

}

return g;

}

调用

# int gcd(int, int)

gcd = _mod.gcd

gcd.argtypes = (ctypes.c_int, ctypes.c_int)

gcd.restype = ctypes.c_int

print(gcd(35, 42)) # 7

in_mandel:

原函数

/* Test if (x0,y0) is in the Mandelbrot set or not */

int in_mandel(double x0, double y0, int n) {

double x=0,y=0,xtemp;

while (n > 0) {

xtemp = x*x - y*y + x0;

y = 2*x*y + y0;

x = xtemp;

n -= 1;

if (x*x + y*y > 4) return 0;

}

return 1;

}

调用

# int in_mandel(double, double, int)

in_mandel = _mod.in_mandel

in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)

in_mandel.restype = ctypes.c_int

print(in_mandel(0,0,500)) # 1

含有指针形参函数--指针用于修改变量

divide() 函数通过一个参数除以另一个参数返回一个结果值,但是指针是python中不支持的操作。

/* Divide two numbers */

int divide(int a, int b, int *remainder) {

int quot = a / b;

*remainder = a % b;

return quot;

}

对于涉及到指针的参数,你通常需要先构建一个相应的ctypes对象并像下面这样传进去:

divide = _mod.divide

x = ctypes.c_int()

divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))

print(divide(10,3,x))

print(x.value)

在这里,一个 ctypes.c_int 实例被创建并作为一个指针被传进去。 跟普通Python整形不同的是,一个 c_int 对象是可以被修改的。 .value 属性可被用来获取或更改这个值。

更一般的,对于带指针的函数,我们会将其加一层封装后调用,使得通过指针修改的变量通过return返回,这样去c style,使得代码更像python风格:

# int divide(int, int, int *)

_divide = _mod.divide

_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))

_divide.restype = ctypes.c_int

def divide(x, y):

rem = ctypes.c_int()

quot = _divide(x,y,rem)

return quot, rem.value

含有指针形参函数--指针用于接收数组

avg() 函数又是一个新的挑战。C代码期望接受到一个指针和一个数组的长度值。 但是,在Python中,我们必须考虑这个问题:数组是啥?它是一个列表?一个元组? 还是 array 模块中的一个数组?还是一个 numpy 数组?还是说所有都是? 实际上,一个Python“数组”有多种形式,你可能想要支持多种可能性。

/* Average values in an array */

double avg(double *a, int n) {

int i;

double total = 0.0;

for (i = 0; i < n; i++) {

total += a[i];

}

return total / n;

}

python -> c数组

(ctypes.c_int * 数组长度)(数组元素)

内在逻辑是:对于列表和元组,from_list 方法将其转换为一个 ctypes 的数组对象

nums = [1, 2, 3]

a = (ctypes.c_int * len(nums))(3,4,5)

print(a)

print(a[0], a[1], a[2])

# <__main__.c_int_Array_3 object at 0x7f2767d4fd08>

# 3 4 5

array对象本身存储结构和c数组一致,直接提取内存地址传给c指针即可:

import array

a = array.array('d',[1,2,3])

print(a)

ptr = a.buffer_info() # 返回tuple:(地址, 长度)

print(ptr[0])

print(ctypes.cast(ptr[0], ctypes.POINTER(ctypes.c_double))) # 目标地址存入指针

numpy数组自带ctypes.data_as(ctypes指针)方法,更为方便。

有如上基础,我们可以做出将python序列转化为c数组指针的class(这个class我没有完全弄懂其含义),并封装avg函数:

# void avg(double *, int n)

# Define a special type for the 'double *' argument

class DoubleArrayType:

def from_param(self, param):

typename = type(param).__name__

if hasattr(self, 'from_' + typename):

return getattr(self, 'from_' + typename)(param)

elif isinstance(param, ctypes.Array):

return param

else:

raise TypeError("Can't convert %s" % typename)

# Cast from array.array objects

def from_array(self, param):

if param.typecode != 'd':

raise TypeError('must be an array of doubles')

ptr, _ = param.buffer_info()

return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))

# Cast from lists/tuples

def from_list(self, param):

val = ((ctypes.c_double)*len(param))(*param)

return val

from_tuple = from_list

# Cast from a numpy array

def from_ndarray(self, param):

return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))

DoubleArray = DoubleArrayType()

_avg = _mod.avg

_avg.argtypes = (DoubleArray, ctypes.c_int)

_avg.restype = ctypes.c_double

def avg(values):

return _avg(values, len(values))

结构体

/* A C data structure */

typedef struct Point {

double x,y;

} Point;

/* Function involving a C data structure */

double distance(Point *p1, Point *p2) {

return hypot(p1->x - p2->x, p1->y - p2->y);

}

继承ctypes.Structure,_fields_命名结构体内元素即可:

# struct Point { }

class Point(ctypes.Structure):

_fields_ = [('x', ctypes.c_double),

('y', ctypes.c_double)]

# double distance(Point *, Point *)

distance = _mod.distance

distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))

distance.restype = ctypes.c_double

>>> p1 = Point(1,2)

>>> p2 = Point(4,5)

>>> p1.x

1.0

>>> p1.y

2.0

>>> distance(p1,p2)

4.242640687119285

>>>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值