Python ctypes指向结构体的指针

3

我在获取指向结构体的指针时遇到了问题。这是我的代码,它抛出异常“ArgumentError:argument 1::预期 LP_LP_List 实例,而不是指向 LP_LP_List 的指针。”

class List(Structure):
    _fields_ = (
        ('head', POINTER(Node)),
        ('tail', POINTER(Node)),
        ('current', POINTER(Node)),
        ('saved', POINTER(Node)),
        ('infosize', c_int),
        ('listsize', c_ulong),
        ('current_index', c_ulong),
        ('save_index', c_ulong),
        ('modified', c_bool),
        ('search_origin', c_int),
        ('search_dir', c_int),
        )

list_p = POINTER(List)

create = lib.DLL_CreateList
create.argtypes = [POINTER(POINTER(List)),]
create.restype = POINTER(List)
mem = POINTER(list_p)()
retval = create(byref(mem))

这似乎是按照推荐的方式进行操作,但并不起作用。

感谢任何帮助。

对于那些不想阅读下面所有细节以找到解决方案的人来说,最后一部分应该像这样:

#list_p = POINTER(List)  # Not needed

create = lib.DLL_CreateList
create.argtypes = [POINTER(POINTER(List)),]
create.restype = POINTER(List)
control = POINTER(List)()
list_p = create(byref(control))

它“不工作”的方式是什么? - dstromberg
我编辑了上面的内容,包括完整的异常信息但不包括回溯信息。 - cnobile
如果您将创建的列表作为函数返回值返回,为什么要通过引用传递列表指针?“create”实际上是做什么的? - PaulMcG
我正在使用Python编写单元测试,针对我在12年前编写的Link List API进行测试。该API被数千人使用,并且每周仍在下载。DLL_CreateList(List **list)函数创建一个中央控制结构,所有链表节点都可以从中进行控制。想法是将空指针传递给控制结构的指针。然后它会在内存中创建链表控制结构。下一次调用将附加所需的数据结构到控制结构,然后可以通过其他调用进行复制。http://tetrasys-design.net/download/Linklist/linklist-1.2.1.tar.gz - cnobile
1个回答

5
API听起来像是通过引用传递的指针将被"create"修改,因此必须通过引用传递,并且返回值是新创建的列表。
如果这是用"C"语言编写的,我猜你会有类似以下代码:
List *create(List **control);
List *control;
List *plist;
plist = create(&control);

使用ctypes/Python,这将映射为:
create.argtypes = [POINTER(POINTER(List)),]
create.restype = POINTER(List)
control = POINTER(List)()
newlist = create(byref(control))

这样会更好吗?

编辑: 由于create函数不仅修改传入的参数,而且返回创建的列表,因此我们可以忽略返回值,只需保留参数即可:

create.argtypes = [POINTER(POINTER(List)),]
control = Pointer(List)()
create(byref(control))

为了完善您的Python API,您可以考虑编写一个上下文管理器,以便在调用Create(当上下文管理器的__enter__方法由with语句调用以初始化列表控制块时)并自动调用Destroy(当上下文管理器的__exit__方法在with语句的作用域结束时被调用)时进行处理。然后您的Python代码可能如下所示:

with listManager as ListManager():
    # do stuff with list managed by list manager

# end of with statement, listManager.__exit__ is automatically called, so that Destroy always
# gets called, even if an exception is raised - this is easier for API users than expecting them
# to write their own try-finally code, and put Destroy in the finally block

编辑:- 我在Ubuntu上构建了您的DLL,并使用上述Python代码,看起来它确实创建了一个列表。

~/dev/python$ python
Python 2.6.6 (r266:84292, Sep 15 2010, 16:22:56) 
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import *
>>> 
>>> class List(Structure): pass
... 
>>> lib = CDLL('./libdll.so')
>>> create = lib.DLL_CreateList
>>> create.argtypes = [POINTER(POINTER(List)),] 
>>> create.restype = POINTER(List) 
>>> 
>>> control = POINTER(List)() 
>>> create(byref(control))
<__main__.LP_List object at 0x7fdc0c607e60>

编辑 - 我编写了一个 pyparsing 工具,可以读取 C 头文件并输出占位符结构子类和函数 argtypes 和 restype 定义。以下是我为您的 lib 所获得的结果 - 虽然未经过测试,但可能会让您的 API 测试起步更容易:

from ctypes import *
ll = CDLL("./libdll.so")

# user defined types
class ll_Info(Structure): pass
class ll_DLL_Boolean(Structure): pass
class ll_DLL_SrchOrigin(Structure): pass
class sys_select_fd_set(Structure): pass
class ll_List(Structure): pass
class sys_time_timeval(Structure): pass
class ll_DLL_SrchDir(Structure): pass
class ll_DLL_Return(Structure): pass
class ll_DLL_SearchModes(Structure): pass
class ll_DLL_InsertDir(Structure): pass

# functions
ll.DLL_CreateList.restype = POINTER(ll_List)
ll.DLL_CreateList.argtypes = (POINTER(POINTER(ll_List)),)
ll.DLL_DestroyList.restype = None
ll.DLL_DestroyList.argtypes = (POINTER(POINTER(ll_List)),)
ll.DLL_Version.restype = c_char_p
ll.DLL_Version.argtypes = ()
ll.DLL_IsListEmpty.restype = ll_DLL_Boolean
ll.DLL_IsListEmpty.argtypes = (POINTER(ll_List),)
ll.DLL_IsListFull.restype = ll_DLL_Boolean
ll.DLL_IsListFull.argtypes = (POINTER(ll_List),)
ll.DLL_CurrentPointerToHead.restype = ll_DLL_Return
ll.DLL_CurrentPointerToHead.argtypes = (POINTER(ll_List),)
ll.DLL_CurrentPointerToTail.restype = ll_DLL_Return
ll.DLL_CurrentPointerToTail.argtypes = (POINTER(ll_List),)
ll.DLL_DecrementCurrentPointer.restype = ll_DLL_Return
ll.DLL_DecrementCurrentPointer.argtypes = (POINTER(ll_List),)
ll.DLL_DeleteCurrentRecord.restype = ll_DLL_Return
ll.DLL_DeleteCurrentRecord.argtypes = (POINTER(ll_List),)
ll.DLL_DeleteEntireList.restype = ll_DLL_Return
ll.DLL_DeleteEntireList.argtypes = (POINTER(ll_List),)
ll.DLL_FindNthRecord.restype = ll_DLL_Return
ll.DLL_FindNthRecord.argtypes = (POINTER(ll_List),POINTER(ll_Info),c_ulong,)
ll.DLL_GetCurrentRecord.restype = ll_DLL_Return
ll.DLL_GetCurrentRecord.argtypes = (POINTER(ll_List),POINTER(ll_Info),)
ll.DLL_GetNextRecord.restype = ll_DLL_Return
ll.DLL_GetNextRecord.argtypes = (POINTER(ll_List),POINTER(ll_Info),)
ll.DLL_GetPriorRecord.restype = ll_DLL_Return
ll.DLL_GetPriorRecord.argtypes = (POINTER(ll_List),POINTER(ll_Info),)
ll.DLL_InitializeList.restype = ll_DLL_Return
ll.DLL_InitializeList.argtypes = (POINTER(ll_List),c_size_t,)
ll.DLL_IncrementCurrentPointer.restype = ll_DLL_Return
ll.DLL_IncrementCurrentPointer.argtypes = (POINTER(ll_List),)
ll.DLL_InsertRecord.restype = ll_DLL_Return
ll.DLL_InsertRecord.argtypes = (POINTER(ll_List),POINTER(ll_Info),ll_DLL_InsertDir,)
ll.DLL_RestoreCurrentPointer.restype = ll_DLL_Return
ll.DLL_RestoreCurrentPointer.argtypes = (POINTER(ll_List),)
ll.DLL_SaveList.restype = ll_DLL_Return
ll.DLL_SaveList.argtypes = (POINTER(ll_List),c_char_p,)
ll.DLL_SetSearchModes.restype = ll_DLL_Return
ll.DLL_SetSearchModes.argtypes = (POINTER(ll_List),ll_DLL_SrchOrigin,ll_DLL_SrchDir,)
ll.DLL_StoreCurrentPointer.restype = ll_DLL_Return
ll.DLL_StoreCurrentPointer.argtypes = (POINTER(ll_List),)
ll.DLL_SwapRecord.restype = ll_DLL_Return
ll.DLL_SwapRecord.argtypes = (POINTER(ll_List),ll_DLL_InsertDir,)
ll.DLL_UpdateCurrentRecord.restype = ll_DLL_Return
ll.DLL_UpdateCurrentRecord.argtypes = (POINTER(ll_List),POINTER(ll_Info),)
ll.DLL_GetSearchModes.restype = POINTER(ll_DLL_SearchModes)
ll.DLL_GetSearchModes.argtypes = (POINTER(ll_List),POINTER(ll_DLL_SearchModes),)
ll.DLL_GetCurrentIndex.restype = c_ulong
ll.DLL_GetCurrentIndex.argtypes = (POINTER(ll_List),)
ll.DLL_GetNumberOfRecords.restype = c_ulong
ll.DLL_GetNumberOfRecords.argtypes = (POINTER(ll_List),)

保留传入的参数通常是人们所做的,但我收到了将其传递出函数的请求,但通常会被丢弃。我已经尝试过你的建议,但它也没有起作用。newlist = Pointer(List)() <-- 这是从哪里来的? - cnobile
好的,第一次肯定是做错了什么。我移动了一些代码并让它正常工作了。control = POINTER(List)() newlist = create(byref(control)) 现在可以使用了。谢谢。 - cnobile
是的,就像我之前提到的那样,它可以正常工作。第一次尝试时可能是我做错了什么,但现在它能够正常运行。再次感谢您的帮助。 - cnobile
哇,非常感谢,这可能非常有帮助。在过去的几个月中,这个API在FreshMeat / freecode上的受欢迎程度出现了最近的激增。我倾向于最近做大部分的Python而不是C。然而,最近有人在DLL_AddRecord()函数中发现了一个12年的bug,这让我想编写单元测试。再次感谢。 - cnobile

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接