相关文章推荐
虚心的梨子  ·  mac ...·  6 月前    · 
坏坏的斑马  ·  [Solved] Unreachable ...·  11 月前    · 

当一个C函数返回一个动态数组时,Python ctypes会发生错误行为

0 人关注

我正在为Matlab的动态库开发Python包装类,以便在Python中读取Matlab的MAT文件,我遇到了一个奇怪的行为,我无法从ctypes接口中解释。

C语言的函数签名看起来像这样。

    const mwSize *mxGetDimensions(const mxArray *);

这里,mwSize是一个重命名的size_t,而mxArray*是一个不透明的指针。这个函数返回Matlab数组的 "形状"。返回的指针指向size_t数组,该数组内部存储在mxArray对象中,是not空终止(其大小通过另一个函数获得)。

为了从Python中调用这个函数,我对库的设置如下。

    libmx = ctypes.cdll.LoadLibrary('libmx.dll')
    libmx.mxGetDimensions.restype = ctypes.POINTER(ctypes.c_size_t)
    libmx.mxGetDimensions.argtypes = [ctypes.c_void_p]

并在获得mxArray*中的VAR后,我打电话给。

    dims = libmx.mxGetDimensions(VAR)
    print(dims[0],dims[1])

VAR已知是二维的,形状是(1, 13)(用C程序验证),但是我的Python代码在c_ulonglong中返回(55834574849 0)。在测试MAT文件中存储的所有变量中,结果是一致的垃圾。

我做错了什么?使用 VAR 的其他库调用似乎工作正常,因此 VAR 指向了有效对象。如上所述,在 C 语言程序中调用 mxGetDimensions() 可以正常工作。

如有任何意见,敬请指教。谢谢

2 个评论
Well, actually converting 55834574849 to hex gives the answer :) 55834574849 -> 0xD 0000 0001 so it seems that it's an array of uint32_t . Try with a restype set to ctypes.POINTER(ctypes.c_uint32) and tell us if it works.
kesh
@Neitsa - Beautiful. That was it. It is curious how the discrepancy came about in Python & C calls, though. In my C test code, I verified that size_t is indeed uint64_t... Anyways, thank you! (If you could repost it as an answer, I'll accept it for your credit)
python
ctypes
kesh
kesh
发布于 2019-09-20
1 个回答
kesh
kesh
发布于 2019-09-21
已采纳
0 人赞同

@Neitsa solved my immediate issue in his comment under the OP, and further investigation of libmx.dll resolved the remaining discrepancy between Python & C versions.

因为Matlab的libmx.dll可以追溯到很久之前,因为它最早是在32位时代编写的,为了向后兼容,DLL包含了其函数的多个版本。结果发现, const mwSize *mxGetDimensions(const mxArray *); 是该函数的最老版本,而相关的C头文件(matrix.h)中有一行 #define mxGetDimensions mxGetDimensions_800 可以用其最新版本覆盖该函数。很明显,Python 的 ctypes 没有检查 C 头文件;所以,我只能通过头文件来找出要使用的函数版本。

最后,当我把代码改为 POINTER(c_size_t) 时,得到了正确的行为。

    libmx = ctypes.cdll.LoadLibrary('libmx.dll')
    libmx.mxGetDimensions_800.restype = ctypes.POINTER(ctypes.c_size_t)
    libmx.mxGetDimensions_800.argtypes = [ctypes.c_void_p]