zoukankan      html  css  js  c++  java
  • 『Python CoolBook』使用ctypes访问C代码_下_demo进阶

    点击进入项目

    这一次我们尝试一下略微复杂的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
    >>>
  • 相关阅读:
    spring属性注入
    spring的bean标签属性
    spring三种实例化bean的方式
    测试环境部署
    sql server将字符串转换为 uniqueidentifier 时失败
    mybatis parametertype 多个参数
    ORACLE-023:令人烦恼的 ora-01722 无效数字
    PostMan使用教程
    sqlserver复制表数据到另一个表
    to_number()函数的用法
  • 原文地址:https://www.cnblogs.com/hellcat/p/9078321.html
Copyright © 2011-2022 走看看