不同操作系统上的Python ctypes问题

5

我将尝试将C函数转换为适用于Python 3.6的函数。

以下是代码:

lib = ctypes.WinDLL('ftrScanAPI.dll') # provided by fingerprint scanner
class FTRSCAN_IMAGE_SIZE(ctypes.Structure):
    _fields_ = [
    ("nWidth", ctypes.c_int),
    ("nHeight", ctypes.c_int),
    ("nImageSize", ctypes.c_int)
]

print('Open device and get device handle...')
hDevice = lib.ftrScanOpenDevice()
print('handle is', hDevice)
print('Get image size...')
Image_size = FTRSCAN_IMAGE_SIZE(0, 0, 0)
if lib.ftrScanGetImageSize(hDevice, ctypes.byref(Image_size)):
    print('Get image size succeed...')
    print('  W', Image_size.nWidth)
    print('  H', Image_size.nHeight)
    print('  Size', Image_size.nImageSize)
else:
    print('Get image size failed...')

函数定义:

typedef struct FTR_PACKED __FTRSCAN_IMAGE_SIZE {
    int nWidth;
    int nHeight;
    int nImageSize;
} FTRSCAN_IMAGE_SIZE, *PFTRSCAN_IMAGE_SIZE;
FTRHANDLE ftrScanOpenDevice();  # typedef void *  FTRHANDLE;
BOOL ftrScanGetImageSize(FTRHANDLE ftrHandle, 
    PFTR_SCAN_IMAGE_SIZE pImageSize);

然而,相同代码的不同操作系统似乎会产生不同的结果:

  • 在Windows 7 64位上
    output1
  • 在Windows 10 64位上
    我没有打印“handle is here” output2
  • 我的尝试:
    根据stackoverflow上的一些答案,这可能是由于没有明确分配函数argtypes和restype,所以我尝试过并失败了。


    你能发布代码吗? - 101
    我已重新编辑了帖子,看看是否有任何不清楚的陈述?谢谢。 - newman
    是的,你可能是对的。你的Python是64位的吗?你能否也发布两个函数原型(用C语言编写),以及FTRSCAN_IMAGE_SIZE?我怀疑hDevice = lib.ftrScanOpenDevice()截断了该值。https://dev59.com/Ea7la4cB1Zd3GeqPaky4#52272969。请还发布一下你分配argtypes和restype的代码块。 - CristiFati
    1
    请不要使用图片。您必须将输出作为文本复制到问题中。另外,第二个输出与您的代码不匹配!(没有打印句柄!) - Antti Haapala -- Слава Україні
    3个回答

    5
    在99%的情况下,参数(和/或返回)类型不一致是导致问题的原因(请查看[SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)以获取更多详细信息)。
    在使用CTypes时,请始终打开[Python.Docs]: ctypes - A foreign function library for Python
    我找到了[GitHub]: erikssm/futronics-fingerprint-reader - (master) futronics-fingerprint-reader/ftrScanAPI.h(我不知道它与您当前拥有的有何不同,但迄今为止您发布的内容似乎匹配),并对您的代码进行了一些更改:
    • 为函数定义argtypesrestype

    • 定义缺失的类型(仅为了清晰起见)

    • 一些其他微不足道的更改(重命名)

    • 在上述文件中我注意到的另一件事是一个#pragma pack(push, 1)宏(更多详情请参阅[MS.Docs]: pack)。对于这个结构来说,这没有任何区别(感谢 @AnttiHaapala 的提示),因为3个int(4字节)成员的对齐方式并未改变,但对于其他结构(带有“较小”成员类型,如charshort),你可能想添加:_pack_ = 1

    你修改后的代码(不用多说,我没有运行它,因为我没有.dll文件):

    #!/usr/bin/env python
    
    import ctypes as cts
    from ctypes import wintypes as wts
    
    
    # ...
    
    
    class FTRSCAN_IMAGE_SIZE(cts.Structure):
        # _pack_ = 1
        _fields_ = (
            ("nWidth", cts.c_int),
            ("nHeight", cts.c_int),
            ("nImageSize", cts.c_int),
        )
    
    PFTRSCAN_IMAGE_SIZE = cts.POINTER(FTRSCAN_IMAGE_SIZE)
    FTRHANDLE = cts.c_void_p
    
    lib = cts.WinDLL("ftrScanAPI.dll")  # provided by fingerprint scanner
    
    ftrScanOpenDevice = lib.ftrScanOpenDevice
    ftrScanOpenDevice.argtypes = ()
    ftrScanOpenDevice.restype = FTRHANDLE
    
    ftrScanGetImageSize = lib.ftrScanGetImageSize
    ftrScanGetImageSize.argtypes = (FTRHANDLE, PFTRSCAN_IMAGE_SIZE)
    ftrScanGetImageSize.restype = wts.BOOL
    
    print("Open device and get device handle...")
    h_device = ftrScanOpenDevice()
    print("Handle is:", h_device)
    print("Get image size...")
    image_size = FTRSCAN_IMAGE_SIZE(0, 0, 0)
    
    if ftrScanGetImageSize(h_device, cts.byref(image_size)):
        print("Get image size succeed...")
        print("  W:", image_size.nWidth)
        print("  H:", image_size.nHeight)
        print("  Size:", image_size.nImageSize)
    else:
        print("Get image size failed...")
    

    不应该需要打包,因为那个结构已经被正确地打包了。 - Antti Haapala -- Слава Україні
    @AnttiHaapala:感谢您的评论。我已相应更新了我的答案。 - CristiFati

    2
    你应该展示你的.argtypes和.restype尝试,但试试这个:

    您应该展示您的.argtypes和.restype尝试,但尝试一下这个:

    from ctypes import wintypes as w
    
    class FTRSCAN_IMAGE_SIZE(ctypes.Structure):
        _fields_ = [('nWidth', ctypes.c_int),
                    ('nHeight', ctypes.c_int),
                    ('nImageSize', ctypes.c_int)]
    
    FTRHANDLE = ctypes.c_void_p
    
    lib = ctypes.WinDLL('ftrScanAPI.dll')
    lib.ftrScanOpenDevice.argtypes = None
    lib.ftrScanOpenDevice.restype = FTRHANDLE
    lib.ftrScanGetImageSize.argtypes = FTRHANDLE,ctypes.POINTER(FTRSCAN_IMAGE_SIZE)
    lib.ftrScanGetImageSize.restype = w.BOOL
    

    检查使用WinDLLCDLL的区别。如果你的Python是64位的,那么这并不重要,但在32位的Python上会有所不同。如果函数使用C调用约定(__cdecl),则使用CDLL,如果函数使用__stdcall调用约定,则使用WinDLL。如果头文件不清楚,默认通常为__cdecl。编辑:从另一个答案中的API链接中可以看出,它是__stdcall,应该使用WinDLL


    2
    问题似乎只是设备句柄是一个64位实体(void指针的typedef)。在Windows 7中它能够工作只是偶然,因为句柄的上33位被正确地设置为零。所有ctypes函数的返回类型默认为32位int。现在在Windows 10中,似乎第32位(符号位)被设置了,在将句柄强制转换为int并将其推入64位堆栈以进行函数调用时,会发生符号扩展。结果地址的所有上部位都被设置为0xFFFFFFFFA...,这指向内核空间而不是用户空间。因此,也许你可以通过以下方式使你的代码“工作”:
    lib.ftrScanOpenDevice.restype = c_void_p
    

    这并不意味着你不应该为所有函数定义参数和返回类型 - 你应该这样做,否则它们将遵循 C 语言的默认参数提升规则,并且会工作...或者完全无法工作,这取决于函数原型本身是否与默认参数提升兼容。例如,任何接受float作为参数的函数都必须在定义原型之后才能正确调用。


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