python与c混合编程

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值