python中ctypes的使用

在Python中,可以直接调用c/c++写的so二进制代码,具体可参照官方文档Python中的ctypes模块使用

GNU的基本库是如下两个:

/lib/x86_64-linux-gnu/libc.so.6
/usr/lib/x86_64-linux-gnu/libstdc++.so.6

使用如下代码就能加载到Python环境中了,只需要指定文件名,Python就能自动找到对应文件:

import ctypes
r = ctypes.cdll.LoadLibrary('libc.so.6')

接下来就可以非常方便地使用so中的函数方法了:

In [9]: r.strchr('hello world', ord('o'))
Out[9]: 38484216

似乎使用起来不太对,这是因为python不知道函数原型,所以需要指定函数的入参和出参类型:

In [16]: strchr.argtypes = [ctypes.c_char_p, ctypes.c_char]

In [17]: strchr.restype = ctypes.c_char_p

In [18]: strchr('hello world', 'o')
Out[18]: 'o world'

这样python就知道'o'应该转换成char类型了,就不需要使用ord()转成整数了。

很明显使用二进制lib库,能提升计算速度,从下面的例子来看,大概有五倍的差距:

import random
rand = r.rand

In [25]: %timeit sum([rand() % 10 for i in xrange(1000)])
1000 loops, best of 3: 253 us per loop

In [26]: %timeit sum([random.randint(0, 10) for i in xrange(1000)])
1000 loops, best of 3: 1.15 ms per loop

对于数据类型的设计,Python真的太美妙了,使用乘法来创建数组类型,例如创建一个长度为5的整型数组类型:

IntArray5 = ctypes.c_int * 5
array = IntArray5(1,2,3,4,5)

关于ctypes中地址指针传递

从Python的官方文档来看,ctypes.pointer和ctypes.byref都可以进行取地址,但是ctypes.pointer做了更多的事情。像使用c语言中的scanf来获取输入,就需要存储地址,使用byref就足够了:

a = ctypes.c_int()
b = ctypes.create_string_buffer(10)
ctypes.sizeof(b)
>>> 10

r.scanf("%d %s", ctypes.byref(a), b)

举个完整的例子

一个完整的例子,包括如何将python中的数据传递给so,然后从so中获取计算结果。

首先是C++代码如下,用来生成so库,Line结构体用来存储一条折线上的所有点。

头文件line.h内容为:

truct Point
{
    int x;
    int y;
};

struct Line
{
    int count;
    Point points[32];
};

int generate_line(Line *line);

实现文件为line.cpp:

#include "line.h"
#include <stdio.h>
#include <stdlib.h>

int generate_line(Line *line)
{
    if (NULL == line) {
        return -1; 
    }   

    printf("cout:%d\n", line->count);
    for (int i = 0; i < 32; ++i) {
        printf("(%d,%d)", line->points[i].x, line->points[i].y);
    }   
    printf("\n");

    line->count = 10; 

    Point p;
    for (int i = 0; i < line->count; ++i) {
        p.x = i;
        p.y = i;
        line->points[i] = p;
    }   

    return 0;
}

Makefile内容如下,make一下就可以生成libline.so了:

all:
    g++ -shared -O3 -Wall -fPIC line.cpp -I./ -o libline.so 

接着就可以在Python中使用了,加载库中函数以及定义原型如下:

dll = ctypes.cdll.LoadLibrary('/tmp/libline.so')
generate_line = dll._Z13generate_lineP4Line
generate_line.restype = ctypes.c_int
generate_line.argtypes = [ctypes.POINTER(Line)]

定义Python的类型,以及随意初始化一些值:

class Point(ctypes.Structure):
    _fields_ = [('x', ctypes.c_int), ('y', ctypes.c_int)]

class Line(ctypes.Structure):
    _fields_ = [('count', ctypes.c_int), ('points', Point * 32)]

line = Line()

line.count = 3

line.points[0] = Point(1,2)

line.points[1] = Point(3,4)

line.points[2] = Point(5,6)

然后就可以执行了,这里执行两遍:

res = generate_line(ctypes.byref(line))
res = generate_line(ctypes.byref(line))

输出内容如下,符合预期:

cout:3
(1,2)(3,4)(5,6)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)
cout:10
(0,0)(1,1)(2,2)(3,3)(4,4)(5,5)(6,6)(7,7)(8,8)(9,9)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)(0,0)
发表于 2014年10月10日 16:05   评论:0   阅读:4435  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo