开篇
最近两次遇到在Python中调用C/C++的动态链接库,实际调用的是关于RTSP视频流解码的SDK库,过程中踩了不少坑,在此做个记录。其中主要涉及Python中的ctypes模块的使用,怎样定义以及设置动态库中的回调函数。
动态库
C/C++程序可编译成动态链接库提供给用户调用,并且可在windows和linux平台下编译出对应平台使用的库。正好先后使用Python调用了windows和Linux平台的动态库,使用时要确定拿到的是对应平台的库文件。 动态库的加载示例:
# _*_coding:utf-8_*_
import os
import platform
from ctypes import *
linux_dll_name = 'xxx.so'
win_dll_name = 'xxx.dll'
sdk = None
sdk_dll_dir = os.getcwd()
sys_type = platform.system()
if sys_type == "Windows":
sdk = cdll.LoadLibrary(sdk_dll_dir + win_dll_name)
else:
sdk = cdll.LoadLibrary(sdk_dll_dir + linux_dll_name)
说明一下:若使用C++编写和编译动态库时,导出的方法名会发生变化,原因是C++中有方法重载,编译时会添加额外标识。此时需要做声明:extern "C"
Python中的ctypes模块
Python中有个专门用来调用c代码的模块ctypes,ctypes中定义了C语言中的基本变量类型,甚至还考虑了不同平台的差异,指定了所占的长度,如:
- c_int8
- c_int16
- c_int32
- c_int64
普通类变量类型定义
在调用动态库中的C函数时需要注意相应的变量类型都需要使用c_xxx这样的方式进行定义。
结构体的定义(这个很容易出错,需要额外注意)
C代码中的定义
typedef struct _FrameHeader {
u16 Width;
u16 Height;
}FrameHeader;
对应到Python中的定义
class FrameHeader(Structure):
_fields_ = [
('Width', c_uint16),
('Height', c_uint16)
]
关于值的比较
在Python中调用C代码返回值为ctypes类型,如常见的c_int类型,如果需要对该值进行判断,代码如下:
ret = init() #调用动态库中的C函数init(),返回值为c_int
if ret == 123 : #变量值判断,不能用ret == c_int(123)
dosomething()
特殊情况,结构体里面包含自身,类似链表节点的定义
typedef struct _DataFrame {
void* Head;
void* Bmp;
void* H264;
struct _DataFrame* Prev;
}DataFrame;
对应到Python中的定义
class DataFrame(Structure):
pass
DataFrame._fields_ = [
('Head', POINTER(FrameHeader)),
('Bmp', POINTER(BmpData)),
('H264', c_void_p),
('Prev', POINTER(DataFrame))
]
关于指针的表示
ctypes中用pointer和byref对应C语言中的指针,通常使用byref()的地方同样也可用指针函数pointer(),但pointer()作为参数通常会额外创建一个指针对象,如果并不需要再次使用该指针对象的话,使用 byref()会更快。一般情况可参考C代码中的标识,如果遇到&a这样的就用byref, 遇到int *p这样的就用pointer。
C代码内容
#define Handle void*
void function(Handle *handle)
Python代码在实际调用该函数时的写法
handle = c_void_p(0)
init(pointer(handle)) # 此处也可用byref(handle)
需要注意的是pointer和POINTER的区别,POINTER返回类型对象,用来指定函数的参数和返回值的类型用,比如声明一个回调函数:
CALLBACK_FUNC = CFUNCTYPE(c_int32, c_int32, POINTER(DataFrame), c_void_p) #其中DataFrame是一个结构体
怎样编写回调函数
关于回调函数的使用主要有两点:
- 回调函数的定义
- 回调函数的注册
可能遇到的问题有:
回调函数声明时,由于Python的函数不需要指定返回值类型,所以回调函数的定义中,第一个参数为返回值类型,如果返回值为void,则定义为None
Python中回调函数的声明
# 返回值int32
DATA_CALLBACK_FUNC1 = CFUNCTYPE(c_int32, c_int32, POINTER(DataFrame), c_void_p)
# 返回值为void
DATA_CALLBACK_FUNC2 = CFUNCTYPE(None, c_int32, POINTER(DataFrame), c_void_p)
注册回调函数时,需要将回调函赋值给一个变量,然后再进行注册, 否则在运行时会产生段错误,原因是若没有相关的赋值引用,回调函数相当与匿名,会被回收。
cb = CALLBACK_FUNC(frame_callback)
sdk.SetCallback(cb)
回调函数中返回一个指针指向一个buffer,此时需要在Python代码中定义一个buffer
# 下面的6220800是一个图像的buffer大小(1920*1080*3),实际使用时可定义的更大些以满足更高分辨率
# 如果把POINTER(c_char * 6220800) 改为c_char_p 则会报错
CALLBACK_FUNC = CFUNCTYPE(
c_int, c_int, c_void_p, c_int, POINTER(c_char * 6220800),POINTER(FRAME_INFO))
总结
以上就是最近在使用Python调用摄像头的SDK时的一些心得体会,写的不是特别详细,主要 是记录下踩过的一些坑,可能还有些坑还没被踩到,后面遇到时再进一步补充。