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

Posted Bruceoxl

tags:

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

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

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

静态库和动态库(Windows版)

静态库和动态库(Linux版)

1 Windows创建动态库

1.1生成动态库

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

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

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_API void 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使用
【注2】C++的代码加extern “C”,是为了保证编译时生成的函数名不变,这样动态调用dll时才能找到相应的函数

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

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

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

1.2 dumpbin工具的使用

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

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

dumpbin使用方式:

dumpbin [选项] [文件名]

其中多个选项间用空格分开,多个文件名间也用空格分开,文件名可以为后缀为.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]:此选项显示导入到可执行文件或DLL的DLL列表(静态链接的和延迟加载)和上述每个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]filename:filename为要为其查找匹配.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:在程序图像(例如exe和dll)中转储结构化异常处理(SHE)表的展开描述符。/UNWINDINFO仅适用于IA64图像。

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

dumpbin

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

#dumpbin -exports xxx.dll

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

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

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

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




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

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

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

ctypes官方文档

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

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

1.必须使用ctypes的数据类型。
2.参数类型用关键字argtypes定义,返回类型用restype定义,其中argtypes必须是一个序列,如tuple或list,否则会报错。
3.若没有显式定义参数类型和返回类型,Python默认为int型。Python在调用动态库中的函数时需要指定函数的参数类型和返回值类型。通过Objdll._FuncPtr.restype来指定动态库函数的返回值类型,通过Objdll._FuncPtr.argtypes来指定动态库函数的参数类型,Objdll._FuncPtr.argtypes的类型为turple,包含动态库函数的参数类型列表,指定的参数类型必须为C/C++中参数类型所对应的ctypes类型。

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

import platform
import 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()

运行结果如下:

结果和使用C是一样的。

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


2.1值类型

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

C/C++代码:

class Math {

public:
	int add(int a, int b);

};

int Math::add(int a, int b)
{
	return a + b;
}

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

//C math
Math mathObj;
DLL_API int add(int a, int b)
{
	return mathObj.add(a, b);
}

Python代码:

import platform
import 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)

运行结果如下:

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



2.2指针类型

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

byref相当于C的取地址符号,在参数传递时可以通过byref传递函数的指针。pointer的POINTER的区别是,pointer返回的是一个实例,而POINTER返回的是一个类型。

【例1】
C/C++代码:

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

	return 0;
}

Python代码:

import platform
import 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)

运行结果如下:

【例2】求和
C/C++代码:

//DLL_API int sum(uint32_t nArr[], 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_t i;
	*nSum = 0;

	for (i = 0; i < nLength; i++)
	{
		*nSum += nArr[i];
	}

	return 0;
}

Python代码:

import platform
import ctypes
import 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调用sum函数时,第一个参数需要传递指针,而以下语句返回就是指针的地址:

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

当然还有以下方式:

import platform
import ctypes
import 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)

运行结果如下:

【例3】
C/C++代码:

//sort
DLL_API int bubble_sort(uint32_t *nOldArr, uint32_t nLen, uint32_t *nNewArr)
{
	int i, 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;
			}
		}
	}
	return 0;
}

Python代码:

import platform
import ctypes
import 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])

运行结果如下:

C代码的最后一个参数是一个数组,Python中就需要构建一个数组,将其地址传到C函数中。

2.3结构体类型

【例1】
C/C++代码:

//结构体
typedef struct {
	int nX;
	int nY;
	int nZ;
}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));

	return 0;
}

Python代码:

import platform
import 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)

运行结果如下:

【例2】
C/C++代码:

typedef struct {
	int nMax;
	int nMin;
}STMaxMin;

DLL_API int max_min(int *nArr, uint32_t nLength, STMaxMin *stMaxMin)
{
	if (nArr == nullptr)
	{
		return -1;
	}
	int i;
		
	stMaxMin->nMax = nArr[0];
	stMaxMin->nMin = nArr《Python开发 - Python杂记》Python与C/C++混合编程

《Python开发 - Python杂记》Python项目删除__pycache__文件夹

《Python开发 - Python杂记》Python项目删除__pycache__文件夹

python3.3不自带的模块或工具包 下载以后要怎么处理才能在开发环境中调用 (比如放到pyth

python杂记

学习杂记