美文网首页
05.Python与C桥接编程-数据类型

05.Python与C桥接编程-数据类型

作者: 杨强AT南京 | 来源:发表于2018-11-03 01:41 被阅读35次

      这个主题专门针对Python的数据类型与C类型做一个对应。很多系统层面的C的开发基本上在Python中可以得到对应的移植应用。
      比如:在网络通信中,解析IP,UDP等协议包的时候,会用到以位为单位的内存操作。

    本主题的内容:

    1. 把C中以为单位的内存中数据转换为Python中对应数据;
    2. 读取网络的IP数据包,并使用Python解析数据包中每个对应数据。

    准备

      下面是IP协议数据包格式:

    IP数据包格式说明

    一. ctypes类型、C类型与Python类型

    1. 类型对应表

    ctypes type C type Python type
    c_bool _Bool bool (1)
    c_char char 1-character bytes object
    c_wchar wchar_t 1-character string
    c_byte char int
    c_ubyte unsigned char int
    c_short short int
    c_ushort unsigned short int
    c_int int int
    c_uint unsigned int int
    c_long long int
    c_ulong unsigned long int
    c_longlong __int64 or long long int
    c_ulonglong unsigned __int64 or unsigned long long int
    c_size_t size_t int
    c_ssize_t ssize_t or Py_ssize_t int
    c_float float float
    c_double double float
    c_longdouble long double float
    c_char_p char * (NUL terminated) bytes object or None
    c_wchar_p wchar_t * (NUL terminated) string or None
    c_void_p void * int or None

    注意:
      1. 上面类型还有别名,比如c_byte的别名就是class ctypes.c_uint8,别名体现了位数。
      2. 上面所有类型都继承class ctypes._SimpleCData,该类只有唯一的属性value

    2. ctype类型使用例子

      代码1:整数

      import ctypes
      v = ctypes.c_int ( 2 )
      print ( v )
      print ( v.value )
    

      结果1:

    c_int(2)
    2
    

      代码2:字符串

      s = ctypes.c_wchar_p  ("这是一个字符串" )
      print ( s )
      print ( s.value )
    

      结果2:

    c_wchar_p(4509017776)
    这是一个字符串
    

      ctypes类型是Python类,可以转换为Python内置类型,该类的主要作用就是就是可以控制Python数据的位数。可以构造2个字节的整数等,这样对数据进行更加精确的操作。

    3. 从字节序列中拷贝

      每个对应的类型还提供静态方法来产生对应的ctypes数据类型,这些静态方法来自所有ctypes类型的根类_CData类,该类不是public的,但其方法可以通过继承的方式在ctypes的每个类型中直接使用:

    函数 函数说明
    from_buffer(source[, offset]) 共享字节序列,产生对应的数据
    from_buffer_copy(source[, offset]) 从字节序列中拷贝,产生对应的数据
    from_address(address) 从一个地址产生对应的数据
    from_param(obj) 把一个python的数据obj,适配成C的参数
    in_dll(library, name) 加载在共享库中定义的变量

      代码:(从动态库与地址加载例子暂时不列出来)

    #从其他字节序列中拷贝
    v2=ctypes.c_int.from_buffer(v,0)
    print(v2,v2.value)
    a=20
    v3=ctypes.c_int.from_param(a)
    print(v3)
    

      结果:

    c_int(2) 2
    <cparam 'i' (20)>
    

    提示:
      1. 有了ctypes,python的数据可以采用更优的内存存储,比如在Python没有1字节或者2字节整数,使用ctypes就可以产生2字节整数(类型为c_short或者c_int16),这是Python类型没有办法实现的事情。
      2. 更加有用的是,我们可以利用from_buffer函数直接使用2字节转换成整数(在后面的综合例子中可以说明)。
      3. ctypes类型可以作为字节序列使用。


    二、在Python中使用结构体

      Python中没有结构体,通过结构体,可以批量存储不同字节的数据,尤其在把字节序列批量转换成多个数据的时候特别有用,比如把IP数据包按照格式读取特别方便。同时Structure的父类也是_CData,也可以调用from_buffer等函数。

    1. 使用python内置数据构造结构体

      构造Python结构体,遵循如下几个步骤:
        |-继承ctypes.Structure类;
        |-定义结构体每个字段的名字与类型
        |-构造结构体对象
        |-访问结构体中数据
      其中字段的定义在fields中定义。fields是个list类型,每个元素由元组构成('字段名',类型,位数)
      代码:

    import ctypes
    #结构体
    class Point(ctypes.Structure):
        _fields_=[ ("x", ctypes.c_int),
                   ("y", ctypes.c_byte, 4)]
    
    p=Point(200,5)
    print(p)
    print(p.x,p.y)
    

      结果:

      <__main__.Point object at 0x10d6a72f0>
      200   5
    

    2. 使用字节序列构造结构体

      使用字节序列构造结构体,需要把字节序列拷贝到结构体,或者共享字节序列空间,可以使用两种方式:
        |-方式一:覆盖__new__,定制内存存储方式
        |-方式二:使用from_buffer函数直接返回。
      实际上上述两种方式都要使用from_buffer构造存储空间。
      方式一实现代码:

    import ctypes
    #结构体
    class Point(ctypes.Structure):
        _fields_=[("x",ctypes.c_byte,4),("y",ctypes.c_byte,4)]
        def __new__(self, buf):
            return self.from_buffer(buf,0)
    
    a=ctypes.c_byte(20)
    p2=Point(a)
    print(p2.x,p2.y)
    
    #p3=Point(2,2)  #这种方式就不能再使用,参数个数无法匹配
    

      方式一结果:

    4 1
    

      方式二实现代码:

    import ctypes
    #结构体
    class Point(ctypes.Structure):
        _fields_=[("x",ctypes.c_byte,4),("y",ctypes.c_byte,4)]
        
    a=ctypes.c_byte(20)
    p2=Point.from_buffer(a,0)
    print(p2)
    print(p2.x,p2.y)
    

      方式二结果:

      <__main__.Point object at 0x107c171e0>
      4 1
    

      y=1在高位第5个位,等于2^4x=4在低位,大家可以再回顾下二进制运算的游戏规则。

      方式一还可以在init构造器中对字段数据进行处理:

    import ctypes
    #结构体
    class Point(ctypes.Structure):
        _fields_=[("x",ctypes.c_byte,4),("y",ctypes.c_byte,4)]
        def __new__(self, buf):
            return self.from_buffer(buf,0)
        #参数与__new__保持一致
        def __init__(self,buf):
            self.x=6
    
    a=ctypes.c_byte(20)
    p2=Point(a)
    print(p2.x,p2.y)
    

      运行结果:

    6 1
    

    3. 使用结构体解析IP数据包

      注意:下面代码在Mac OS与Linux系统下运行,需要root用户。

    #!/usr/bin/python
    #coding=utf-8
    
    import socket
    import  struct
    from ctypes import *
    
    class IP(Structure):
        _fields_ = [
            ("ihl",             c_ubyte, 4),
            ("version",         c_ubyte, 4),
            ("tos",             c_ubyte),
            ("len",             c_ushort),
            ("id",              c_ushort),
            ("offset",          c_ushort),
            ("ttl",             c_ubyte),
            ("protocol_num",    c_ubyte),
            ("sum",             c_ushort),
            ("src",             c_uint),
            ("dst",             c_uint),
        ]
        def __new__(self, socket_buffer=None):
            return self.from_buffer_copy(socket_buffer)
        def __init__(self, socket_buffer=None):
            self.src_address = socket.inet_ntoa(struct.pack("<I", self.src))
            self.dst_address = socket.inet_ntoa(struct.pack("<I", self.dst))
            self.ver=self.version
    sk=socket.socket(socket.AF_INET,socket.SOCK_RAW,0)
    while True:
        buf=sk.recv(2048,0)
        ip_header = IP(buf[:20]) #可以使用from_buffer
        print(ip_header.src_address)
        print(ip_header.dst_address)
        print(ip_header.ver)
    

      说明:
      (1)其中使用struct在Python内置类型与字节序列的转换是另外一个话题,在单独的篇幅说明。
      (2)关于C动态库,函数指针,也使用单独的篇幅来说明。


    资源

    本主题代码列表:
      |-ctypes01_type.py
      |-ctypes02_struct.py
      |-ctypes03_struct.py
      |-ctypes04_struct_new.py
      |-ctypes05_struct_init.py
    下载地址:
    https://github.com/QiangAI/PythonSkill/tree/master/AdvPython/04ctypes

    相关文章

      网友评论

          本文标题:05.Python与C桥接编程-数据类型

          本文链接:https://www.haomeiwen.com/subject/cidhxqtx.html