Python与C/C++混合编程

Posted 嵌入式实验楼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python与C/C++混合编程相关的知识,希望对你有一定的参考价值。


Python与C/C++混合编程

嵌入式实验楼

看到上面蓝色字了么?关注下吧!


Python是脚本语言,可以做的事情非常的多,本文将要讲解使用Python来调用C/C+库。

关于C/C++库的创建请看笔者以前的文章,这里只给出Window下的创建过程。




Python与C/C++混合编程

1 Windows创建动态库

Python与C/C++混合编程


1.1生成动态库

1.首先创建一个动态库工程。

Python与C/C++混合编程                           

取消预编译头,这里选择DLL

Python与C/C++混合编程


2.创建头文件(.h)以及源文件(.cpp)

DynamicLib.h

#ifndef _DYNAMIC_LIB_H_#define _DYNAMIC_LIB_H_ #include <iostream> #define DLL_EXPORTS #ifdef DLL_EXPORTS#define DLL_API extern"C" __declspec(dllexport)#else#define DLL_API extern"C" __declspec(dllimport)#endif namespace DynamicLib{ DLL_APIvoid print_hello();}#endif // _DYNAMIC_LIB_H_

DynamicLib.cpp

#include"DynamicLib.h" DLL_API void DynamicLib::print_hello(){ std::cout<< "Hello world!" << std::endl;}


【注1__declspec(dllexport)是导出符号,也就是定义需要导出函数的dll中给导出函数的函数声明前面加上导出符号,表示该方法可以导出给其他DLL或者exe使用

【注2C++的代码加extern"C",是为了保证编译时生成的函数名不变,这样动态调用dll时才能。

 

3.选择release生成解决方案,这里选择64位。

Python与C/C++混合编程

最后生成的库如下图所示:

Python与C/C++混合编程

好了,动态库就创建好了。

 

1.2dumpbin工具的使用

dumpbin.exeMicrosoft  COFF二进制文件转换器,它显示有关通用对象文件格式(COFF)二进制文件的信息。可用使用dumpbin检查COFF对象文件、标准COFF对象库、可执行文件和动态链接库等。

dumpbin.exe所在路径是VS安装目录\VC\bin\dumpbin.exe,也可以通过开始菜单里面的Visual Studio开发人员命令提示来运行。

Python与C/C++混合编程

dumpbin使用方式:dumpbin [选项] [文件名]

Python与C/C++混合编程

其中多个选项间用空格分开,多个文件名间也用空格分开,文件名可以为后缀为.obj.lib.dll.exe,如果没有指定任何输入文件,它将列出所有的选项。

选项说明:参数的使用可以用”-”或者”/(-ALL等于/ALL)后面跟选项名。有些选项可以在选项名后接”:”。使用空格或制表符(Tab)分割命令选项。选项名,关键字和文件名是不区分大小写的。大多数的参数可以应用于所有的二进制文件,有少部分参数只能用于特定的文件。

(1)/ALL :此选项显示除代码反汇编外的所有可用信息。使用/DISASM显示反汇编。可以与/ALL一起使用/RAWDATA:NONE来省略文件的原始二进制详细资料。

(2)/ARCHIVEMEMBERS:此选项显示有关库成员对象的最少信息。 (3)/CLRHEADER  file:其中file为用/clr生成的图像文件。CLRHEADER显示有关在任何托管程序中使用的.net头的信息。输出显示.net头及其中各节的位置和大小(以字节计算)

(3)/DIRECTIVES:此选项转储图像中由编译器生成的.directive节。

(4)/DEPENDENTS:转储图像从中导入函数的DLL的名称。不要转储导入函数名。

(5)/DISASM:此选项显示代码段的反汇编,如果出现在文件中则使用符号。

(6)/EXPORTS:此选项显示从可执行文件或DLL导出的所有定义。

(7)/FPO:此选项显示框架指针优化(FPO)记录。

(8)/HEADERS:此选项显示文件头和每节的头。当用于库时,显示每个成员对象的头。

(9)/IMPORTS[:file]:此选项显示导入到可执行文件或DLLDLL列表(静态链接的和延迟加载)和上述每个DLL的各个导入。可选file规范允许指定仅显示某个DLL的导入。

(10)/LINENUMBERS:此选项显示COFF行号。如果对象文件是用程序数据库(/Zi)C7兼容(/Z7)或仅限行号(/Zd)编译的,则它包含行号。如果可执行文件或DLL是与生成调试信息(/DEBUG)链接的,则它包含COFF行号。

(11)/LINKERMEMBER[:{1|2}]:此选项显示库中定义的公共符号。指定参数1将按对象顺序显示符号及其偏移量。指定参数2将显示对象的偏移量和索引号,然后按字母顺序列车这些符号及每个符号的对象索引。若要两个输出都获得,指定不带数字参数的/LINKERMEMBER

(12)/LOADCOMFIG:此选项转储IMAGE_LOAD_CONFIG_DIRECTORY结构,此结构是由WindowsNT加载程序使用并在WIINNT.H中定义的可选结构。

(13)/OUT:filename:此选项指定输出的filename。默认情况下,DUMPBIN将信息显示到标准输出。

(14)/PDBPATH[:VERBOSE]filenamefilename为要为其查找匹配.pdb文件的.dll.exe文件名。VERBOSE(可选)为报告曾尝试在其中定位.pdb文件的所有目录。/PDBPATH将沿调试器搜索.pdb文件的同一路径搜索计算机,并将报告那些.pdb文件(若有)filename中指定的文件相对应。

(15)/RAWDATA[:{1|2|4|8|NONE}[,number]]:此选项显示文件中每节的原始内容。参数说明:1,默认值,内容以十六进制字节显示,如果内容具有打印的表示形式,则还显示为ASCII字符;2,内容显示为十六进制的2字节值;4,内容显示为十六进制的恶4字节值;8,内容显示为十六进制的8字节值;NONE,取消显示原始数据,此参数对控制/ALL输出很有用;number,显示的行被设置为每行具有number个值的宽度。

(16)/RELOCATIONS:此选项显示对象或图像中的任何重定位。

(17)/SECTION:section:此选项限制与指定的section有关的信息的输出。

(18)/SUMMARY:此选项显示有关节的最少信息(包括总大小)。如果未指定其它选项,则此选项为默认值。

(19)/SYMBOLS:此选项显示COFF符号表。符号表存在于所有对象文件中。而对于图像文件,只有当它是与/DEBUG链接时,它才包含COFF符号表。

(20)/UNWINDINFO:在程序图像(例如exedll)中转储结构化异常处理(SHE)表的展开描述符。/UNWINDINFO仅适用于IA64图像。

 

完整的选项可移步至MSDN文档查看

https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-options?view=msvc-160

 

本文主要用dumpbin查看动态库函数:

#dumpbin -exports xxx.dll

Python与C/C++混合编程

从上图可以看出动态库导出了1个函数,与上面代码中的导出函数一致。

值得注意的是,要想导出动态库,就需要加extern"C" 语句,否则C++会按照自己的规则篡改函数的名称。C++支持函数重载,就是在函数名字改编阶段记录下函数的相关参数信息。C++标准并没有定义名字改编的标准,因此会导致不同编译器编译出来的动态库不能通用。

C标准规定了名字改编的标准,extern"C"告诉编译器在编译代码是按照C的标准进行编译。我相信很多做嵌入式的朋友都看到过extern"C"语句。。

另外需要注意,extern"C"修饰的函数进行了重载,则会在编译时报错,因为C语言并不支持函数的重载。


Python与C/C++混合编程

2 Python调用C/C++动态库

Python与C/C++混合编程


Python调用动态库是通过ctypes这个内建的包。ctypes提供了C的兼容数据类型,允许调用DLL或者共享库中的函数。通过该模块能可以使用Python的代码对这些库进行调用,非常方便。

ctypes 适合于“中轻量级”的Python C/C++混合编程。特别是遇到第三方库提供动态链接库和调用文档,且没有编译器或编译器并不互相兼容的场合下,使用ctypes特别方便。值得注意的是,对于某种需求,在Python本身就可以实现的情况下(例如获取系统时间、读写文件等),应该优先使用Python自身的功能而不要使用操作系统提供的API接口,否则你的程序会丧失跨平台的特性。

ctypes官方文档:

https://docs.python.org/3/library/ctypes.html


Python类型和C语言类型的对应关系:

Python与C/C++混合编程


该表格列举了ctypescpython之间基本数据的对应关系,在定义函数的参数和返回值时,需记住几点:

1.必须使用ctypes的数据类型。

2.参数类型用关键字argtypes定义,返回类型用restype定义,其中argtypes必须是一个序列,如tuplelist,否则会报错。

3.若没有显式定义参数类型和返回类型,Python默认为int型。Python在调用动态库中的函数时需要指定函数的参数类型和返回值类型。通过Objdll._FuncPtr.restype来指定动态库函数的返回值类型,通过Objdll._FuncPtr.argtypes来指定动态库函数的参数类型,Objdll._FuncPtr.argtypes的类型为turple,包含动态库函数的参数类型列表,指定的参数类型必须为C/C++中参数类型所对应的ctypes类型。

好了,下面直接看Python调用动态库例子吧。

import platformimport ctypes suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath)  if __name__ == '__main__':  ## 调用print_hello objDll.print_hello()


运行结果如下:

Python与C/C++混合编程

结果和使用C是一样的。

下面以不同的参数类型来讲解如何使用Python代用动态库。

 

2.1值类型

对于参数类型和返回值类型都为值类型的动态库函数,操作相对简单,只需要指定对应的参数和返回值ctype类型即可进行调用。

C/C++代码:

class Math { public: intadd(int a, int b); }; int Math::add(int a, int b){ returna + b;}

C++的函数调用使用需要extern"C"来辅助,也就是说还是只能调用C函数,不能直接调用方法,但是能解析C++方法。不是用extern"C",构建后的动态链接库没有这些函数的符号表。

//C mathMath mathObj;DLL_API int add(int a, int b){ returnmathObj.add(a, b);}

Python代码:

import platformimport ctypes suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll = ctypes.cdll.LoadLibrary(libPath) if __name__ == '__main__':  # 定义函数参数 nA = ctypes.c_int(2) nB = ctypes.c_int(3) # 指定函数返回值类型 objDll.add.restype = ctypes.c_int  # 指定函数的参数类型 objDll.add.argtypes = (ctypes.c_int,ctypes.c_int, )  # 调用函数 res = objDll.add(nA, nB) print('C : sum = ', res)

运行结果如下:

Python与C/C++混合编程

 

为了简单,下面的代码将使用C编写。

 

2.2指针类型

创建ctypes类型的指针需要借助三个相关的函数:

Python与C/C++混合编程


【例1

C/C++代码:

DLL_API int max(uint32_t a,uint32_t b, uint32_t *maxNum){ *maxNum= a > b ? a : b;  return0;}

Python代码:

import platformimport ctypes suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath) if __name__ == '__main__':  # 定义函数参数 nA = ctypes.c_uint32(2) nB = ctypes.c_uint32(3)  nMax = ctypes.c_uint32(0)  # 指定函数返回值类型 objDll.max.restype = ctypes.c_int  # 指定函数的参数类型 objDll.max.argtypes = (ctypes.c_uint32,ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32), )  # 调用函数 res = objDll.max(nA, nB,ctypes.byref(nMax))  maxValue = nMax.value  print('C : max = ', maxValue)

运行结果如下:

Python与C/C++混合编程


【例2】求和

C/C++代码:

//DLL_API int sum(uint32_tnArr[], uint32_t nLength, uint32_t *nSum)DLL_API int sum(uint32_t *nArr,uint32_t nLength, uint32_t *nSum){ if(nArr == nullptr) { return-1; } uint32_ti; *nSum= 0;  for(i = 0; i < nLength; i++) { *nSum+= nArr[i]; }  return0;}

Python代码:

import platformimport ctypesimport numpy as np suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath) if __name__ == '__main__':  data = np.array([[0, 1, 2, 3]],dtype=np.uint32)  # 定义函数参数 nArr= data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32))  nLength = ctypes.c_uint32(4)  nSum = ctypes.c_uint32(0)  # 指定函数返回值类型 objDll.sum.restype = ctypes.c_int  # 指定函数的参数类型 objDll.sum.argtypes = (ctypes.POINTER(ctypes.c_uint32),ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32), )  # 调用函数 res = objDll.sum(nArr, nLength,ctypes.byref(nSum))  sumValue = nSum.value  print('C : sum = ', sumValue)

运行结果如下:

Python与C/C++混合编程

nArr =data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32))

Python与C/C++混合编程


当然还有以下方式:

import platformimport ctypesimport numpy as np suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath)  if __name__ == '__main__':  data = np.array([[0, 1, 2, 3]],dtype=np.uint32)  # 定义函数参数 nArr =data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)).contents  print('nArr : ', nArr) nLength = ctypes.c_uint32(4)  nSum = ctypes.c_uint32(0)  # 指定函数返回值类型 objDll.sum.restype = ctypes.c_int  # 指定函数的参数类型 objDll.sum.argtypes =(ctypes.POINTER(ctypes.c_uint32), ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32),)  # 调用函数 res = objDll.sum(ctypes.byref(nArr),nLength, ctypes.byref(nSum))  sumValue = nSum.value  print('C : sum = ', sumValue)

运行结果如下:

Python与C/C++混合编程

 

【例3

C/C++代码:

//sortDLL_API int bubble_sort(uint32_t*nOldArr, uint32_t nLen, uint32_t *nNewArr){ inti, j, temp;  for(i = 0; i < nLen; i++) { nNewArr[i]= nOldArr[i]; }  for(i = 0; i < nLen - 1; i++) { for(j = 0; j < nLen - 1 - i; j++) { if(nNewArr[j] > nNewArr[j + 1]) { temp= nNewArr[j]; nNewArr[j]= nNewArr[j + 1]; nNewArr[j+ 1] = temp; } } } return0;}

Python代码:

import platformimport ctypesimport numpy as np suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath) if __name__ == '__main__':  data = np.array([[10, 2, 15, 3, 56]],dtype=np.uint32)  # 定义函数参数 nOldArr =data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)).contents  print('nOldArr : ', nOldArr)  nLen = ctypes.c_uint32(5)  nNewArr = (5 * ctypes.c_uint32)()  # 指定函数返回值类型 objDll.bubble_sort.restype = ctypes.c_int  # 指定函数的参数类型 objDll.bubble_sort.argtypes =(ctypes.POINTER(ctypes.c_uint32), ctypes.c_uint32,ctypes.POINTER(ctypes.c_uint32), )  # 调用函数 res =objDll.bubble_sort(ctypes.byref(nOldArr), nLen, nNewArr)  print(nNewArr[0], ' ', nNewArr[1], ' ',nNewArr[2], ' ', nNewArr[3], ' ', nNewArr[4])

运行结果如下:

Python与C/C++混合编程


 


2.3结构体类型

【例1

C/C++代码:

//结构体typedef struct { intnX; intnY; intnZ;}STPoint; DLL_API int sum_square(STPoint*stPoint, uint32_t *nSum){ if(stPoint == nullptr) { return-1; }  *nSum= ((stPoint->nX) * (stPoint->nX) + (stPoint->nY) * (stPoint->nY) +(stPoint->nZ) * (stPoint->nZ));  return0;}

Python代码:

import platformimport ctypes suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath) class STPoint(ctypes.Structure): _fields_ = [("nX", ctypes.c_int),("nY", ctypes.c_int), ("nZ", ctypes.c_int)] if __name__ == '__main__':  stPoint = STPoint()  stPoint.nX = 2 stPoint.nY = 3 stPoint.nZ = 4  nSum = ctypes.c_uint32()  # 指定函数返回值类型 objDll.sum_square.restype = ctypes.c_int  # 指定函数的参数类型 objDll.sum_square.argtypes =(ctypes.POINTER(STPoint), ctypes.POINTER(ctypes.c_uint32), )  # 调用函数 res =objDll.sum_square(ctypes.byref(stPoint), ctypes.byref(nSum))  sumSquare = nSum.value  print('C : sum_square = ',sumSquare)


 

运行结果如下:

Python与C/C++混合编程

 

【例2

C/C++代码:

typedef struct { intnMax; intnMin;}STMaxMin; DLL_API int max_min(int *nArr,uint32_t nLength, STMaxMin *stMaxMin){ if(nArr == nullptr) { return-1; } inti;  stMaxMin->nMax= nArr[0]; stMaxMin->nMin= nArr[0];  for(i = 0; i < nLength; i++) { if(nArr[i] > stMaxMin->nMax) { stMaxMin->nMax= nArr[i]; }  if(nArr[i] < stMaxMin->nMin) { stMaxMin->nMin= nArr[i]; }  }  return0;}


 

Python代码:

import platformimport ctypesimport numpy as np suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath) classSTMaxMin(ctypes.Structure): _fields_ = [("nMax",ctypes.c_int), ("nMin", ctypes.c_int)] if __name__ == '__main__':  data = np.array([[10, 2, 15, 3, 56]],dtype=np.int)  nArr =data.ctypes.data_as(ctypes.POINTER(ctypes.c_int))  nLength = ctypes.c_uint32(5)  stMaxMin = STMaxMin()  # 指定函数返回值类型 objDll.max_min.restype = ctypes.c_int  # 指定函数的参数类型 objDll.max_min.argtypes = (ctypes.POINTER(ctypes.c_int),ctypes.c_uint32, ctypes.POINTER(STMaxMin), )  # 调用函数 res = objDll.max_min(nArr, nLength,ctypes.byref(stMaxMin))   print('C : max = ',stMaxMin.nMax, 'min = ',stMaxMin.nMin )

运行结果如下:

Python与C/C++混合编程

 

Python中调用dll中方法的一般步骤:

1.使用extern c关键字和__declspec(dllexport)以及__declspec(dllimport)dll进行包装。

2.使用ctypes库加载动态库。

3.根据C代码中数据类型和ctypes中类型的对应关系指定动态库函数的返回值类型和参数类型。

4.调用动态库中的函数。

5.对函数的返回结果进行转换成python中的类型进行使用。

 

【注】C语言中没有引用类型,如果动态库是由C++编写存在引用类型参数的函数,需要先用指针类型包装成C动态库。


Python与C/C++混合编程

3 Python与C/C++速度比较

Python与C/C++混合编程


很多朋友都会说,Python调用C库,这不是多此一举嘛,直接用Python写不就好啦,或者直接用C不就好啦。在回答这个问题之前,这里先给一个例子来说明,也就是前面的排序算法。

直接看代码:

# -*- coding: utf-8 -*-"""@file main.py@author BruceOu@version V1.0@date 2021-08-06@blog https://blog.bruceou.cn/@Official Accounts 嵌入式实验楼@brief main"""import platformimport ctypesimport numpy as npimport time suffixNmae = { "Windows":".dll", "Linux":".so"} libPath = './lib/x64/DynamicLib'+ suffixNmae[platform.system()]  def bubbleSort(arr): n = len(arr)  # 遍历所有数组元素 for i in range(n):  # Last i elements are already in place for j in range(0, n-i-1):  if arr[j] > arr[j+1] : arr[j], arr[j+1] = arr[j+1],arr[j] # 加载动态库objDll =ctypes.cdll.LoadLibrary(libPath) isCpp = False if __name__ == '__main__':  arr = np.random.randint(low=1, high=100000,size=10000, dtype=np.uint32)  data = np.array(arr, dtype=np.uint32)  start = time.time()  if(isCpp): length = data.size  # 定义函数参数 nOldArr =data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)).contents  nLen = ctypes.c_uint32(length)  nNewArr = (length * ctypes.c_uint32)()  # 指定函数返回值类型 objDll.bubble_sort.restype =ctypes.c_int  # 指定函数的参数类型 objDll.bubble_sort.argtypes =(ctypes.POINTER(ctypes.c_uint32), ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32),)  # 调用函数 res =objDll.bubble_sort(ctypes.byref(nOldArr), nLen, nNewArr)  print(nNewArr[0], ' ', nNewArr[1], ' ',nNewArr[2], ' ', nNewArr[3], ' ', nNewArr[4])  else: bubbleSort(arr)  print(arr[0], ' ', arr[1], ' ', arr[2],' ', arr[3], ' ', arr[4])  end = time.time()  print('time : ', end- start)


看看两种语言实现的结果:

C[isCpp=True]

Python与C/C++混合编程

以上两次的结果大约在0.0937s,再来看看Python的算法耗时。

Python[isCpp=False]

耗时为24s左右。

可以看到都不是一个数量级的,而且随着计算的复杂度越高。但是为啥又要用Python呢?Python简洁啊,库也多,花费少量时间写代码,运行时间可以通过并行GPU等手段优化,因此当我们提高代码运行速度,又想做一些C不好实现的逻辑代码时就可以使用C写算法,用Python实现逻辑,而且很多Python库都是使用C写的



☆ END ☆


扫描二维码

获取更多精彩

嵌入式实验楼




以上是关于Python与C/C++混合编程的主要内容,如果未能解决你的问题,请参考以下文章

Python与C/C++混合编程

《Python开发 - Python杂记》Python与C/C++混合编程

《Python开发 - Python杂记》Python与C/C++混合编程

你尝试过C语言和Python一起混合编程吗?两者相加岂不是无敌了!

VS2010与Matlab2010b混合编程

混合编程JNI之第一篇 Hello world