在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)
从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)