1 简介
ctypes是一个自Python 2.5开始引入的,Python自带的函数库。其提供了一系列与C、C++语言兼容的数据结构类与方法,可基于由C源代码编译而来的DLL动态链接库文件,进行Python程序与C程序之间的数据交换与相互调用。
2 基于GCC的DLL/SO动态链接库的编译本文使用gcc/g++编译器进行C源代码的编译操作。
当需要使用gcc进行dll动态链接库文件(Linux上为so文件)的编译时,可使用如下命令进行:
>>> gcc -shared –fPIC xxx.c –oxxx.dll
上述命令中,“-shared”与“–fPIC”参数用于指示编译器进行动态链接库的编译,其后接输入文件名,一般为“.c”或“.cpp”结尾的文件。“-o”参数用于指定输出文件名,一般为“.dll”结尾的动态链接库文件。执行完此命令后,给定的输出路径下就会生成一个动态链接库文件,以供Python代码调用。如果将上述命令用于C++源码的编译,则应将gcc编译器换为g++,且输入文件拓展名一般为“.cpp”。且如果当前操作系统使用的是Linux,则动态链接库一般以“.so”作为拓展名。
3 C++源码中的extern声明原理上,Python解释器只可以直接调用C的函数接口,而对于C++的函数接口,则需要通过extern声明转换为C的函数接口,而后才能进行dll编译,以供Python调用。如果不做这样的声明,则将导致Python解释器提示找不到目标函数接口。
如果要将C++的函数接口声明为Python可调用的版本,则需要将文件中所有的函数原型声明放入extern声明的代码块中,例:
extern "C"{
int add(int aNum, int bNum);
}
int add(int aNum, int bNum)
{ ... }
上述代码中,假设我们声明了一个函数定义add,则需要将其函数原型声明放入extern的声明代码块中。extern声明以extern "C"开头,后接一对花括号代码块,其中包含下面所有函数的函数原型声明。
extern中也可略去原型声明,直接在其内部定义函数,这样做的效果类似于内联函数。例:
extern "C"
{
int add(int aNum, int bNum)
{ ... }
}
最后,本节所讨论的extern声明的目的是将C++代码转变为可被Python调用的形式,而对于C语言源码,则无需进行此步骤,直接编译即可。
4
from ctypes import *
当编译好一个dll或so动态库文件后,在Python中就可通过ctypes模块去解析与调用了。解析dll文件可调用CDLL函数,其接受dll文件名作为参数,返回一个解析后的实例对象:
dllObj = CDLL('1.dll')
取得此对象后,即可像调用实例方法那样,以此对象调用dll文件中的各种函数。也就是说,dll文件中的所有函数,在经过CDLL函数解析并返回一个对象后,均可看做是此对象的实例方法,可以以点号的形式进行调用。
例:假设定义了如下C源代码,并已编译为“1.dll”文件:
int addNum(int xNum, int yNum){ return xNum + yNum;}
而后即可在Python中传入参数并调用此函数:
dllObj = CDLL('1.dll')print(dllObj.addNum(1, 2))
输出结果为3。由此可见,当调用CDLL函数解析dll文件,并得到解析对象后,即可像调用实例方法那样调用dll文件中的函数,且函数的参数就是实例方法的参数。
6 函数的输入、输出数据类型首先考虑如下改写的代码:
double addNum(double xNum, double yNum){ return xNum + yNum;}
此代码唯一的改动之处在于将上文的addNum
函数的输入以及输出的数据类型均由int转成了double。此时如果直接编译此文件,并在Python中调用,就会发现程序抛出了ctypes定义的ctypes.ArgumentError
异常,提示参数有错误。出现此问题的原因在于DLL文件无法在调用其中函数时自动设定数据类型,而如果不对类型进行设定,则调用函数时默认的输入、输出类型均为int。故如果函数的参数或返回值包含非int类型时,就需要对函数的参数以及返回值的数据类型进行设定。设定某个函数的参数和返回值的数据类型分别通过设定每个函数的argtypes与restype属性实现。argtypes需要设定为一个tuple,其中依次给出各个参数的数据类型,这里的数据类型均指ctypes中定义的类型。同理,由于C语言函数只能返回一个值,故restype属性就需要指定为单个ctypes类型。对于上文的返回值为double的addNum,代码修改为如下形式即可运行:
dllObj = CDLL('1.dll')
dllObj.addNum.argtypes = (c_double,c_double)
dllObj.addNum.restype = c_double
print(dllObj.addNum(1.1, 2.2))
上述代码在调用addNum之前,分别设定了此函数的输入参数为两个double,返回值也为double,然后以两个小数作为参数调用这个函数,返回值为3.3,结果正确。对于一个返回值类型为char指针的C语言函数,则只需要设定restype为c_char_p,函数即可直接返回Python的str类型至Python代码中。例:设有如下返回字符串指针的C语言函数:char *helloStr(){ return "Hello!";}
则Python的调用代码:
dllObj = CDLL('1.dll')
dllObj.addNum.restype = c_char_p
print(dllObj.helloStr())
上述代码调用了返回字符串指针的helloStr函数,并先行设定返回值类型为c_char_p,则调用后即可直接获得一个Python字符串“Hello!”。
转载:https://www.jianshu.com/p/0d8b43be23c6
转载:https://zhuanlan.zhihu.com/p/20152309
首先c或者c++需要编译成动态链接库,在cmake里面把add_executable改成add_library([project_name] SHARED [xx.cpp])
在写c++代码的时候,由于c++有重载功能,所以编译器在编译的时候是会把函数改名的,这个时候就需要强调需要被python调用的函数是以c的方式编译。例如:
extern "C"
{
float test()
{
printf("hello cpp");
return 1;
}
}
也就是把要调用的代码加上extern “C” 这句非常重要,没他不行,会说找不到函数声明的,因为在编译的时候被改名了。
编译好了之后是个.so文件,在python里面直接引用这个文件就可以了,具体操作为
import ctypes
ll = ctypes.cdll.LoadLibrary
lib = ll("./lib/libtest.so")
这样就加载了动态链接库,lib这个对象就是动态链接库对象了,想用刚才的test函数,直接用lib.test()就可以了。
接下来是传参部分,以opencv为例,传递mat图像,然后返回一个浮点数,
在c++文件里这样写
float test(int height, int width, uchar* frame_data)
{
cv::Mat image(height, width, CV_8UC3);
uchar* pxvec =image.ptr(0);
int count = 0;
for (int row = 0; row < height; row++)
{
pxvec = image.ptr(row);
for(int col = 0; col < width; col++)
{
for(int c = 0; c < 3; c++)
{
pxvec[col*3+c] = frame_data[count];
count++;
}
}
}
float value = 0.2;
return value;
}
这里传入的是uchar*类型,只能通过指针来传递,然后一个一个元素的赋值。
在python里面怎么写呢
import ctypes
import cv2
ll = ctypes.cdll.LoadLibrary
lib = ll("./lib/libtest.so")
lib.test.restype = ctypes.c_float
frame = cv2.imread('test.jpg')
frame_data = np.asarray(frame, dtype=np.uint8)
frame_data = frame_data.ctypes.data_as(ctypes.c_char_p)
value = lib.test(frame.shape[0], frame.shape[1], frame_data)
print value
这里补充了一句restype,如果不加这一句的话返回的是一个指针,python并不能解析出来,只能打印出他的地址,所以在之前生命test这个函数返回类型是float,这样value才是python能用的float类型
ctypes就是把数据转成c语言支持的类型,c_char_p就是char类型的指针,还有其他类型的,比方说c_int,就是int类型,但是没有int指针,只有void指针,具体有哪些可以查一下ctypes,这个很好查的
另外,dtype一定是uint8,不然会默认成uint64,那样在读数据的时候就需要每隔8个读一次了,中间7个都是0,我之前就遇到了这个坑,在代码里面写了个%8来判断的。