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

2018-11-03  本文已影响35人  杨强AT南京

  这个主题专门针对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

上一篇下一篇

猜你喜欢

热点阅读