|
ctypes基本数据类型映射表ctypes是Python的外部函数库。提供了与C兼容的数据类型,并允许调用DLL或共享库中的函数。可使用该模块以纯Python形式对这些库进行封装。下面主要介绍如何使用ctypes模块对C语言编译的动态链接库要求的数据类型进行封装,主要包括以下几类:C语言中基础的数据类型(如char,int等)数组类型指针类型结构体类型嵌套结构体结构体数组结构体指针指针数组结构体指针数组ctypes的类型对应如下:ctypestypeCtypePythontypec_bool_Boolbool(1)c_charchar1-characterbytesobjectc_wcharwchar_t1-characterstringc_bytecharintc_ubyteunsignedcharintc_shortshortintc_ushortunsignedshortintc_intintintc_uintunsignedintintc_longlongintc_ulongunsignedlongintc_longlong__int64orlonglongintc_ulonglongunsigned__int64orunsignedlonglongintc_size_tsize_tintc_ssize_tssize_torPy_ssize_tintc_floatfloatfloatc_doubledoublefloatc_longdoublelongdoublefloatc_char_pchar*(NULterminated)bytesobjectorNonec_wchar_pwchar_t*(NULterminated)stringorNonec_void_pvoid*intorNone动态链接库下面是测试用的C语言代码#include#includetypedefstructstudent{charclass;intgrade;longarray[3];int*point;}student_t;typedefstructnest_stu{charrank;student_tnest_stu;student_tstrct_array[2];student_t*strct_point;student_t*strct_point_array[2];}nest_stu_t;typedefstructg_student{intg_grade;}g_stu;g_stug_stu_t={11};inttest_func(charchar_arg,intint_arg,floatfloat_arg,char*stu_buf,char*nest_stu_buf,char*out_buf){//datatypetestprintf("chararg:%c\n",char_arg);printf("intarg:%d\n",int_arg);printf("floatarg:%f\n",float_arg);student_t*stu_p=NULL;nest_stu_t*nest_stu_p=NULL;stu_p=(student_t*)stu_buf;nest_stu_p=(nest_stu_t*)nest_stu_buf;//structtypetestprintf("structclass:%c\n",stu_p->class);printf("structgrade:%d\n",stu_p->grade);printf("structarray[0]:%darray[1]:%d\n",stu_p->array[0],stu_p->array[1]);printf("structpoint:%d\n",*(stu_p->point));//neststructtestprintf("neststructrank:%d\n",nest_stu_p->rank);printf("neststructstugrade:%d\n",nest_stu_p->nest_stu.grade);//structarrayprintf("neststructarray[0]grade:%d\n",nest_stu_p->strct_array[0].grade);printf("neststructarray[1]grade:%d\n",nest_stu_p->strct_array[1].grade);//structpointprintf("neststructpointgrade:%d\n",nest_stu_p->strct_point->grade);//structpointarrayprintf("neststructpointarray[0]grade:%d\n",nest_stu_p->strct_point_array[0]->grade);printf("neststructpointarray[1]grade:%d\n",nest_stu_p->strct_point_array[1]->grade);//outbuftestmemcpy(out_buf,stu_p,sizeof(int)*2);return1;}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061编译命令gcc-Wall-g-fPIC-shared-olibstruct.so.0ctype_code.csetsrc=…\src\tab_calc.c…/src/shifting_calc.cgcc-ocalc.so--share-fPIC%src%基础数据类型#-*-coding:utf-8-*-fromctypesimport*#字符,仅接受onecharacterbytes,bytearrayorintegerchar_type=c_char(b"a")byte_type=c_char(1)#字节string_type=c_wchar_p("abc")#字符串int_type=c_int(2)#整型#直接打印输出的是对象信息,获取值需要使用value方法print(char_type,byte_type,int_type)print(char_type.value,byte_type.value,string_type.value,int_type.value)print(c_int())#c_long(0)print(c_wchar_p("Hello,World"))#c_wchar_p(140018365411392)print(c_ushort(-3))#c_ushort(65533)123456789101112131415161718当给指针类型的对象c_char_p、c_wchar_p和c_void_p等赋值时,将改变它们所指向的内存地址,而不是它们所指向的内存区域的内容(这是因为Python的bytes对象是不可变的):fromctypesimport*s="Hello,World"c_s=c_wchar_p(s)print(c_s)#c_wchar_p(139966785747344)print(c_s.value)#HelloWorldc_s.value="Hi,there"print(c_s)#内存地址已经改变c_wchar_p(139966783348904)print(c_s.value)#Hi,thereprint(s)#第一个对象没有改变Hello,World12345678910但要注意不能将它们传递给会改变指针所指内存的函数。如果你需要可改变的内存块,ctypes提供了create_string_buffer()函数,它提供多种方式创建这种内存块。当前的内存块内容可以通过raw属性存取,如果你希望将它作为NUL结束的字符串,请使用value属性fromctypesimport*p=create_string_buffer(3)print(sizeof(p),repr(p.raw))p=create_string_buffer(b"Hello")print(sizeof(p),repr(p.raw))print(repr(p.value))p=create_string_buffer(b"Hello",10)print(sizeof(p),repr(p.raw))p.value=b"Hi"print(sizeof(p),repr(p.raw))12345678910111213数组类型数组的创建和C语言的类似,给定数据类型和长度即可,如下fromctypesimport*#数组#定义类型char_array=c_char*3#初始化char_array_obj=char_array(b"a",b"b",2)#打印只能打印数组对象的信息print(char_array_obj)#打印值通过value方法print(char_array_obj.value)1234567891011fromctypesimport*int_array=(c_int*3)(1,2,3)foriinint_array:print(i)char_array_2=(c_char*3)(1,2,3)print(char_array_2.value)12345678这里需要注意,通过value方法获取值只适用于字符数组,其他类型如print(int_array.value)的使用会报错。5个0的整形数组:(c_int*5)()前三个数为1-3,后续全为0的10长度浮点型数组:(c_double*10)(1,2,3)对于Python而言,数字类型的数组是一个可迭代对象,其可通过for循环、next方法等方式进行迭代,以获取其中的每一个值。例:foriin(c_double*10)(1,2,3):print(i)输出结果为1.0、2.0、3.0以及后续的7个0.0。12345678910111213数组对象在数据类型上可看作是指针,且指针变量在ctypes中就等同于int类型,故所有涉及到指针传递的地方,均无需考虑修改argtypes属性的问题。直接以默认的int类型传入即可多维数组多维数组与一维数组在语法上大体类似,但在字符串数组上略有不同。这里讨论数字类型的高维数组。此外,为简单起见,都是对二维数组进行讨论,更高维度的数组在语法上与二维数组是相似的,不再赘述。高维数组类可简单的通过在一维数组类外部再乘上一个数字实现(c_int*4)*3这样就得到了一个所有值均为0的二维数组对象。又例:只实例化了第一个一维数组的全部以及第二个一维数组的前两个值,而其他所有值均为0。((c_int*4)*3)((1,2,3,4),(5,6))二维数组在使用时与一维数组一致,其可直接作为指针参数传入C的函数接口进行访问,在C语言内部其等同于C语言中声明的二维数组。而对于Python,这样的数组对象可通过双层的for循环去迭代获取每个数值字符串数组字符串数组在ctypes中的行为更接近于C语言中的字符串数组,其需要采用二维数组的形式来实现,而不是Python中的一维数组。首先,需要通过c_char类型乘上一个数,得到一个字符串类型,而后将此类型再乘上一个数,就能得到可以包含多个字符串的字符串数组。例:实例化了一个3字符串数组,每个字符串最大长度为10。((c_char*10)*3)()对于C语言而言,上述的字符串数组实例可直接当做字符串指针传入C函数,其行为等同于在C中声明的char(*)[10]指针。下详细讨论Python中对此对象的处理。首先,字符串数组也是可迭代对象,可通过for循环迭代取值,对于上例的对象,其for循环得到的每一个值,都是一个10个长度的字符串对象。这样的字符串对象有两个重要属性:value和raw。value属性得到是普通字符串,即忽略了字符串终止符号(即C中的\0)以后的所有内容的字符串,而raw字符串得到的是当前对象的全部字符集合,包括终止符号。也就是说,对于10个长度的字符串对象,其raw的结果就一定是一个10个长度的字符串。例:foriin((c_char*10)*3)():print(i.value)print(i.raw)上述代码中,i.value的输出全为空字符串(b’’),而对于i.raw,其输出则为b’\x00\x00…’,总共10个\x00。也就是说,value会忽略字符串终止符号后的所有字符,是最常用的取值方式,raw得到不忽略终止字符的字符串。create_string_buffer接下来讨论ctypes中对字符串对象的赋值方法。由于ctypes的字符串对象通过某个固定长度的字符串类实例化得到,故在赋值时,这样的字符串对象只可以接受等同于其声明长度的字符串对象作为替代值,这是普通Python字符串做不到的。要得到这样的定长字符串,需要用到ctypes的create_string_buffer函数。ctypes提供了create_string_buffer()函数创建一定长度的内存区域。当前的内存块内容可以通过raw属性存取,如果是创建NULL结束的字符串,使用value属性获取内存块的值#-*-coding:utf-8-*-fromctypesimport*p=create_string_buffer(b"hello",10)#createa10bytebufferprint(sizeof(p),repr(p.raw))#快速创建内存区域的方法p=create_string_buffer(3)#createa3bytebuffer,initializedtoNULbytesprint(sizeof(p),repr(p.raw))#3b'\x00\x00\x00'#createabuffercontainingaNULterminatedstringp=create_string_buffer(b"Hello")print(sizeof(p),repr(p.raw))#6b'Hello\x00'print(repr(p.value))#b'Hello'p=create_string_buffer(b"Hello",10)#createa10bytebufferprint(sizeof(p),repr(p.raw))#10b'Hello\x00\x00\x00\x00\x00'p.value=b"Hi"print(sizeof(p),repr(p.raw))#10b'Hi\x00lo\x00\x00\x00\x00\x00'1234567891011121314151617181920create_string_buffer函数用于创建固定长度的带缓冲字符串。其接受两个参数,第一参数:字节字符串。必须是字节类型的字符串第二参数:目标长度,返回值:为被创建的定长度字符串对象,可以赋值给字符串数组中的某个对象。在Python2中,普通的字符串就是字节字符串,在Python3中,所有的字符串默认为Unicode字符串,故可以通过字符串的encode()、decode()方法进行编码方式的转化。fromctypesimport*charList=((c_char*10)*3)()strList=['aaa','bbb','ccc']foriinrange(3):charList[i]=create_string_buffer(strList[i].encode(),10)foriincharList:print(i.value)或者这样写:fromctypesimport*12345678910111213生成一个长度为10的前面部分是abcdefg,后面用NULL补齐的字符数组。temp_string=create_string_buffer(b"abcdefg",10)print(temp_string)print(temp_string.value)print(temp_string.raw)注意,通过create_string_buffe12345注意,通过create_string_buffer函数创建的字符串对象,其长度必须严格等同于被赋值的字符串对象的声明长度,即如果声明的是10长度字符串,那么create_string_buffer的第二参数就必须也是10,否则代码将抛出TypeError异常,提示出现了类型不一致在字符串数组的初始化过程中,这样的字符串对象也可作为初始化的参数。例:fromctypesimport*strList=['aaa','bbb','ccc']charList=((c_char*10)*3)(*[create_string_buffer(i.encode(),10)foriinstrList])foriincharList:print(i.value.decode())1234567上述代码将实例化与初始化合并,通过列表推导式得到了3个10长度的缓冲字符串,并使用星号展开,作为实例化的参数。则这样得到的charList效果等同于上例中通过依次赋值得到的字符串数组对象。最后通过for循环输出字符串对象的value属性(一个bytes字符串),且通过decode方法将bytes转化为str。参数的引用传递(byref)有时候C函数接口可能由于要往某个地址写入值,或者数据太大不适合作为值传递,从而希望接收一个指针作为数据参数类型。这和传递参数引用类似。ctypes暴露了byref()函数用于通过引用传递参数,使用pointer()函数也能达到同样的效果,只不过pointer()需要更多步骤,因为它要先构造一个真实指针对象。所以在Python代码本身不需要使用这个指针对象的情况下,使用byref()效率更高。byref不会构造一个指针对象,因此速度比pointer快。(byref()只是用来传递引用参数)。其_obj是只读属性,不能更改。指针和引用是非常常用的(特别在C中)byref(temp_var)temp_var的引用pointer(i)temp_var的指针其中如果不是必须使用指针的话,引用会更快。>>>i=c_int()>>>f=c_float()>>>s=create_string_buffer(b'\000'*32)>>>print(i.value,f.value,repr(s.value))00.0b''>>>libc.sscanf(b"13.14Hello",b"%d%f%s",...byref(i),byref(f),s)3>>>print(i.value,f.value,repr(s.value))13.1400001049b'Hello'>>>1234567891011指针类型(POINTER())POINTER()用于定义某个类型的指针。POINTER返回类型对象。其实就是:POINTER就是用来定义指针类型。使用POINTER()创建指针总共2步操作:首先使用POINTER来定义指针类型,然后再通过指针类型去创建指针,fromctypesimport*#POINTER(c_int)相当于C/C++中int*#POINTER(c_double)相当于C/C++中double*#POINTER(c_float)相当于C/C++中float*int_ptr=POINTER(c_int)int_99=c_int(99)ptr=int_ptr(int_99)#相当于C/C++中int*ptr=&int_99print(ptr)print(ptr[0])print(ptr.contents)print(ptr.contents.value)##99#c_long(99)#99123456789101112131415161718POINTER()常用的作用:给argtypes指定函数的参数类型、给restype指定返回值类型。ctypes.POINTER(ctypes.c_float)==type(ptr)#Trueptr的类型可通过POINTER获得如果不指定,默认的类型都将被当做是整形。实参类型(除了None,integer,stringbytes,可隐式转换)和返回值的类型用ctypes.c_系列类型显示的指定。(见ctypes文档:None,integers,bytesobjectsand(unicode)stringsaretheonlynativePythonobjectsthatcandirectlybeusedasparametersinthesefunctioncalls.)。实际上,最终得到的返回结果的类型已经由显示指定的ctypes.c_类型,转化为了对应的python类型。比如指定.restype=c_char_p,函数的返回值的类型将是bytes类型。根据ctypes官方文档的描述:ctypes实例由一个内存块和若干描述符构成,描述符用来访问内存块。内存块并不保存python对象,而仅仅保存python对象的内容。外部访问保存的内容时,都会构造一个新的python对象指针(pointer())可以将ctypes类型数据传入pointer()函数创建指针。pointer()函数内部其实还是通过POINTER()函数实现的。pointer()简化了步骤,使用更方便。pointer用来获取一个变量的指针,相当于C里面的&。pointer()用于将对象转化为指针。pointer会创建一个实际的指针对象,当不需要实际指针对象时可以使用byref()。.contents属性即其所指的对象,但是指向是能够变动的。给ptr.contents赋值则可以更改指针的指向,但是指针类型必须匹配。ctypes.addressof返回C实例的地址。pointer的用法需要注意的是,必须在ctypes类型上使用,不能在python类型上使用。所有ctypes类型的实例都包含一个存放C兼容数据的内存块;该内存块的地址可由addressof()辅助函数返回。还有一个实例变量被公开为_objects;此变量包含其他在内存块包含指针的情况下需要保持存活的Python对象。fromctypesimport*i=c_int(42)print(i)pi=pointer(i)print(pi)#指针实例拥有contents属性,它返回指针指向的真实对象,如上面的i对象。print(pi.contents)#ctypes并没有OOR(返回原始对象),每次访问这个属性时都会构造返回一个新的相同对象print(pi.contentsisi)print(pi.contentsispi.contents)i=c_int(99)#指针的contents属性赋值为另一个c_int实例将会导致该指针指向该实例的内存地址:pi.contents=iprint(pi.contents)#指针对象也可以通过整数下标进行访问print(pi[0])#通过整数下标赋值可以改变指针所指向的真实内容print(i)pi[0]=22print(i)1234567891011121314151617181920212223242526fromctypesimport*#指针类型int_obj=c_int(3)int_p=pointer(int_obj)print(int_p)#使用contents方法访问指针print(int_p.contents)#获取指针指向的值print(int_p[0])temp_var=c_float(12345.789)print(temp_var)print(temp_var.value)print('*****************************************')ptr=pointer(temp_var)print(ptr)print(ptr.contents)#指针的指向print(ptr.contents.value)#指针所指向的内容print('*****************************************')print(hex(addressof(ptr.contents)))print((hex(addressof(temp_var))))print('*****************************************')ptr.contents=c_float(321.321)#改变指针的指向print(ptr.contents.value)print(temp_var.value)print('*****************************************')temp_var_2=c_float(55.55)print(temp_var_2)ptr_2=pointer(temp_var_2)ptr_2.contents.value=99.99#改变指针所指向的内容print(temp_var_2)123456789101112131415161718192021222324252627282930313233deffunc_print():#字符串格式化num=int(input('请输入一个十进制整数'))#将str类型转换成int类型print(num,'的二进制为:',bin(num))#个数可变的位置参数法print(str(num)+'的二进制数为:',bin(num))#使用+作为连接符print('%s的二进制数为:%s'%(num,bin(num)))#格式化字符串法print('{0}的二进制数为:{1}'.format(num,bin(num)))#格式化字符串print(f'{num}的二进制数为:{bin(num)}')#格式化字符串法print('{0}的八进制数为:{1}'.format(num,oct(num)))print(f'{num}的十六进制数为:{hex(num)}')12345678910fromctypesimport*ct_arr_ptr=pointer((c_int*5)(1,2,3,4,5))#这是ct_arr_ptr对象的地址print(ct_arr_ptr)##这是ct_arr_ptr所指向的内容在内存中的真实地址print(hex(addressof(ct_arr_ptr.contents)))#这是contents对象的地址,每次都临时生成新的,但是都引用上面那个不变的内存里的东西.print(ct_arr_ptr.contents)123456789101112创建空指针的方式不带参数的调用指针类型创建一个NULL指针,NULL指针有一个False布尔值fromctypesimport*null_ptr=POINTER(c_int)()print(bool(null_ptr))1234指针类型的转换ctypes提供cast()方法将一个ctypes实例转换为指向另一个ctypes数据类型的指针。cast()接受两个参数参数1是ctypes对象,它是或可以转换成某种类型的指针,参数2是ctypes指针类型。它返回第二个参数的一个实例,该实例引用与第一个参数相同的内存块。fromctypesimport*int_p=pointer(c_int(5))print(int_p)cast_type=cast(int_p,POINTER(c_char))print(cast_type)print(cast_type.contents)print(cast_type.contents.value)123456789函数指针CFUNCTYPE用来定义ctype的函数指针,指向python函数,实际是传给c语言调用的。可以看到python和c的相互调用方式了:python里loadlibrary方式调用c;python提供FUNCTYPE的python回调给c语言调用;libc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')qsort=libc.qsortqsort.restype>>CMPFUNCP=CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))type(CMPFUNCP)>>defpython_cmp(a,b):print('cmpinpython')returna[0]-b[0]ia=(c_int*10)(1,3,5,7,9,2,4,6,8,10)qsort(id,len(ia),sizeof(c_int),CMPFUNCP(python_cmp()))ia12345678910#更简单的写法是用decoratorCFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))defpython_cmp(a,b):print('%s:%s',a[0],b[0])returna[0]-b[0]qsort(ia,len(ia),sizeof(c_int),python_cmp)1234567891011121314151617181920FUNCTYPE和PFUNCTYPE的区别?FUNCTYPE是封装的python函数是给c语言调用的,它将不受GIL影响,纯c的。而PFUNCTYPE封装的python函数仍然受GIL影响,这个区别还是很大的.c希望调用的python不要卡在GIL里的话,用FUNCTYPE;如果有些GIL操作再c里不可忽略,用PFUNCTYPE.其他的指针类型基本类型中只包含了c_char_p和c_void_p两个指针类型,其他的指针类型该如何使用?数组该如何定义和使用?我们来看看这两个类型的使用。//sum_module.c#includeintsum(inta[],size_tlen){intret=0;for(size_ti=0;i#lib.sum([1,2,3],3)#ctypes.ArgumentError:argument1:on'tknowhowtoconvertparameter112345678910会发现ctypes报错了,不知道类型如何进行转换,也就是说ctypes的隐式转换是不支持数组类型的。我们需要用ctypes的数组来传参数。importctypeslib=ctypes.cdll.LoadLibrary("sum_module.so")array=(ctypes.c_int*3)(1,2,3)printlib.sum(array,len(array))i=ctypes.c_int(5)printlib.sum(i,1)123456789ctypes的数组定义就是用ctypes中的类型*大小。指针的用法importctypeslib=ctypes.cdll.LoadLibrary("sum_module.so")i=ctypes.c_int(5)lib.sum2.argtypes=(ctypes.POINTER(ctypes.c_int),ctypes.c_size_t)printlib.sum2(ctypes.pointer(i),1)1234567importctypesi=ctypes.c_int(5)printctypes.pointer(i)#i=5printctypes.pointer(i)#TypeError:_type_musthavestorageinfo1234567对于单个字符串,其不需要通过指针指针转换即可当做指针传递voidprintStr(char*str){printf("%s",str);}1234则Python中:dllObj=CDLL(‘a.dll’)dllObj.printStr(‘Hello!’)由此可见,对于单个字符串传进dll,则直接通过字符串传递即可,传递过程中字符串会自动被转化为指针。而对于返回单个字符串的C函数,需要通过修改restype属性为c_char_p后,即可在Python中直接接收字符串返回值。还有传递char*指针参数,然后回去改变值,需要定义变量传递C部分voidmodifyStr(char*str){//使用strcpy将1233赋值给strstrcpy(str,”1233”);}12345Python部分fromctypesimport*#将python变量转换成char*变量str_data=""cdata=c_char_p(str(str_data).encode())dllObj=CDLL('a.dll')dllObj.modifyStr(cdata)#将char*数据转换成python变量str_data=str(cdata.value,encoding="utf-8")print("str_data",str_data)123456789101112对于单个数值的指针,则需要通过byref或者pointer函数取得。首先考虑如下函数:voidswapNum(int*a,int*b){inttemp=*a;*a=*b;*b=temp;}此函数接收两个int类型指针,并在函数内部交换指针所在位置的整形值。此时如果通过Python传入这两个指针参数,就需要使用到byref或者pointer函数。byref函数类似于C语言中的取地址符号&,其直接返回当前参数的地址,而pointer函数更为高级,其返回一个POINTER指针类型,一般来说,如果不需要对指针进行其他额外处理,推荐直接调用byref函数获取指针,这是较pointer更加快速的方法。此外,这两个函数的参数都必须是ctypes类型值,可通过ctypes类型实例化得到,不可直接使用Python内部的数值类型。例:fromctypesimport*dllObj=CDLL(‘a.dll’)a,b=c_int(1),c_int(2)dllObj.swapNum(byref(a),byref(b))以上代码首先通过c_int类型实例化得到了两个c_int实例,其值分别为1和2。然后调用上文中的swapNum函数,传入的实际参数为byref函数的返回指针。这样就相当于在C语言中进行形如swapNum(&a,&b)的调用。要将c_int类型再转回Python类型,可以访问实例对象的value属性:print(a.value,b.value)对于pointer函数,其同样接受一个ctypes实例作为参数,并返回一个POINTER指针类型,而不是简单的一个指针。POINTER指针类型在传参时也可直接作为指针传入C语言函数,但在Python中,其需要先访问contents属性,得到指针指向的数据,其一般为ctypes类型的实例,然后再访问value属性,得到实例所对应的Python类型的数据。例:fromctypesimport*dllObj=CDLL(‘a.dll’)a,b=pointer(c_int(1)),pointer(c_int(2))print(a.contents.value,b.contents.value)dllObj.swapNum(a,b)print(a.contents.value,b.contents.value)函数“进/出参数”的定义python并不会直接读取到dll的源文件,所以python不仅看不到函数要什么,也看不到函数返回什么。,那么如何告诉python,函数需要什么参数、返回什么类型的值?答案是用argtypes和restypeargtypes:函数的参数类型。restype:函数的返回值类型。默认情况下python认为函数返回了一个C中的int类型。如果函数返回别的类型,就需要用到restype命令。示例:fuction.argtypes=[c_char_p,c_int]只指定了参数类型,没有指定返回类型,所以返回类型默认是intfunction.restype=c_char_p指定fuction这个函数的返回值是c_char_p如果需要返回非int的类型就需要进行指定。指定参数类型的好处在于,ctypes可以处理指针的转换,无需代码中进行转换。继续使用上面sum2函数为例i=ctypes.c_int(5)lib.sum2.argtypes=(ctypes.POINTER(ctypes.c_int),ctypes.c_size_t)printlib.sum2(ctypes.pointer(i),1)printlib.sum2(i,1)可以使用pointer(i)和i作为sum2的第一个参数,会自动处理是否为指针的情况。示例:调用了strchr函数,这个函数接收一个字符串指针以及一个字符作为参数,返回另一个字符串指针。>>>strchr=libc.strchr>>>strchr(b"abcdef",ord("d"))8059983>>>strchr.restype=c_char_p#c_char_pisapointertoastring>>>strchr(b"abcdef",ord("d"))b'def'>>>print(strchr(b"abcdef",ord("x")))None>>>123456789如果希望避免上述的ord(“x”)调用,可以设置argtypes属性,第二个参数就会将单字符的Python二进制字符对象转换为C字符:>>>strchr.restype=c_char_p>>>strchr.argtypes=[c_char_p,c_char]>>>strchr(b"abcdef",b"d")'def'>>>strchr(b"abcdef",b"def")Traceback(mostrecentcalllast):File"",line1,inArgumentError:argument2:exceptions.TypeErrornecharacterstringexpected>>>print(strchr(b"abcdef",b"x"))None>>>strchr(b"abcdef",b"d")'def'>>>12345678910111213如果外部函数返回了一个整数,你也可以使用要给可调用的Python对象(比如函数或者类)作为restype属性的值。将会以C函数返回的整数对象作为参数调用这个可调用对象,执行后的结果作为最终函数返回值。这在错误返回值校验和自动抛出异常等方面比较有用。>>>GetModuleHandle=windll.kernel32.GetModuleHandleA>>>defValidHandle(value):...ifvalue==0:...raiseWinError()...returnvalue...>>>>>>GetModuleHandle.restype=ValidHandle>>>GetModuleHandle(None)486539264>>>GetModuleHandle("somethingsilly")Traceback(mostrecentcalllast):File"",line1,inFile"",line3,inValidHandleOSError:[Errno126]Thespecifiedmodulecouldnotbefound.WinError函数可以调用Windows的FormatMessage()API获取错误码的字符串说明,然后返回一个异常。WinError接收一个可选的错误码作为参数,如果没有的话,它将调用GetLastError()获取错误码。12345678910111213141516请注意,使用errcheck属性可以实现更强大的错误检查手段;详情请见参考手册。结构体、联合结构体类型的实现:“结构体”和“联合”必须继承自ctypes模块中的Structure和Union。子类必须定义fields属性。fields是一个二元组列表,二元组中包含fieldname和fieldtype。type字段必须是一个ctypes类型,比如c_int,或者其他ctypes类型:结构体、联合、数组、指针。pack属性决定结构体的字节对齐方式,默认是4字节对齐,创建时使用_pack_=1可以指定1字节对齐。注意,在Python中定义的结构体,其变量名、类名等均可以不同于C语言中的变量名,但结构体变量的数量、数据类型与顺序必须严格对应于C源码中的定义,否则可能将导致内存访问出错。示例1:一个简单的TestStruct结构体,它包含名称为x和y的两个变量,还展示了如何通过构造函数初始化结构体。fromctypesimport*classTestStruct(Structure):fields=((‘x’,c_int),(‘y’,c_double),)以上代码定义了一个结构体类型,其等同于C中的struct声明。此结构体定义了两个结构体变量:x对应于一个int类型,y对应于一个double类型。结构体类型可以通过实例化得到一个结构对象,在实例化的同时也可传入初始化参数,作为结构变量的值。在得到结构对象后,也可通过点号访问结构体成员变量,从而对其赋值。例:fromctypesimport*classTestStruct(Structure):_fields_=(('x',c_int),('y',c_double),)test_struct=TestStruct(1,2)print(test_struct.x,test_struct.y)test_struct.x,test_struct.y=10,20print(test_struct.x,test_struct.y)123456789101112131415上述代码通过TestStruct(1,2)得到一个结构体实例test_struct,第一次输出结果即为12.0。而后,再通过属性访问的方式修改了结构体中的两个变量,则第二次输出结果为1020.0。上面定义的结构体可直接传入C代码中,且上文已经提到,两边定义的结构体变量的各种名称均可不同,但数据类型、数量与顺序必须一致。例:structTestStruct{inta;doubleb;}extern"C"{voidprintStruct(TestStructtestStruct);}voidprintStruct(TestStructtestStruct){printf("%d%f\n",testStruct.a,testStruct.b);}Python部分:fromctypesimport*dllObj=CDLL('1.dll')classTestStruct(Structure):_fields_=(('x',c_int),('y',c_double),)test_struct=TestStruct(1,2)dllObj.printStruct(test_struct)1234567891011121314151617181920212223242526272829由此可见,在Python中实例化得到的结构体实例,可以直接当做C中的结构体实参传入。结构体也可以指针的方式传入,通过byref或者pointer函数即可实现。同样的,这两个函数都可直接接受结构体实例作为参数进行转化,byref返回简单指针,而pointer返回指针对象,可访问其contents属性得到指针所指向的值。例:C部分,上述printStruct函数修改为接受结构体指针的版本:voidprintStruct(TestStruct*testStruct){printf("%d%f\n",testStruct->a,testStruct->b);}Python部分:fromctypesimport*dllObj=CDLL('1.dll')classTestStruct(Structure):_fields_=(('x',c_int),('y',c_double),)test_struct=TestStruct(1,2)dllObj.printStruct(byref(test_struct))123456789101112131415161718上述代码将结构体对象testStruct作为byref的参数,从而将其转换为指针传入printStruct函数中。示例:fromctypesimport*dllObj=CDLL(‘1.dll’)classTestStruct(Structure):fields=((‘x’,c_int),(‘y’,c_double),)test_struct=pointer(TestStruct(1,2))dllObj.printStruct(test_struct)print(test_struct.contents.x,test_struct.contents.y)上述代码通过结构体对象生成了一个指针类型,并将此指针传入函数,可达到同样的效果。且在Python内部,结构体指针类型可以访问其contents属性,得到指针所指向的结构体,然后可继续访问结构体的x与y属性,得到结构体中保存的值。另外,如结构体用于链表操作,即包含指向结构体指针时,若直接定义:fromctypesimport*importtypesclassTestStruct(Structure):fields=((‘x’,c_int),(‘y’,c_double),(‘next’,TestStruct))则python会报错type未定义,这时就需要POINTERfromctypesimport*importtypesclassTestStruct(Structure):passTest._fields_=[('x',c_int),('y',c_char),('next',POINTER(TestStruct))]示例2:fromctypesimport*12345678910111213141516学生信息如下stu_info=[(“class”,“A”),(“grade”,90),(“array”,[1,2,3]),(“point”,4)]创建结构体类classStudent(Structure):fields=[(“class”,c_char),(“grade”,c_int),(“array”,c_long*3),(“point”,POINTER(c_int))]print("sizeofStudent:",sizeof(Student))实例化long_array=c_long*3long_array_obj=long_array(1,2,3)int_p=pointer(c_int(4))stu_info_value=[c_char(b"A"),c_int(90),long_array_obj,int_p]stu_obj=Student(*stu_info_value)123456这样打印报错,因为字段名和python关键字class重名了,这是需要特别注意的点print(“stuinfo:”,stu_obj.class,stu_obj.grade,stu_obj.array[0],stu_obj.point[0])print(“stuinfo:”,stu_obj.grade,stu_obj.array[0],stu_obj.point[0])输出:sizeofStudent:40stuinfo:9014如果把_pack_改为1,则输出:sizeofStudent:37stuinfo:9014嵌套结构体嵌套结构体的使用需要创建基础结构体的类型,然后将基础结构体的类型作为嵌套结构体的成员,注意基础结构体所属字段的字段类型是基础结构体的类名,如下:#-*-coding:utf-8-*-fromctypesimport*#创建结构体类classStudent(Structure):_fields_=[("class",c_char),("grade",c_int),("array",c_long*3),("point",POINTER(c_int))]#创建类型,nest_stu字段的类型为基础结构体的类名classNestStudent(Structure):_fields_=[("rank",c_char),("nest_stu",Student)]#实例化long_array=c_long*3long_array_obj=long_array(1,2,3)int_p=pointer(c_int(4))stu_info_value=[c_char(b"A"),c_int(90),long_array_obj,int_p]stu_obj=Student(*stu_info_value)#实例化nest_stu_info_list=[c_char(b"M"),stu_obj]nest_stu_obj=NestStudent(*nest_stu_info_list)print(f"neststuinfo:{nest_stu_obj.rank}",end="")print(f"basicstuinfo:{nest_stu_obj.nest_stu.grade}")结构体数组结构体数组与普通数组的创建类似,需要提前创建结构体的类型,然后使用structtype*array_length的方法创建数组。#-*-coding:utf-8-*-fromctypesimport*#创建结构体类classStudent(Structure):_fields_=[("class",c_char),("grade",c_int),("array",c_long*3),("point",POINTER(c_int))]#增加结构体数组成员classNestStudent(Structure):_fields_=[("rank",c_char),("nest_stu",Student),("struct_array",Student*2)]#实例化long_array=c_long*3long_array_obj=long_array(1,2,3)int_p=pointer(c_int(4))stu_info_value=[c_char(b"A"),c_int(90),long_array_obj,int_p]stu_obj=Student(*stu_info_value)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172结构体数组创建结构体数组类型stu_array=Student*2用Student类的对象实例化结构体数组stu_array_obj=stu_array(stu_obj,stu_obj)实例化nest_stu_info_list=[c_char(b"M"),stu_obj,stu_array_obj]nest_stu_obj=NestStudent(*nest_stu_info_list)打印结构体数组第二个索引的grade字段的信息print("stustructarrayinfo:",end=’‘)print(nest_stu_obj.struct_array[1].grade,end=’')print(nest_stu_obj.struct_array[1].array[0])结构体指针首先创建结构体,然后使用ctype的指针方法包装为指针。fromctypesimport*#创建结构体类classStudent(Structure):_fields_=[("class",c_char),("grade",c_int),("array",c_long*3),("point",POINTER(c_int))]classNestStudent(Structure):_fields_=[("rank",c_char),("nest_stu",Student),("strct_array",Student*2),("struct_point",POINTER(Student))]12345678910111213141516171819202122实例化long_array=c_long*3long_array_obj=long_array(1,2,3)int_p=pointer(c_int(4))stu_info_value=[c_char(b"A"),c_int(90),long_array_obj,int_p]stu_obj=Student(*stu_info_value)123456结构体指针#创建结构体数组类型stu_array=Student*2##用Student类的对象实例化结构体数组stu_array_obj=stu_array(stu_obj,stu_obj)#曾接结构体指针成员,注意使用类型初始化指针是POINTER()#实例化,对Student的对象包装为指针使用pointer()nest_stu_info_list=[c_char(b"M"),stu_obj,stu_array_obj,pointer(stu_obj)]nest_stu_obj=NestStudent(*nest_stu_info_list)#结构体指针指向Student的对象print(f"stustructpointinfo:{nest_stu_obj.struct_point.contents}")#访问Student对象的成员print(f"stustructpointinfo:{nest_stu_obj.struct_point.contents.grade}")1234567891011121314结构体指针数组创建结构体指针数组的顺序为先创建结构体,然后包装为指针,最后再创建数组,用结构体指针去实例化数组。#-*-coding:utf-8-*-fromctypesimport*#创建结构体类classStudent(Structure):_fields_=[("class",c_char),("grade",c_int),("array",c_long*3),("point",POINTER(c_int))]12345678910111213141516实例化long_array=c_long*3long_array_obj=long_array(1,2,3)int_p=pointer(c_int(4))stu_info_value=[c_char(b"A"),c_int(90),long_array_obj,int_p]stu_obj=Student(*stu_info_value)#结构体指针数组#创建结构体数组类型stu_array=Student*2##用Student类的对象实例化结构体数组stu_array_obj=stu_array(stu_obj,stu_obj)#创建结构体指针数组stu_p_array=POINTER(Student)*2#使用pointer()初始化stu_p_array_obj=stu_p_array(pointer(stu_obj),pointer(stu_obj))12345678910111213141516曾接结构体指针成员,注意使用类型初始化指针是POINTER()classNestStudent(Structure):_fields_=[("rank",c_char),("nest_stu",Student),("strct_array",Student*2),("strct_point",POINTER(Student)),("strct_point_array",POINTER(Student)*2)]12345678实例化,对Student的对象包装为指针使用pointer()nest_stu_info_list=[c_char(b"M"),stu_obj,stu_array_obj,pointer(stu_obj),stu_p_array_obj]nest_stu_obj=NestStudent(*nest_stu_info_list)#数组第二索引为结构体指针print(nest_stu_obj.strct_point_array[1])#指针指向Student的对象print(nest_stu_obj.strct_point_array[1].contents)#Student对象的grade字段print(nest_stu_obj.strct_point_array[1].contents.grade)实例1:ctypes+socket对端用c语言tcpsocket发送pythonsocket.recv,得到bytes(b’xxxx’)ctypesStructure转换bytestoctypes解析数据,用Structure按c的方式解析数据.123456789101112131415161718192021222324defstruct2stream(s):length=ctypes.sizeof(s)p=ctypes.cast(ctypes.pointer(s),ctypes.POINTER(ctypes.c_char*length))returnp.contents.rawdefstream2struct(string,stype):ifnotissubclass(stype,ctypes.Structure):raiseValueError('Notactypes.Structure')length=ctypes.sizeof(stype)stream=(ctypes.c_char*length)()stream.raw=stringp=ctypes.cast(stream,ctypes.POINTER(stype))returnp.contentsclassCommandHeader(ctypes.Structure):_pack_=4_fields_=[#Sizeofthisdescriptor(inbytes)('MsgCommand',ctypes.c_int),('MsgParam',ctypes.c_int),('unkown',ctypes.c_short),('unkown1',ctypes.c_short),('startx',ctypes.c_short),('starty',ctypes.c_short),('width',ctypes.c_short),('height',ctypes.c_short),('len',ctypes.c_int)]classStructConverter(object):def__init__(self):pass@classmethoddefencoding(cls,raw,structs):"""'encode'meansrawbinarystreamtoctypestructure."""ifrawisnotNoneandstructsisnotNone:returnstream2struct(raw,structs)else:returnNone@classmethoddefdecoding(cls,data):"""'decodemeansctpyestructuretorawbinarystream"""ifdataisnotNone:returnstruct2stream(data)else:returnNone123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354收发过程:#receivetry:raw=fds.recv(ctypes.sizeof(CommandHeader))exceptsocket.errorase:exit(1)header=StructConverter.encoding(raw,CommandHeader)#sendresp=CommandHeader()try:fds.send(StructConverter.decoding(data=resp))exceptsocket.erroraseOGGER.info('%s',e)return1234567891011121314实例2libusb使用参考https://pypi.org/project/libusb1/并以自己实现的python调用libusb底层库的实现为例子:defaoa_update_point(self,action,x,y,ops=0):globalreportifops==0:#left=up=0x,y=self.axis_convert(x,y)real_x=x-self.ref_xreal_y=y-self.ref_yelse:real_x=xreal_y=y#LOGGER.info('realpoint(%d%d)',real_x,real_y)ifaction=='move'oraction=='down':report=Report(REPORT_ID,1,0,0,0,int(real_x),int(real_y))ifaction=='up':report=Report(REPORT_ID,0,0,0,0,int(real_x),int(real_y))ifops==0:self.set_ref(x,y)#transfer是1个Structuruepointerobjtransfer=U.libusb_alloc_transfer(0)#contents是实体transfer.contents.actual_length=sizeof(Report)#p_report=cast(pointer(report),c_void_p)transfer.contents.buffer=cast(pointer(report),c_void_p)#putreportbufferintocontrol_buffercontrol_buffer=create_string_buffer(sizeof(Report)+LIBUSB_CONTROL_SETUP_SIZE)#python级别的内存填充,memmove+addressofmemmove(addressof(control_buffer)+LIBUSB_CONTROL_SETUP_SIZE,addressof(report),sizeof(report))#可以看出这个是signedchar0x1--->0x10x0小端!#实际调用了:#setup=cast(addressof(control_buffer),libusb_control_setup_p).contentsU.libusb_fill_control_setup(addressof(control_buffer),U.LIBUSB_ENDPOINT_OUT|U.LIBUSB_REQUEST_TYPE_VENDOR,AndroidA0AProtocl.AOA_SEND_HID_EVENT.value[0],1,0,6)#LOGGER.info(control_buffer.raw)U.libusb_fill_control_transfer(transfer,self.aoa_handle,pointer(control_buffer),null_callback,None,0)transfer.contents.flags=U.LIBUSB_TRANSFER_FREE_BUFFER|U.LIBUSB_TRANSFER_FREE_TRANSFERrets:int=U.libusb_submit_transfer(transfer)ifrets>>fromctypes.utilimportfind_library>>>find_library("c")'/usr/lib/libc.dylib'>>>find_library("m")'/usr/lib/libm.dylib'>>>find_library("bz2")'/usr/lib/libbz2.dylib'>>>find_library("AGL")'/System/Library/Frameworks/AGL.framework/AGL'>>>12345678910在Windows中,find_library()在系统路径中搜索,然后返回全路径,但是如果没有预定义的命名方案,find_library(“c”)调用会返回Nonefromctypes.utilimportfind_libraryprint(find_library(“m”))print(find_library(“c”))print(find_library(“bz2”))使用ctypes包装动态链接库,更好的方式可能是在开发的时候就确定名称,然后硬编码到包装模块中去,而不是在运行时使用find_library()寻找库。3、加载DLL有很多方式可以将动态链接库加载到Python进程。其中之一是实例化以下类的其中一个:classctypes.CDLL(name,mode=DEFAULT_MODE,handle=None,use_errno=False,use_last_error=False,winmode=None)此类的实例就是已加载的动态链接库。库中的函数使用标准C调用约定,并假定返回int。在Windows上创建CDLL实例可能会失败,即使DLL名称确实存在。当某个被加载DLL所依赖的DLL未找到时,将引发OSError错误并附带消息“[WinError126]Thespecifiedmodulecouldnotbefound”.此错误消息不包含缺失DLL的名称,因为WindowsAPI并不会返回此类信息,这使得此错误难以诊断。要解决此错误并确定是哪一个DLL未找到,你需要找出所依赖的DLL列表并使用Windows调试与跟踪工具确定是哪一个未找到。参见:查找DLL依赖的工具(https://docs.microsoft.com/cpp/build/reference/dependents)classctypes.OleDLL(name,mode=DEFAULT_MODE,handle=None,use_errno=False,use_last_error=False,winmode=None)。仅Windows环境可用:此类的实例即加载好的动态链接库,其中的函数使用stdcall调用约定,并且假定返回windows指定的HRESULT返回码。HRESULT的值包含的信息说明函数调用成功还是失败,以及额外错误码。如果返回值表示失败,会自动抛出OSError异常。classctypes.WinDLL(name,mode=DEFAULT_MODE,handle=None,use_errno=False,use_last_error=False,winmode=None)。仅Windows环境可用:此类的实例即加载好的动态链接库,其中的函数使用stdcall调用约定,并假定默认返回int。调用动态库导出的函数之前,Python会释放globalinterpreterlock,并在调用后重新获取。classctypes.PyDLL(name,mode=DEFAULT_MODE,handle=None)。这个类实例的行为与CDLL类似,只不过不会在调用函数的时候释放GIL锁,且调用结束后会检查Python错误码。如果错误码被设置,会抛出一个Python异常。所以,它只在直接调用PythonC接口函数的时候有用。共享库也可以通用使用一个预制对象来加载,这种对象是LibraryLoader类的实例,具体做法或是通过调用LoadLibrary()方法,或是通过将库作为加载器实例的属性来提取。classctypes.LibraryLoader(dlltype)加载共享库的类。dlltype应当为CDLL,PyDLL,WinDLL或OleDLL类型之一。LoadLibrary(name)加载共享库到进程中并将其返回。此方法总是返回一个新的库实例。可用的预制库加载器有如下这些:ctypes.cdll:cdll是CDll类的对象。ctypes.windll:仅限Windows:windll是WinDLL类的对象。ctypes.oledll:仅限Windows:创建OleDLL实例。ctypes.pydll:创建PyDLL实例。ctypes.pythonapi:访问CPythonapi,可以使用ctypes.pythonapi,一个PyDLL的实例,它将PythonCAPI函数作为属性公开。请注意所有这些函数都应返回Cint,当然这也不是绝对的,因此你必须分配正确的restype属性以使用这些函数。访问dll,首先需引入ctypes库:fromctypesimport*“stdcall调用约定”和“cdecl调用约定”声明的导出函数,python在加载使用时是不同。stdcall调用约定:两种加载方式fromctypesimport*#方法1Objdll=ctypes.windll.LoadLibrary("dllpath")#方法2Objdll=ctypes.WinDLL("dllpath")cdecl调用约定:也有两种加载方式fromctypesimport*#方法1Objdll=ctypes.cdll.LoadLibrary("dllpath")#方法2Objdll=ctypes.CDLL("dllpath")12345678910111213141516加载dll的几种常用方法ctypes导出了cdll对象,在Windows系统中还导出了windll和oledll对象,通过操作这些对象的属性,可以载入外部的动态链接库。cdll:载入按标准的cdecl调用协议导出的函数。在Windows系统中还导出了windll和oledll对象:windll:导入的库按stdcall调用协议调用其中的函数。oledll:也按stdcall调用协议调用其中的函数,并假定该函数返回的是WindowsHRESULT错误代码,并当函数调用失败时,自动根据该代码甩出一个OSError异常。加载完dll后会返回一个DLL对象,使用其中的函数方法则相当于操作该对象的对应属性。注意:经过stdcall声明的方法,如果不是用def文件声明的导出函数或者extern“C”声明的话,编译器会对函数名进行修改函数参数类型,通过设置函数的argtypes属性函数返回类型,函数默认返回c_int类型,如果需要返回其他类型,需要设置函数的restype属性要加载一个dll,有好几种方法:fromctypesimport*dll_1=cdll.filenamedll_2=cdll.LoadLibrary("filename")dll_3=CDLL("filename")cdll.filename.func_1()1234567等价于dll_hwnd=cdll.filenamedll_hwnd.func_1()这三个都会调用filename.dll(Windows下自动补充后缀)并返回一个句柄一样的东西,我们便可以通过该句柄去调用该库中的函数。这里还有一个用法,由于dll导出的时候会有一个exports表,上面记录了哪些函数是导出函数,同时这个表里也有函数的序号,因此我们可以这样来访问表里的第一个函数fromctypesimport*cdll.filename[1](要注意是从1而非0开始编号)返回的是该函数的指针一样的东西,如果我们要调用的话就在后面加(parameter)即可。关于exports表,GCC似乎编译时会自动生成def,可以在里面查,如果只有DLL的话,可以用VC的depends,或者dumpbin来查。4、调用DLL中的函数动态链接库的导出函数有2中方式进行访问属性方式:属性方式访问会缓存这个函数,因而每次访问它时返回的都是同一个对象。索引方式:索引方式访问,每次都会返回一个新的对象fromctypesimportCDLLlibc=CDLL("libc.so.6")#OnLinuxprint(libc.time==libc.time)#Trueprint(libc['time']==libc['time'])#False1234564.1示例:(Windows环境)Windows会自动添加通常的.dll文件扩展名。fromctypesimport*kernel32=windll.kernel32print(kernel32)user32=windll.user32print(user32)libc=cdll.msvcrtprint(libc)调用dll对象的属性来操作函数fromctypesimport*libc=cdll.msvcrtprint(libc.printf)print(windll.kernel32.GetModuleHandleA)print(libc.time(None))print(hex(windll.kernel32.GetModuleHandleA(None)))print(windll.kernel32.MyOwnFunction)123456789101112131415161718192021调用time()函数返回一个系统时间戳,调用GetModuleHandleA()函数返回一个win32模块句柄。调用的两个函数都使用了空指针(用None作为空指针)如果你用cdecl调用方式调用stdcall约定的函数,则会甩出一个异常ValueError。反之亦然。>>>cdll.kernel32.GetModuleHandleA(None)Traceback(mostrecentcalllast):File"",line1,inValueErrorrocedureprobablycalledwithnotenougharguments(4bytesmissing)>>>>>>windll.msvcrt.printf(b"spam")Traceback(mostrecentcalllast):File"",line1,inValueErrorrocedureprobablycalledwithtoomanyarguments(4bytesinexcess)>>>另外:printf将打印到真正标准输出设备,而*不是*sys.stdout,因此这些实例只能在控制台提示符下工作,而不能在IDLE或PythonWin中运行。>>>printf=libc.printf>>>printf(b"Hello,%s\n",b"World!")Hello,World!14>>>printf(b"Hello,%S\n","World!")Hello,World!14>>>printf(b"%dbottlesofbeer\n",42)42bottlesofbeer19>>>printf(b"%fbottlesofbeer\n",42.5)Traceback(mostrecentcalllast):File"",line1,inArgumentError:argument2:exceptions.TypeErroron'tknowhowtoconvertparameter2>>>除了整数、字符串以及字节串之外,所有的Python类型都必须使用它们对应的ctypes类型包装,才能够被正确地转换为所需的C语言类型。>>>printf(b"Anint%d,adouble%f\n",1234,c_double(3.14))Anint1234,adouble3.14000031>>>123456789101112131415161718192021222324252627282930313233UNICODE和ANSI版本注意:Win32系统的动态库,比如kernel32和user32,通常会同时导出同一个函数的ANSI版本和UNICODE版本。UNICODE版本通常会在名字最后以W结尾ANSI版本的则以A结尾。win32的GetModuleHandle函数会根据一个模块名返回一个模块句柄,该函数暨同时包含这样的两个版本的原型函数,并通过宏UNICODE是否定义,来决定宏GetModuleHandle导出的是哪个具体函数。/*ANSIversion/HMODULEGetModuleHandleA(LPCSTRlpModuleName);/UNICODEversion*/HMODULEGetModuleHandleW(LPCWSTRlpModuleName);windll不会通过这样的魔法手段来帮你决定选择哪一种函数,你必须显式的调用GetModuleHandleA或GetModuleHandleW,并分别使用字节对象或字符串对象作参数。getattr()方法来获得函数有时候,dlls的导出的函数名不符合Python的标识符规范,比如“??2@YAPAXI@Z”。此时,你必须使用getattr()方法来获得该函数。>>>getattr(cdll.msvcrt,"??2@YAPAXI@Z")>>>123通过exports获得函数Windows下,有些dll导出的函数没有函数名,而是通过其顺序号调用。对此类函数,你也可以通过dll对象的数值索引来操作这些函数。>>>cdll.kernel32[1]>>>cdll.kernel32[0]Traceback(mostrecentcalllast):File"",line1,inFile"ctypes.py",line310,in__getitem__func=_StdcallFuncPtr(name,self)AttributeError:functionordinal0notfound>>>1234567894.2示例:(Linux环境)必须使用包含文件扩展名的文件名来导入共享库。因此不能简单使用对象属性的方式来导入库。因此,你可以使用方法LoadLibrary(),或构造CDLL对象来导入库。fromctypesimport*libc_so_1=cdll.LoadLibrary("libc.so.6")print(libc_so_1)libc_so_2=CDLL("libc.so.6")print(libc_so_2)1234567示例1:写一个简单的Hello来说明一下最基本的用法。首先定义一个简单的c函数。//hello_module.c#includeinthello(constchar*name){printf("hello%s!\n",name);return0;}1234567编译生成动态库,动态库不同的系统后缀不同(Windows的dll,Linux的so,Mac的dylib),需要注意,本文以so为例。gcc-fPIC-sharedhello_module.c-ohello_module.so通过ctypes来进行动态库加载及函数调用,注意windows的调用方式有专有的API。importctypeslib=ctypes.cdll.LoadLibrary(“hello_module.so”)lib.hello(“world”)#helloworld!以上便是简单的ctypes使用流程,加载动态库,然后就可以调用动态库中的函数。有几点需要注意的地方:类型的隐私转换的,python的str转换为了c的constchar*默认的函数返回值认为是int,不为int的需要自行修改函数的参数类型未指定,只能使用ctypes自带的类型隐私转换需要说明的一点是,int和uint都有对应的8、16、32、64的类型可供使用。示例2:#-*-coding:utf-8-*-fromctypesimport*#学生信息如下stu_info=[("class","A"),("grade",90),("array",[1,2,3]),("point",4)]#创建结构提类classStudent(Structure):_fields_=[("class",c_char),("grade",c_int),("array",c_long*3),("point",POINTER(c_int))]print("sizeofStudent:",sizeof(Student))#实例化long_array=c_long*3long_array_obj=long_array(1,2,3)int_p=pointer(c_int(4))stu_info_value=[c_char(b"A"),c_int(90),long_array_obj,int_p]stu_obj=Student(*stu_info_value)1234567891011121314151617181920212223242526272829303132结构体指针数组创建结构体数组类型stu_array=Student*2#用Student类的对象实例化结构体数组stu_array_obj=stu_array(stu_obj,stu_obj)创建结构体指针数组stu_p_array=POINTER(Student)*2使用pointer()初始化stu_p_array_obj=stu_p_array(pointer(stu_obj),pointer(stu_obj))曾接结构体指针成员,注意使用类型初始化指针是POINTER()classNestStudent(Structure):fields=[(“rank”,c_char),(“nest_stu”,Student),(“strct_array”,Student*2),(“strct_point”,POINTER(Student)),(“strct_point_array”,POINTER(Student)*2)]实例化,对Student的对象包装为指针使用pointer()nest_stu_info_list=[c_char(b"M"),stu_obj,stu_array_obj,pointer(stu_obj),stu_p_array_obj]nest_stu_obj=NestStudent(*nest_stu_info_list)#数组第二索引为结构体指针print(nest_stu_obj.strct_point_array[1])#指针指向Student的对象print(nest_stu_obj.strct_point_array[1].contents)#Student对象的grade字段print(nest_stu_obj.strct_point_array[1].contents.grade)实例化动态链接库的载入对象so_obj=cdll.LoadLibrary(“./libstruct.so”)准备入参char_arg=c_char(b"Z")int_arg=c_int(13)float_arg=c_float(3.14159)准备出参out_buf=create_string_buffer(b"",sizeof(Student))注意C语言源码中入参要求是指针,所以这里需要再次使用pointer()rest=so_obj.test_func(char_arg,int_arg,float_arg,pointer(stu_obj),pointer(nest_stu_obj),out_buf)或者使用ctypes.bryef()方法自动转换,更快一点rest=so_obj.test_func(char_arg,int_arg,float_arg,byref(stu_obj),byref(nest_stu_obj),out_buf)打印函数返回值print("funcrest:",rest)打印出参print("outbuf:",out_buf[0:sizeof(c_int)*2])输出:stuinfo:9014chararg:Zintarg:13floatarg:3.141590structclass:Astructgrade:90structarray[0]:1array[1]:2structpoint:4neststructrank:77neststructstugrade:90neststructarray[0]grade:90neststructarray[1]grade:90neststructpointgrade:90neststructpointarray[0]grade:90neststructpointarray[1]grade:90funcrest:1outbuf:b’A\x00\x00\x00Z\x00\x00\x00’#此处90转换为字符打印了如果python需要使用动态链接库的符号,直接调用即可,如下:调用C源码中的g_stu结构体so_symble=so_obj.g_stu_tprint(so_symble)输出:最后,关于共用体、位域、函数返回值等方法,这部分的方法建议看一下官方文档。5、回调函数ctypes允许创建一个指向Python可调用对象的C函数。它们有时候被称为回调函数。首先,你必须为回调函数创建一个类,这个类知道调用约定,包括返回值类型以及函数接收的参数类型及个数。CFUNCTYPE()工厂函数使用cdecl调用约定创建回调函数类型。在Windows上,WINFUNCTYPE()工厂函数使用stdcall调用约定为回调函数创建类型。这些工厂函数的第一个参数是返回值类型,回调函数的参数类型作为剩余参数。这里展示一个使用C标准库函数qsort()的例子,它使用一个回调函数对数据进行排序。qsort()将用来给整数数组排序fromctypesimport*defpy_cmp_func(a,b):print("py_cmp_func",a[0],b[0])return0libc=cdll.msvcrt#导入libIntArray5=c_int*5#定义整数型数组类型ia=IntArray5(5,1,7,33,99)#数组初始化qsort=libc.qsort#得到qsort函数qsort.restype=None#设置返回值类型#qsort()的参数#:指向待排序数据的指针,#:元素个数,#:每个元素的大小,#:指向排序函数的指针,即回调函数。#回调函数接收两个元素的指针,#如果第一个元素小于第二个,则返回一个负整数,#如果相等则返回0,否则返回一个正整数。#所以,回调函数要接收两个整数指针,返回一个整数。首先我们创建回调函数的类型CMP_FUNC=CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))#创建回调函数的类型cmp_func=CMP_FUNC(py_cmp_func)#创建回调函数qsort(ia,len(ia),sizeof(c_int),cmp_func)#调用函数123456789101112131415161718192021222324252627282930工厂函数可以当作装饰器工厂,所以可以这样写:fromctypesimport*defpy_cmp_func(a,b):print("py_cmp_func",a[0],b[0])return0@CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int))defpy_cmp_func(a,b):print("py_cmp_func",a[0],b[0])returna[0]-b[0]libc=cdll.msvcrt#导入libIntArray5=c_int*5#定义整数型数组类型ia=IntArray5(5,1,7,33,99)#数组初始化qsort=libc.qsort#得到qsort函数qsort.restype=None#设置返回值类型qsort(ia,len(ia),sizeof(c_int),py_cmp_func)12345678910111213141516171819206、函数原型函数原型就相当于C/C++中函数的声明,也仅仅只是声明,没有定义。外部函数也可通过实例化函数原型来创建。“函数原型”类似于C中的函数原型;它们在不定义具体实现的情况下描述了一个函数(返回类型、参数类型、调用约定)。工厂函数必须使用函数所需要的结果类型和参数类型来调用,并可被用作装饰器工厂函数,在此情况下可以通过@wrapper语法应用于函数。ctypes.CFUNCTYPE(restype,*argtypes,use_errno=False,use_last_error=False)。返回的函数原型会创建使用标准C调用约定的函数。该函数在调用过程中将释放GIL。如果use_errno设为真值,则在调用之前和之后系统errno变量的ctypes私有副本会与真正的errno值进行交换;use_last_error会为Windows错误码执行同样的操作。ctypes.WINFUNCTYPE(restype,*argtypes,use_errno=False,use_last_error=False)。Windowsonly:Thereturnedfunctionprototypecreatesfunctionsthatusethestdcallcallingconvention.ThefunctionwillreleasetheGILduringthecall.use_errnoanduse_last_errorhavethesamemeaningasabove.ctypes.PYFUNCTYPE(restype,*argtypes)。返回的函数原型会创建使用Python调用约定的函数。该函数在调用过程中将不会释放GIL。这些工厂函数所创建的函数原型可通过不同的方式来实例化,具体取决于调用中的类型与数量。prototype(address)。在指定地址上返回一个外部函数,地址值必须为整数。prototype(callable)。基于Pythoncallable创建一个C可调用函数(回调函数)。prototype(func_spec[,paramflags])。返回由一个共享库导出的外部函数。func_spec必须为一个2元组(name_or_ordinal,library)。第一项是字符串形式的所导出函数名称,或小整数形式的所导出函数序号。第二项是该共享库实例。prototype(vtbl_index,name[,paramflags[,iid]])。返回将调用一个COM方法的外部函数。vtbl_index虚拟函数表中的索引。name是COM方法的名称。iid是可选的指向接口标识符的指针,它被用于扩展的错误报告。COM方法使用特殊的调用约定:除了在argtypes元组中指定的形参,它们还要求一个指向COM接口的指针作为第一个参数。可选的paramflags形参会创建相比上述特性具有更多功能的外部函数包装器。paramflags必须为一个与argtypes长度相同的元组。此元组中的每一项都包含有关形参的更多信息,它必须为包含一个、两个或更多条目的元组。第一项是包含形参指令旗标组合的整数。1指定函数的一个输入形参。2输出形参。外部函数会填入一个值。4默认为整数零值的输入形参。可选的第二项是字符串形式的形参名称。如果指定此项,则可以使用该形参名称来调用外部函数。可选的第三项是该形参的默认值。示例:演示了如何包装Windows的MessageBoxW函数以使其支持默认形参和已命名参数。相应windows头文件的C声明是这样的:WINUSERAPIintWINAPIMessageBoxW(HWNDhWnd,LPCWSTRlpText,LPCWSTRlpCaption,UINTuType);这是使用ctypes的包装:fromctypesimportc_int,WINFUNCTYPE,windllfromctypes.wintypesimportHWND,LPCWSTR,UINTprototype=WINFUNCTYPE(c_int,HWND,LPCWSTR,LPCWSTR,UINT)paramflags=(1,"hwnd",0),(1,"text","Hi"),(1,"caption","Hellofromctypes"),(1,"flags",0)MessageBox=prototype(("MessageBoxW",windll.user32),paramflags)MessageBox()MessageBox(text="Spam,spam,spam")MessageBox(flags=2,text="foobar")12345678910示例:演示了输出形参。这个win32GetWindowRect函数通过将指定窗口的维度拷贝至调用者必须提供的RECT结构体来提取这些值。这是相应的C声明:WINUSERAPIBOOLWINAPIGetWindowRect(HWNDhWnd,LPRECTlpRect);这是使用ctypes的包装:>>>fromctypesimportPOINTER,WINFUNCTYPE,windll,WinError>>>fromctypes.wintypesimportBOOL,HWND,RECT>>>prototype=WINFUNCTYPE(BOOL,HWND,POINTER(RECT))>>>paramflags=(1,"hwnd"),(2,"lprect")>>>GetWindowRect=prototype(("GetWindowRect",windll.user32),paramflags)>>>123456带有输出形参的函数如果输出形参存在单一值则会自动返回该值,或是当输出形参存在多个值时返回包含这些值的元组,因此当GetWindowRect被调用时现在将返回一个RECT实例。输出形参可以与errcheck协议相结合以执行进一步的输出处理与错误检查。Win32GetWindowRectAPI函数返回一个BOOL来表示成功或失败,因此此函数可执行错误检查,并在API调用失败时引发异常:>>>deferrcheck(result,func,args):...ifnotresult:...raiseWinError()...returnargs...>>>GetWindowRect.errcheck=errcheck>>>1234567如果errcheck不加更改地返回它所接收的参数元组,则ctypes会继续对输出形参执行常规处理。如果你希望返回一个窗口坐标的元组而非RECT实例,你可以从函数中提取这些字段并返回它们,常规处理将不会再执行:>>>deferrcheck(result,func,args):...ifnotresult:...raiseWinError()...rc=args[1]...returnrc.left,rc.top,rc.bottom,rc.right...>>>GetWindowRect.errcheck=errcheck>>>12345678ctypes提供的工具函数ctypes.addressof(obj)ctypes.addressof(obj):以整数形式返回内存缓冲区地址。obj必须为一个ctypes类型的实例。ctypes.alignment(obj_or_type)ctypes.alignment(obj_or_type):返回一个ctypes类型的对齐要求。obj_or_type必须为一个ctypes类型或实例。ctypes.byref(obj[,offset])ctypes.byref(obj[,offset]):返回指向obj的轻量指针,该对象必须为一个ctypes类型的实例。offset默认值为零,且必须为一个将被添加到内部指针值的整数。byref(obj,offset)对应于这段C代码((char*)&obj)+offset)返回的对象只能被用作外部函数调用形参。它的行为类似于pointer(obj),但构造起来要快很多。ctypes.cast(obj,type)ctypes.cast(obj,type)此函数类似于C的强制转换运算符。它返回一个type的新实例,该实例指向与obj相同的内存块。type必须为指针类型,而obj必须为可以被作为指针来解读的对象。ctypes.create_string_buffer(init_or_size,size=None)ctypes.create_string_buffer(init_or_size,size=None):此函数会创建一个可变的字符缓冲区。返回的对象是一个c_char的ctypes数组。init_or_size必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字节串对象。如果将一个字节串对象指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个NUL终结符。可以传入一个整数作为第二个参数以允许在不使用字节串长度的情况下指定数组大小。ctypes.create_unicode_buffer(init_or_size,size=None)ctypes.create_unicode_buffer(init_or_size,size=None):此函数会创建一个可变的unicode字符缓冲区。返回的对象是一个c_wchar的ctypes数组。init_or_size必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字符串。如果将一个字符串指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个NUL终结符。可以传入一个整数作为第二个参数以允许在不使用字符串长度的情况下指定数组大小。ctypes.DllCanUnloadNow()ctypes.DllCanUnloadNow():仅限Windows:此函数是一个允许使用ctypes实现进程内COM服务的钩子。它将由_ctypes扩展dll所导出的DllCanUnloadNow函数来调用。ctypes.DllGetClassObject()ctypes.DllGetClassObject():仅限Windows:此函数是一个允许使用ctypes实现进程内COM服务的钩子。它将由_ctypes扩展dll所导出的DllGetClassObject函数来调用。ctypes.util.find_library(name)ctypes.util.find_library(name):尝试寻找一个库并返回路径名称。name是库名称并且不带任何前缀如lib以及后缀如.so,.dylib或版本号(形式与posix链接器选项-l所用的一致)。如果找不到库,则返回None。确切的功能取决于系统。ctypes.util.find_msvcrt()ctypes.util.find_msvcrt():仅限Windows:返回Python以及扩展模块所使用的VC运行时库的文件名。如果无法确定库名称,则返回None。如果你需要通过调用free(void*)来释放内存,例如某个扩展模块所分配的内存,重要的一点是你应当使用分配内存的库中的函数。ctypes.FormatError([code])ctypes.FormatError([code]):仅限Windows:返回错误码code的文本描述。如果未指定错误码,则会通过调用Windowsapi函数GetLastError来获得最新的错误码。ctypes.GetLastError()ctypes.GetLastError():仅限Windows:返回Windows在调用线程中设置的最新错误码。此函数会直接调用WindowsGetLastError()函数,它并不返回错误码的ctypes私有副本。ctypes.get_errno()ctypes.get_errno():返回调用线程中系统errno变量的ctypes私有副本的当前值。ctypes.get_last_error()ctypes.get_last_error():仅限Windows:返回调用线程中系统LastError变量的ctypes私有副本的当前值。ctypes.memmove(dst,src,count)ctypes.memmove(dst,src,count)与标准Cmemmove库函数相同:将count个字节从src拷贝到dst。dst和src必须为整数或可被转换为指针的ctypes实例。ctypes.memset(dst,c,count)ctypes.memset(dst,c,count):与标准Cmemset库函数相同:将位于地址dst的内存块用count个字节的c值填充。dst必须为指定地址的整数或ctypes实例。ctypes.POINTER(type)ctypes.POINTER(type):这个工厂函数创建并返回一个新的ctypes指针类型。指针类型会被缓存并在内部重用,因此重复调用此函数耗费不大。type必须为ctypes类型。ctypes.pointer(obj)ctypes.pointer(obj):此函数会创建一个新的指向obj的指针实例。返回的对象类型为POINTER(type(obj))。注意:如果你只是想向外部函数调用传递一个对象指针,你应当使用更为快速的byref(obj)。ctypes.resize(obj,size)ctypes.resize(obj,size):此函数可改变obj的内部内存缓冲区大小,其参数必须为ctypes类型的实例。没有可能将缓冲区设为小于对象类型的本机大小值,该值由sizeof(type(obj))给出,但将缓冲区加大则是可能的。ctypes.set_errno(value)ctypes.set_errno(value):设置调用线程中系统errno变量的ctypes私有副本的当前值为value并返回原来的值。ctypes.set_last_error(value)ctypes.set_last_error(value):仅限Windows:设置调用线程中系统LastError变量的ctypes私有副本的当前值为value并返回原来的值。ctypes.sizeof(obj_or_type)ctypes.sizeof(obj_or_type):返回ctypes类型或实例的内存缓冲区以字节表示的大小。其功能与Csizeof运算符相同。ctypes.string_at(address,size=-1)ctypes.string_at(address,size=-1):此函数返回从内存地址address开始的以字节串表示的C字符串。如果指定了size,则将其用作长度,否则将假定字符串以零值结尾。ctypes.WinError(code=None,descr=None)ctypes.WinError(code=None,descr=None):仅限Windows:此函数可能是ctypes中名字起得最差的函数。它会创建一个OSError的实例。如果未指定code,则会调用GetLastError来确定错误码。如果未指定descr,则会调用FormatError()来获取错误的文本描述。ctypes.wstring_at(address,size=-1)ctypes.wstring_at(address,size=-1):此函数返回从内存地址address开始的以字符串表示的宽字节字符串。如果指定了size,则将其用作字符串中的字符数量,否则将假定字符串以零值结尾。
|
|