解码ctypes结构

3 投票
1 回答
2034 浏览
提问于 2025-04-16 19:25

我正在学习Python中的ctypes模块,虽然我能搞定一些简单的事情,但在解包C语言的结构体时遇到了一些困难。我决定通过动手实践来学习这个知识。虽然我知道socket标准库里有一个叫gethostbyname_ex()的函数,但我想尝试用ctypeslibc.gethostbyname()自己实现一下。

我能比较轻松地执行libc.gethostbyname()这个函数:

#!/usr/bin/env python
from ctypes import *

cdll.LoadLibrary('libc.so.6')
libc = CDLL('libc.so.6')
he = libc.gethostbyname("www.google.com")

但是这会给我一个hostent的数据结构。我想解包这个结构体的最好方法是抓取C语言的结构体,然后创建一个继承自ctypes.Structure的类,所以我写了这个(我在netdb.h中找到了hostent结构体的定义):

class hostent(Structure):
    '''
    struct hostent
    {
      char *h_name;                 /* Official name of host.  */
      char **h_aliases;             /* Alias list.  */
      int h_addrtype;               /* Host address type.  */
      int h_length;                 /* Length of address.  */
      char **h_addr_list;           /* List of addresses from name server. */
    }
    '''
    _fields_ = [("h_name", c_char_p), ("h_aliases", POINTER(c_char_p)),
                ("h_addrtype", c_int), ("h_length", c_int),
                ("h_addr_list", POINTER(c_char_p))]

我不太确定我是否正确设置了h_aliasesh_addr_list这两个字段,因为每当我尝试将它们作为数组访问时,即使是在查找某个我知道至少有一个别名和一个地址的情况下,访问第0个索引时,我都会遇到一个空指针访问的ValueError异常:

>>> he = hostent(libc.gethostbyname("www.google.com"))
>>> pprint.pprint(he.h_addr_list)
<__main__.LP_c_char_p object at 0xb75dae84>
>>> print he.h_addr_list[0]
Traceback (most recent call last):
  File "/tmp/py2659JxK", line 24, in <module>
    print he.h_addr_list[0]
ValueError: NULL pointer access

任何建议都非常欢迎。

1 个回答

5

你需要先弄清楚gethostbyname这个函数的返回类型是什么:

>>> libc.gethostbyname.restype = POINTER(hostent)
>>> he = libc.gethostbyname("www.google.com")[0]
>>> he.h_aliases[0]
'www.google.com'

另外,h_addr_list不应该声明为POINTER(c_char_p),因为c_char_p是用来处理以空字符结尾的字符串的。在这种情况下,使用POINTER(POINTER(c_ubyte))会更合适。如果是IPv4地址,第一个地址可以通过he.h_addr_list[0][:4]来获取。

撰写回答