『Python CoolBook』使用ctypes訪問C代碼_下_demo進階

點擊進入項目python

這一次咱們嘗試一下略微複雜的c程序。git

1、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函數例子,其中有一個是經過指針參數的方式。github

avg() 函數經過一個C數組執行數據彙集操做。數組

Pointdistance() 函數涉及到了C結構體。ide

/* 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文件後,咱們嘗試調用這些方法。函數

2、Python封裝

.argtypes 屬性是一個元組,包含了某個函數的輸入按時, 而 .restype 就是相應的返回類型。spa

ctypes 定義了大量的類型對象(好比c_double, c_int, c_short, c_float等), 表明了對應的C數據類型。指針

若是你想讓Python可以傳遞正確的參數類型而且正確的轉換數據的話, 那麼這些類型簽名的綁定是很重要的一步。若是你沒有這麼作,不但代碼不能正常運行, 還可能會致使整個解釋器進程掛掉。rest

導入c庫文件

import os
import ctypes

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

簡單數據類型函數封裝

實際上因爲這種函數的參數類型c語言和python語言中的類型是一一對應的,因此即便把.argtypes與.restype註釋掉也能夠正常運行,但建議進行轉換code

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 >>>
相關文章
相關標籤/搜索