提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
目前由于工作原因需在windows环境下利用python调用c语言写的动态链接库。 c/c++代码由于其灵活性,在底层算法中得到广泛使用。许多函数都采用 c/c++语言进行编写,要在其它语言中进行调用需要进行转换。由于此次的函数调用涉及到指针的传入传出,涉及到许多知识点,费了较大工夫才顺利完成,特记录下来留作纪念,以便以后查阅。
一、 ctypes库
ctypes是python的一个外部函数库,提供了c的兼容数据类型,允许调用dll或者共享库的函数。通过ctypes库能以接近c语言的方式在python中完成动态链接库中的调用。
需要注意的是: ctypes只提供了 c的兼容,若要使用c++代码,则需要以c的方式进行编译,即在c++函数声明时加上前缀 "extern C"。
ctypes提供了 c的兼容数据类型,下表列举了两者之间基本数据类型的对应关系。
ctypes数据类型 | c数据类型 |
---|---|
c_char | char |
c_byte | char |
c_ubyte | unsigned char |
c_short | short |
c_int | int |
c_uint | unsigned int |
c_long | long |
c_ulong | unsigned long |
c_longlong | long long |
c_ulonglong | unsigend long long |
c_float | float |
c_double | double |
c_char_p | char* |
c_wchar_p | wchar_t* |
c_void_p | void* |
二、使用步骤
1. c接口函数介绍
动态链接库中的 c函数主要是用于图像处理,接收char*类型字符数组、 float参数、int*指针等参数类型,同时返回int*数组,其函数原型为:
int* code_generate_based_array(const char* msg, int ecLevel, int picWidth, unsigned char* imgData, float coderatio, int* dstWidth)
接口说明:
——入参int* dstWidth会在函数内部进行改变,得到最终图像的宽度
——返回值int*是个整型数组,存储处理完的图像颜色信息,图像的宽度为dstWidth的值,每个int值对应像素的rgb值,即一个int值为r,g,b三通道的结合值
rgb=(int)(((int)r << 16) | (unsigned short)(((unsigned short)g << 8) | b));
2.引入库
在代码中需要处理图像,所以引入了PIL库进行图像处理,同时引入ctypes库进行动态链接库的函数转化及调用,代码如下:
from ctypes import *
from PIL import Image
3.进行接口转换
利用ctypes库将c接口函数表示成python代码。
首先利用CDLL加载动态库:
dll=CDLL("动态链接库路径")
利用ctypes库进行c语言函数的转换:
dll.code_generate_based_array.argtypes=[c_char_p,c_int,c_int,POINTER(c_ubyte),c_float,POINTER(c_int)]
dll.code_generate_based_array.restype=POINTER(c_int)
其中,argtypes定义入参类型, restype定义返回类型。c语言中的指针可通过POINTER进行定义,转换关系为:
—— const char*转化为c_char_p
——int转化为c_int
——unsigned char*转化为POINTER(c_ubyte)
——float转化为c_float
—— int*转化为POINTER(c_int)
将原始图像读取转化为POINTER(c_ubyte)时需先定义一个数组,其代码如下:
def get_img_byte_array(img,width,height):
img=img.convert('RGB')
src_strlist=img.load()
len=width*height*3
result=(c_ubyte*len)()
for i in range(0,height):
for j in range(0,width):
data=src_strlist[j,i]
result[(i*width+j)*3]=data[0]
result[(i*width+j)*3+1]=data[1]
result[(i*width+j)*3+2]=data[2]
return result
这里有一个坑,就是定义数组时的方式,采用以下代码定义的是一维数组
len=width*height*3
result=(c_ubyte*len)()
此时数组长度为width*height*3,此时可采用下标result[i]的方式对数据进行操作;而采用以下代码则定义的是三维数组
result=(c_ubyte*width*height*3)()
需采用result[][][]的方式进行数据操作。
下面将给出接口实际调用的完整代码
from ctypes import *
from PIL import Image
def get_rgb(rgb):
r=(rgb&0xFF0000)>>16;
g=(rgb&0xFF00)>>8;
b=0xFF&rgb;
return (r,g,b)
def get_rgb_matrix(img,width):
return [get_rgb(img[i*width+j]) for i in range(width) for j in range(width)]
'''
get byte array from PIL image
'''
def get_img_byte_array(img,width,height):
img=img.convert('RGB')
src_strlist=img.load()
len=width*height*3
result=(c_ubyte*len)()
for i in range(0,height):
for j in range(0,width):
data=src_strlist[j,i]
result[(i*width+j)*3]=data[0]
result[(i*width+j)*3+1]=data[1]
result[(i*width+j)*3+2]=data[2]
return result
dll=CDLL("动态链接库路径")
img=Image.open(r'G:\pic\other\peo.jpeg')
width=img.size[0]
height=img.size[1]
dll.code_generate_based_array.argtypes=[c_char_p,c_int,c_int,POINTER(c_ubyte),c_float,POINTER(c_int)]
dll.code_generate_based_array.restype=POINTER(c_int)
dstWidth=c_int(0)
result=dll.code_generate_based_array(b'https://www.vrcode.com',3,800,get_img_byte_array(img,width,height),0.5,dstWidth)
img_out=Image.new('RGB',(dstWidth.value,dstWidth.value))
rgbArray=get_rgb_matrix(result, dstWidth.value)
img_out.putdata(rgbArray)
img_out.save('test_out_2.png')
经过测试,上述代码能够完成图像的转换并保存在指定路径,顺利完成接口的调用。
总结
以上就是今天要总结的内容,本文介绍了python调用c语言动态链接库的过程,这个过程中涉及到类型的转换、图像的处理、指针的处理,并且过程中有几个坑,通过探索顺利把坑填平,实现了指定功能,在此分享给需要的朋友。