内容:.加载动态链接库.从已加载的dll中引用函数.调用函数1.基本的数据类型.调用函数2.用自己的数据类型调用函数.确认需要的参数类型(函数原型).返回值.传递指针.结构和联合.结构或联合的对齐方式和字节的顺序.结构和联合中的位.数组.指针.类型转换.未完成的类型.回调函数.访问dlls导出的值.可变长度的数据类型.bugs将要做的和没有做的事情注意:本文中的代码例子使用doctest确保他们能够实际工作。一些代码例子在linux和windows以及苹果机上执行有一定的差别注意:一些代码引用了ctypes的c_int类型。它是c_long在32位机子上的别名,你不应该变得迷惑,如果你期望的是c_int类型,实事上打印的是c_long,它们实事上是相同的类型。加载动态链接库ctypes加载动态链接库,导出cdll和在windows上同样也导出windll和oledll对象。加载动态链接库后,你可以像使用对象的属性一样使用它们。cdll加载使用标准的cdecl调用约定的链接库,而windll库使用stdcall调用约定,oledll也使用stdcall调用约定,同时确保函数返回一个windowsHRESULT错误代码。这错误代码自动的升为WindowsErrorPythonexceptions,当这些函数调用失败时。这有一些windows例子,msvcrt是微软的c标准库,包含大部分的标准c函数,同时使用cdecl调用约定。注:cdecl和stdcall的区别请见*printwindll.kernel32#doctest:+WINDOWSWinDLL'kernel32',handle...at...printcdll.msvcrt#doctest:+WINDOWSCDLL'msvcrt',handle...at...libc=cdll.msvcrt#doctest:+WINDOWSwindows自动添加常用的.dll文件后缀名在linux上,需要使用包含扩展名的文件名来加载一个库,因此属性操作不能正常使用。或者使用dll加载器的LoadLibrary方法,或者通过CDLL构造函数创建一个CDLL的一个实例cdll.LoadLibrary(libc.so.6)#doctest:+LINUXCDLL'libc.so.6',handle...at...libc=CDLL(libc.so.6)#doctest:+LINUXlibc#doctest:+LINUXCDLL'libc.so.6',handle...at...加载dll使用其中函数使用函数就像对象的属性fromctypesimport*libc.printf_FuncPtrobjectat0x...printwindll.kernel32.GetModuleHandleA#doctest:+WINDOWS_FuncPtrobjectat0x...printwindll.kernel32.MyOwnFunction#doctest:+WINDOWSTraceback(mostrecentcalllast):Filestdin,line1,in?Filectypes.py,line239,in__getattr__func=_StdcallFuncPtr(name,self)AttributeError:function'MyOwnFunction'notfound00注意win32系统dll像kernel32和user32大部分导出ANSI和UNICODE版本函数,UNICODE版本以一个W结尾导出而ANSI版本则以一个A结尾导出的。win32GetModuleHandle函数,返回一个指定的module名字的module句柄下面c原型,GetModuleHandle的macro,依赖于它是不是UNICODE。/*ANSIversion*/HMODULEGetModuleHandleA(LPCSTRlpModuleName);/*UNICODEversion*/HMODULEGetModuleHandleW(LPCWSTRlpModuleName);windll不会神奇的自已去选择一个,你必须显式确认GetModuleHandleA或者GetModuleHandleW去使用它们去处理一般字符串或unicode字符串。有时候,dll导出函数名,python无法识别,像??2@YAPAXI@Z.在这情况下,你必须使用getattr去使用这些函数getattr(cdll.msvcrt,??2@YAPAXI@Z)#doctest:+WINDOWS_FuncPtrobjectat0x...在windows上,一些dllS不是导出函数名,而是以顺序,这些函数可以将这些数字当作dll对象的索引来使用这些函数。cdll.kernel32[1]#doctest:+WINDOWS_FuncPtrobjectat0x...cdll.kernel32[0]#doctest:+WINDOWSTraceback(mostrecentcalllast):Filestdin,line1,in?Filectypes.py,line310,in__getitem__func=_StdcallFuncPtr(name,self)AttributeError:functionordinal0notfound调用函数你可调用这些函数,像其它的python函数一样,下面的例子使用time()函数,它以秒为单位返回从unix新纪元的系统时间,GetModuleHandleA()函数,返回一个win32模块句柄。下面的例子用空指针调用函数(None作为空指针)printlibc.time(None)#doctest:+SKIP1150640792printhex(windll.kernel32.GetModuleHandleA(None))#doctest:+WINDOWS0x1d000000ctypes尝试阻止你以错误的参数数量或调用约定来调用函数。不幸的是,这些仅仅能在windows上工作它在函数返回后不会去检查这栈,尽管在调用函数后有错误发生。windll.kernel32.GetModuleHandleA()#doctest:+WINDOWSTraceback(mostrecentcalllast):Filestdin,line1,in?ValueError:Procedureprobablycalledwithnotenougharguments(4bytesmissing)windll.kernel32.GetModuleHandleA(0,0)#doctest:+WINDOWSTraceback(mostrecentcalllast):Filestdin,line1,in?ValueError:Procedureprobablycalledwithtoomanyarguments(4bytesinexcess)产生了同样的exception,当你用cdecl调用约定去使用一个stdcall函数,反之亦然。cdll.kernel32.GetModuleHandleA(None)#doctest:+WINDOWSTraceback(mostrecentcalllast):Filestdin,line1,in?ValueError:Procedureprobablycalledwithnotenougharguments(4bytesmissing)windll.msvcrt.printf(spam)#doctest:+WINDOWSTraceback(mostrecentcalllast):Filestdin,line1,in?ValueError:Procedureprobablycalledwithtoomanyarguments(4bytesinexcess)找到正确的调用约定,你必须检查c头文件或者你想调用的函数的文档。在windows,ctypes使用win32结构exception处理一般的保护错误阻止crashes,当调用无效的参数值windll.kernel32.GetModuleHandleA(32)#doctest:+WINDOWSTraceback(mostrecentcalllast):Filestdin,line1,in?WindowsError:exception:accessviolationreading0x00000020然而有很多种可能性会发生crash,你使用python的ctypes,你应该足够的小心。None,integers,longs,bytestrings和unicodestring是python内置对象,可以直接的作为这些函数的参数,None作为一个空指针,bytestring和unicodestring当作一个内存块,它包含这些数据(char*或者wchar_t*)。python的int和long长整形作为cint类型,它们的值对应着c类型。在调用带有参数的函数之前,我们必须学习更多的关于ctypes数据类型。基础的数据类型ctypes定义了许多主要的c兼容有数据类型数据类型请见使用它们和一个可选的正确的值去创建所有的这些类型c_int()c_long(0)c_char_p(Hello,World)c_char_p('Hello,World')c_ushort(-3)c_ushort(65533)因此这些类型是可变的,他们的值在后面也是可以改变的分配一个新值到这些指针类型c_char_p,c_wchar_p,和c_void_p的一个实例上,改变它们指向的内存位置,而不是这些内存块的内容(当然,因为python的string是不可改变的)s=Hello,Worldc_s=c_char_p(s)printc_sc_char_p('Hello,World')c_s.value=Hi,thereprintc_sc_char_p('Hi,there')prints#firststringisunchangedHello,World你应该注意,不要期望传给函数指针是指向可变的内存。如果你需要可变的内存块,ctypes有一个create_string_buffer函数。这内存块内容通过这raw属性来访问或改变,如果你想访问一个以null结束的字符串,使用这string属性fromctypesimport*p=create_string_buffer(3)#createa3bytebuffer,initializedtoNULbytesprintsizeof(p),repr(p.raw)3'\x00\x00\x00'p=create_string_buffer(Hello)#createabuffercontainingaNULterminatedstringprintsizeof(p),repr(p.raw)6'Hello\x00'printrepr(p.value)'Hello'p=create_string_buffer(Hello,10)#createa10bytebufferprintsizeof(p),repr(p.raw)10'Hello\x00\x00\x00\x00\x00'p.value=