如何将libxml2生成的XML文档指针转为Python ctypes.Structure
如何将libxml2返回的XML文档指针转换为自定义ctypes结构体xmlDoc
核心问题与修正步骤
你的代码存在几个关键问题,导致无法正确将指针转换为自定义结构体:
函数返回值类型错误
xmlReadFile返回的是xmlDoc*指针,你用ctypes.c_uint32接收会在64位系统下截断指针,必须用ctypes.POINTER(xmlDoc)作为返回类型。指针转换未保存结果
ctypes.cast(pDoc, D)执行后没有将转换后的指针赋值给变量,直接访问D.name是访问类型对象而非实例成员,完全错误。参数名拼写错误
函数参数定义里的filname_是拼写错误,应该是filename_,可能导致参数传递异常。编码参数的NULL处理
如果要传递NULL编码,不能用create_string_buffer,直接传None即可。
修正后的完整代码
# libxml2是用于解析XML文档的软件库 ''' 使用该库而非Python标准XML库,是因为它直接调用标准libxml2 DLL, 文档和节点可以直接与C++对象共享。 ''' import ctypes import sys from PyQt5.QtWidgets import ( QApplication, QFileDialog ) import MessageBox as M class xmlDoc(ctypes.Structure): _fields_ = [ ("_private", ctypes.c_void_p), # 应用程序数据 ("type", ctypes.c_uint16), # XML_DOCUMENT_NODE,必须是第二个字段! ("name", ctypes.c_char_p), # 文档的名称/文件名/URI ("children", ctypes.c_void_p), # 文档树 ("last", ctypes.c_void_p), # 最后一个子节点链接 ("parent", ctypes.c_void_p), # 子节点到父节点的链接 ("next", ctypes.c_void_p), # 下一个兄弟节点链接 ("prev", ctypes.c_void_p), # 上一个兄弟节点链接 ("doc", ctypes.c_void_p), # 自引用,公共部分结束 ("compression", ctypes.c_int), # zlib压缩级别 ("standalone", ctypes.c_int), # 是否为独立文档(无外部引用),1表示standalone="yes",0表示其他 ("intSubset", ctypes.c_void_p), # 文档内部子集 ("extSubset", ctypes.c_void_p), # 文档外部子集 ("oldNs", ctypes.c_void_p), # 全局命名空间(旧方式) ("version", ctypes.c_char_p), # XML版本字符串 ("encoding", ctypes.c_char_p), # 外部初始编码(如果有) ("ids", ctypes.c_void_p), # ID属性的哈希表(如果有) ("refs", ctypes.c_void_p), # IDREFs属性的哈希表(如果有) ("URL", ctypes.c_char_p), # 文档的URI ("charset", ctypes.c_int), # 字符集处理的内部标志,实际是xmlCharEncoding类型 ("dict", ctypes.c_void_p), # 用于分配名称的字典,可为NULL ("psvi", ctypes.c_void_p), # 类型/PSVI信息 ("parseFlags", ctypes.c_int), # 解析文档时使用的xmlParserOption集合 ("properties", ctypes.c_int), # 解析结束时设置的xmlDocProperties集合 ] def xmlReadFile(filename, encoding=None, options=0): # 从文件系统或网络解析XML文件 # 参数: # filename: 文件路径或URL # encoding: 文档编码,传None表示使用默认 # options: xmlParserOption的组合值 # 返回: 文档树指针 # 加载libxml2 DLL,注意:如果是GCC编译的DLL,改用ctypes.CDLL libxml2 = ctypes.WinDLL("C:\\Users\\dholstein\\Documents\\XML\\lib\\libxml2.dll") # 设置函数原型,返回值为xmlDoc指针 xmlReadFile_proto = ctypes.WINFUNCTYPE(ctypes.POINTER(xmlDoc), ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int32) # 参数定义,修复拼写错误的filename_ xmlReadFile_params = ((1, "filename_", 0), (1, "encoding_", 0), (1, "options_", 0)) xmlReadFile_api = xmlReadFile_proto(("xmlReadFile", libxml2), xmlReadFile_params) # 转换参数类型,encoding为None时传NULL c_filename = ctypes.create_string_buffer(filename.encode('utf-8')) if filename else None c_encoding = ctypes.create_string_buffer(encoding.encode('utf-8')) if encoding else None c_options = ctypes.c_int32(options) # 调用函数并返回指针 return xmlReadFile_api(c_filename, c_encoding, c_options) def test(): app = QApplication(sys.argv) FileObj = QFileDialog.getOpenFileName(None, "选择要加载的XML文件", None, "XML (*.xml)") if len(FileObj[0]) > 0: # 读取XML文件,获取xmlDoc指针 doc_ptr = xmlReadFile(FileObj[0], "UTF-8", 0) if doc_ptr: # 通过contents访问指针指向的结构体实例 doc = doc_ptr.contents # 打印文档名称,注意转成Python字符串 print("文档名称:", doc.name.decode('utf-8') if doc.name else "无") else: M.MessageBox(0, "XML文件解析失败", "错误", 0) M.MessageBox(0, "完成", "提示", 0) return test()
关键说明
- 结构体
xmlDoc的字段定义必须与libxml2头文件中的xmlDoc结构体完全一致,否则会出现内存访问错误。 - 调用DLL时要注意调用约定:如果你的libxml2 DLL是用GCC编译的,将
ctypes.WinDLL改为ctypes.CDLL,避免调用栈损坏。 - 访问结构体成员时,指针类型必须通过
contents属性获取实际的结构体实例,比如doc_ptr.contents.name。 - 字符串类型的成员(如
name、encoding)是C风格字符串,需要用decode('utf-8')转换为Python字符串。
内容的提问来源于stack exchange,提问作者Danny Holstein
相关产品推荐
相关产品推荐

