『Python CoolBook』使用ctypes访问C代码_下

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

一、C程序

头文件:

#ifndef __SAMPLE_H__
#define __SAMPLE_H__
#include <math.h>

#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数组执行数据聚集操作。

Pointdistance() 函数涉及到了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
>>>

原文地址:https://www.cnblogs.com/hellcat/p/9078321.html

时间: 2024-07-31 11:50:01

『Python CoolBook』使用ctypes访问C代码_下的相关文章

『Python CoolBook』C扩展库_其六_从C语言中调用Python代码

一.C语言运行pyfun的PyObject对象 思路是在C语言中提供实参,传给python函数: 获取py函数对象(PyObject),函数参数(C类型) 获取GIL(PyGILState_Ensure) 确保fun对象可调用 参数转换为python对应类型(Py_BuildValue) 调用python函数(PyObject_Call) 确定调用无异常 检查返回值 释放GIL(PyGILState_Release) 异常处理 #include "Python.h" /* Execut

『Python CoolBook』C扩展库_其六_线程

GIL操作 想让C扩展代码和Python解释器中的其他进程一起正确的执行, 那么你就需要去释放并重新获取全局解释器锁(GIL). 在Python接口封装中去释放并重新获取全局解释器锁(GIL),此时本段程序失去GIL运行,其他线程可以无视本函数的运行而运行,直到Py_END_ALLOW_THREADS: #include "Python.h" ... PyObject *pyfunc(PyObject *self, PyObject *args) { ... Py_BEGIN_ALLO

『Python CoolBook』数据结构和算法_字典比较&amp;字典和集合

一.字典元素排序 dict.keys(),dict.values(),dict.items() 结合max.min.sorted.zip进行排序是个很好的办法,另外注意不使用zip时,字典的lambda操作方法: price = { 'a':1, 'b':2, 'c':3 } # 多个键的值相同时会采取元组比较的形式,实际应用时注意 min_p = min(zip(price.values(), price.keys())) max_p = max(zip(price.values(), pri

『Python进阶』专题汇总

基础知识 『流畅的Python』第1~4章_数据结构.编码 『Python』基础数据结构常见使用方法 『Python CoolBook』数据结构和算法_多变量赋值&“*”的两种用法 『Python CoolBook:Collections』数据结构和算法_collections.deque队列&yield应用 『Python CoolBook:heapq』数据结构和算法_heapq堆队列算法&容器排序 『Python CoolBook:Collections』数据结构和算法_容器型

Python Cookbook(第3版)中文版:15.1 使用ctypes访问C代码

15.1 使用ctypes访问C代码? 问题? 你有一些C函数已经被编译到共享库或DLL中.你希望可以使用纯Python代码调用这些函数,而不用编写额外的C代码或使用第三方扩展工具. 解决方案? 对于需要调用C代码的一些小的问题,通常使用Python标准库中的 ctypes 模块就足够了.要使用 ctypes ,你首先要确保你要访问的C代码已经被编译到和Python解释器兼容(同样的架构.字大小.编译器等)的某个共享库中了.为了进行本节的演示,假设你有一个共享库名字叫 libsample.so 

『Python基础』第5节:条件控制

if 语句的使用 单分支 if 条件: 满足条件后要执行的代码 例如: if 2 < 3: print(222) print(333) 每个条件后面都要使用冒号 :, 表示接下来是满足条件后要执行的语句块 在Python中没有 switch - case 语句 双分支 if 条件: 满足条件要执行的代码 else: 不满足条件就执行这里的代码 例如: age = 18 if age >= 18: print('恭喜你, 成年了') else: print('小屁孩儿') 缩进 你会发现, 在上

『Python基础』第6节:流程控制之while循环

在生活中经常遇到循环的事情, 比如循环列表播放歌曲等. 在Python中, 也有循环, 就是其流程控制语句while. 1. 基本循环 while 条件: 循环体 # 如果条件为真, 那么就执行循环体 # 如果条件为假, 那么就不执行循环体. 举个例子: while True: print('你不要担心') print('青春') print('有一天') 上面的代码有一个问题就是: 没有停止的时候. 只要电脑不死机, 就会一直循环下去. 那么具体的它是怎么执行的呢? 看下面这张图. 那么应该如

『Python基础』第4节:基础数据类型初识

本节只是对基础数据类型做个简单介绍, 详情会在之后慢慢介绍 什么是数据类型? 我们人类可以分清数字与字符串的区别, 可是计算机不能. 虽然计算机很强大, 但在某种程度上又很傻, 除非你明确告诉它数字与字符串的区别. 因此, 在每个编程语言中都有叫 数据类型 的东西, 其实就是对常用的数据类型进行了明确的划分. Python中常用的数据类型有很多种, 本节只介绍3中: 数字.字符串.布尔类型 整数类型 - int 在32位机器上,整数的位数为32位,取值范围为-231-231-1,即-214748

『Python』常用函数实践笔记

库安装: 1).pip & conda 2).在win10下手动安装python库的方法: 『python』计算机视觉_OpenCV3库安装 原生: list.append():添加元素到list末尾 list.extend():使用一个list扩展另一个list 字典列表化:字典是有顺序的,而且list字典等于list字典的key dict = {'c':1,'b':2,'a':3} list(dict) # Out[13]: # ['c', 'b', 'a'] list(dict.keys(