Python ctypes模块

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<uchar>(0);
  int count = 0;
  for (int row = 0; row < height; row++)
  {
    pxvec = image.ptr<uchar>(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来判断的。

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值