ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用C DLL中的函数。ctypes的官方文档在这里。
1. ctypes基本数据类型映射表
参数类型预先设定好,或者在调用函数时再把参数转成相应的c_***类型。ctypes的类型对应如下:
ctypes type | C type | Python Type |
c_char | char | 1-character string |
c_wchar | wchar_t | 1-character unicode string |
c_byte | char | int/long |
c_ubyte | unsigned char | int/long |
c_bool | bool | bool |
c_short | short | int/long |
c_ushort | unsigned short | int/long |
c_int | int | int/long |
c_uint | unsigned int | int/long |
c_long | long | int/long |
c_ulong | unsigned long | int/long |
c_longlong | __int64 or longlong | int/long |
c_ulonglong | unsigned __int64 or unsigned long long | int/long |
c_float | float | float |
c_double | double | float |
c_longdouble | long double float | float |
c_char_p | char * | string or None |
c_wchar_p | wchar_t * | unicode or None |
c_void_p | void * | int/long or None |
对应的指针类型是在后面加上"_p",如int*是c_int_p等等。在python中要实现c语言中的结构,需要用到类。
2. 加载DLL
访问dll,首先需引入ctypes库
from ctypes import *
假设你已经有了一个的DLL(名字是add.dll),且该DLL有一个符合cdecl(这里强调调用约定是因为,stdcall调用约定和cdecl调用约定声明的导出函数,在使用python加载时使用的加载函数是不同的,后面会有说明)调用约定的导出函数Add。
stdcall调用约定:两种加载方式
- Objdll = ctypes.windll.LoadLibrary("dllpath")
- Objdll = ctypes.WinDLL("dllpath")
cdecl调用约定:也有两种加载方式
- Objdll = ctypes.cdll.LoadLibrary("dllpath")
- Objdll = ctypes.CDLL("dllpath")
其实windll和cdll分别是WinDLL类和CDll类的对象。
linux下:
lib_nvr = cdll.LoadLibrary('./nvr.so')
2. python中声明c变量并给动态库传参数
(1)字符串:加 b
login_id = lib_nvr.Login(b'192.168.0.222', 37777, b'admin', b'Ehigh2014') print ("login", login_id)
(2)c风格整型:
#定义一个c类型的变量 size =c_int() //定义一个 c 风格 int 变量 leaving = lib_nvr.GetData(c_char_p(buffer.ctypes.data), 1024*1024*10, pointer(size) )
pointer(size) // 取地址
(3)c风格数组:传递指针
# 测试get_data buffer = numpy.zeros([1024*1024*10], dtype=numpy.uint8) //创建numpy数组 print ("..............") for i in range(15): print (buffer[ i ]) print ("done!") #定义一个c类型的变量 size =c_int() //传递指针 leaving = lib_nvr.GetData(c_char_p(buffer.ctypes.data), 1024*1024*10, pointer(size) )
4. 指针与引用
常用的通过调用ctypes类型的指针函数来创建指针实例:
- from ctype import *
- i = c_int(1)
- pi = POINTER(i)
对指针实例赋值只会改变其指向的内存地址,而不是改变内存的内容,与其他语言类似,如需要可改变内容的字符串,可须使用create_string_buffer()
- >>> p = create_string_buffer("Hello", 10) # create a 10 byte buffer
- >>> print sizeof(p), repr(p.raw)
- 10 'Hello/x00/x00/x00/x00/x00'
不带参数的调用指针类型创建一个NULL指针, NULL指针有一个False布尔值
- >>> null_ptr = POINTER(c_int)()
- >>> print bool(null_ptr)
- False
指针实例有一个contents属性,返回这个指针所指向的对象。
另外,byref()是用来传递引用参数,pointer()作为传参通常会创建一个实际的指针对象,当不需要实际指针对象时,则可使用byref()
5. 结构体类型处理
Structures和Unions必须继承Structure和Union基础类,它们都在ctypes模块中定义,每一个子类必须定义个_fields_属性,_fields_是一个二维的tuples列表,包含着每个field的name及type,这field类型必须是一个ctypes类型,如c_int,或者任何其他的继承ctypes的类型,如Structure, Union, Array, 指针等。
例如有一个简单结构,包含两个整型x和y,可如下初始化一个结构:
- from ctypes import *
- import types
- class Test(Structure):
- _fields_ = [('x', c_int),
- ('y', c_char)]
- test1 = Test(1, 2)
另外,如结构体用于链表操作,即包含指向结构体指针时,若直接定义:
- from ctypes import *
- import types
- class Test(Structure):
- _fields_ = [('x', c_int),
- ('y', c_char),
- ('next', Test)]
则python会报错type未定义,:
- from ctypes import *
- import types
- class Test(Structure):
- pass
- Test._fields_ = [('x', c_int),
- ('y', c_char),
- ('next', POINTER(Test))]
6. 数组定义
数组包含一个固定数目的相同类型的实例,常用创建数组类型是对一个数据类型与一个正数相乘,例如:
- ArrayType = Test * 10
- 初始化和使用数组:
- <span style="color:#0000ff;">>>> from ctypes import *
- >>> TenIntegers = c_int * 10
- >>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- >>> print ii
- <c_long_Array_10 object at 0x...>
- >>> for i in ii: print i,
- </span>
7. 回调函数
ctypes可以从python可调用对象中创建一个c可调用的函数指针,这些通常被称为回调函数。
(这个等用到时,再仔细研究。。。)
8. 一个例子
c函数代码如下
typedef struct { unsigned long DeviceType; int Handle; int NumberOfClients; int SerialNumber; int MaxAllowedClients; }NeoDevice; int _stdcall icsneoFindNeoDevices(unsigned long DeviceTypes, NeoDevice *pNeoDevices, int *pNumberOfDevices);
python使用如下
class NeoDevice(Structure): _fields_ = [("DeviceType",c_ulong), ("Handle",c_int), ("NumberOfClients",c_int), ("SerialNumber",c_int), ("MaxAllowedClients",c_int)] class cNeoVICan(CCanBase): def __init__(self): neoVi = windll.icsneo40 self.icsneoFindNeoDevices = neoVi.icsneoFindNeoDevices if __name__ == "__main__": canBus = cNeoVICan() print canBus.icsneoGetDLLVersion() iNumberOfDevices = (NeoDevice * 10)() num = c_int() iResult = canBus.icsneoFindNeoDevices(c_ulong(65535), cast(iNumberOfDevices, POINT(NeoDevice)), byref(num))