1 将c编译为so
gcc -fPIC - shared xxx.c -o xxx.so
2 在python中导入so
from ctypes import *
lib = CDLL('xxx/xxx/xxx.so')
3 调用c中的方法
ps:这一步很讲究,ctypes这个库很重要,对它要熟悉。主要涉及到如何传参给c,又如何正确的拿到c的返回结果
3-1 举栗:传一个字符串给c,c返回字符串给python
//c代码
char * func(char*,int len){
}
#python传参数
text='abcdefg'
p=c_wchar_p(text)
//或者 p = c_char_p(bytes(text,encoding="utf-8"))
lib.func(p,len(text))
#为了正确拿到c的返回,需要指定其返回值类型
reply = lib.func
reply.restype=c_char_p
result = reply() #这里拿到byte类型
result = str(result,encoding="utf-8")) #转换为字符串类型
ps:如果没有指定返回类型,将会导致报错Segmentation fault
3-2 当底层c仅接受unsigned char*时
当c仅接受unsigned char*时,以上方法不可用,它传的的是char * ,可能会报错BUS ERROR.
经过一番实验,发现ctype中可以进行类型指定
text_str='abcdefg'
#如果需要修改内容,此处可用bytearray
text_bytes = bytes(text_str,encoding="utf-8")
text = create_string_buffer(text_bytes)
lib.func(cast(text,POINTER(c_ubyte)))
4 python和c混合编程
参考1:ctypes官方文档 https://docs.python.org/2/library/ctypes.html
4-1 指针
参考2:https://blog.csdn.net/Kelvin_Yan/article/details/86546784
这里对指针的用法有比较好的例子
#创建 c_ubyte_Array_12 对象
buf = (c_ubyte * 12)()
#获取其指针
p = pointer(buf)
#获取指针内容,直接用buf也行
p.contents
4-2 bytes转python对象
b = b'\xff\x01'
x = int.from_bytes(b,'big') #高位在前
4-3 c回调python
python使用ctypes,可以使得python被c回调,在官方文档中也有说明,这里记录一下用法:
python部分的代码如下:
#定义回调函数结构,第一个参数代表返回值类型,void就传None。后面几个代表参数类型
@CFUNCTYPE(None,c_int,c_int,POINTER(c_ubyte))
#实现一个回调函数
def eg_info(code,argLen,arg):
print("get code",code)
print("get argLen",argLen)
print("get arg",arg[:argLen])
return
#调用c中的代码将函数指针传给c
adder.setCallback(eg_info)
c对应的代码如下:
//写法1
typedef void (* pyCallback)(int,int,unsigned char *);
pyCallback PYCallback;
extern "C" void setCallback(pyCallback callback){
PYCallback = callback;
}
extern "C" int func(int num1, int num2){
unsigned char arg[3];
arg[0] = 0x10;
arg[1] = 0x01;
arg[2] = 0xa1;
if(PYCallback!=NULL)
PYCallback(1,3,arg);
//do something
}
//写法2
void (* pyCallback)(int,int,unsigned char *);
extern "C" void setCallback(pyCallback callback){
pyCallback = callback;
}
extern "C" int func(int num1, int num2){
unsigned char arg[3];
arg[0] = 0x10;
arg[1] = 0x01;
arg[2] = 0xa1;
pyCallback(1,3,arg);
//do something
}
4-4 python获取c的数组(共享内存)
如4-3所说,当c回调Python的函数时,可以传回数组的指针.
如果采用arg[:argLen], 实际上会把c中的数组拷贝一份出来,如果数组太大,这个过程将变得非常缓慢.
在实操的时候我就碰到这样一种情况,
c返回一张BGR图片给python版的opencv, BGR排序已做好,剩下就是在python中接收,
这时候用
image = np.array(arg[:argLen],dtype = np.uint8).reshape(1080,1920,3)
或者
image = np.frombuffer(bytes(arg[:argLen]),dtype = np.uint8).reshape(1080,1920,3)
都是超级慢的
解决方案:
# arg本身就是一个数组指针类型为c_char_p
# 使用定义一个指向数组的指针
'''
ctypes.cast(obj, type)
This function is similar to the cast operator in C. It returns a new instance of type
which points to the same memory block as obj. type must be a pointer type, and obj must be
an object that can be interpreted as a pointer.
'''
array_pointer = cast(arg, POINTER(c_char*bytes_count))
'''
np.frombuffer将直接使用array_pointer指向的内存,不会使用任何额外的内存
'''
image = np.frombuffer(array_pointer.contents, dtype=np.uint8, count=bytes_count).reshape(1080,1920,3)
4-5 c获取python numpy
反过来,当需要把python中的图片数据传给c的时候,由于python中使用的是numpy,相当于把numpy数组传给c。
这时候需要使用ctypes扩展,numpy对ctypes的支持库np.ctypeslib
使用方法如下:
import numpy as np
import cv2
from ctypes import *
#ctypes加载方式
core = CDLL('./lib/libOpencvWrapper.so')
#numpy ctypeslib加载方式
#OpencvWrapper = np.ctypeslib.load_library("libOpencvWrapper","./lib/")
img = cv2.imread("xxx.jpg")
core.getImageData(np.ctypeslib.as_ctypes(img),img.shape[1],img.shape[0],img.shape[2])
ps: np.ctypeslib.as_ctype方法,官方解释为
Create and return a ctypes object from a numpy array
也就是将numpy数组转换为ctypes对象
4-6 python保存c返回的指针
#需要指定函数的返回类型为POINTER(c_ubyte)
lib.func.restype = POINTER(c_ubyte)
ps:不指定或者指定为c_void_p。会导致从python中传回该指针给C的时候,地址高位丢失,从而变成空指针
例如 : 0x559aca46ab60 会变成 0xca46ab60
4-7 python传结构体给c
4-7-1最简单的结构体指针
有c代码如下:
typedef struct{
float x;
float y;
}WPoint2f;
//方法
func(WPoint2f* point2f);
对应python为
class WPoint2f(Structure):
_fields_ = [
('x', c_float),
('y', c_float)
]
point2f = WPoint2f(1.0,2.0)
#传结构体指针
lib.func(pointer(point2f))
4-7-2 传结构体数组指针给c
typedef struct{
float x;
float y;
}WPoint2f;
//方法
func(WPoint2f point2f[10]);
对应python为
class WPoint2f(Structure):
_fields_ = [
('x', c_float),
('y', c_float)
]
point2f_1 = WPoint2f(1.0,2.0)
point2f_2 = WPoint2f(1.3,2.1)
point2f_list = [point2f_1,point2f_2]
c_point2f_list = (WPoint2f * 2)(*point2f_list)
#传结构体指针
lib.func(pointer(c_point2f_list))
4-7-3 传嵌套结构体指针给c
typedef struct{
float x;
float y;
}WPoint2f;
typedef struct{
WPoint2f center;
}WRect2f;
//方法
func(WRect2f* rect2f);
对应python为
class WPoint2f(Structure):
_fields_ = [
('x', c_float),
('y', c_float)
]
class WRect2f(Structure):
_fields_ = [
('center', WPoint2f)
]
point2f = WPoint2f(1.0,2.0)
rect2f = WRect2f(point2f)
#传结构体指针
lib.func(pointer(rect2f))
4-8 python接收c返回的结构体指针
4-8-1 接收c返回的void *
有时候需要无类型返回,这时候需要在python中定义对应的类型为POINTER(c_ubyte) ,(c_void_p在某些场合下会出错,所以不用它)
比如回调函数中返回结构体指针,类型为void*
class Stru(Structure):
_fields_=[
('num',c_int)
]
@CFUNCTYPE(None,POINTER(c_ubyte))
def callback(stru_ref):
if stru_ref is not None:
#先做类型转换
stru_ref = cast(stru_ref,POINTER(Stru))
stru = stru_ref.contents
num = stru.num
4-8-2 接收c返回的Stru*
这时候将类型直接定义成POINTER(Stru)即可
@CFUNCTYPE(None,POINTER(Stru))
def callback(stru_ref):
if stru_ref is not None:
stru = stru_ref.contents
num = stru.num
4-9 传一个python对象给c,从c回调出来给python使用
其中c并不处理这个对象,仅做传参作用。场景:线程回调
经过试验,可以通过ctypes对内存地址恢复python对象
#传内存地址的指针到c
python_obj_id_p = pointer(c_int64(id(udp_manager)))
libxxx.func(python_obj_id_p)
#从c指针恢复python对象
obj_id = c_int64_p.contents.value
python_obj = cast(obj_id, py_object).value
4 其他问题
1)编译so,如果里面有inline函数,编译动态链接库时可能链接不到
解决办法:将Inline函数移动到头文件中,并用extern修饰
extern inline xxx
第二种方法,将其声明为static