【问题标题】:python ctypes issue on different OSes不同操作系统上的python ctypes问题
【发布时间】:2019-04-10 11:44:27
【问题描述】:

我正在尝试将 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 位上

  • 在 Windows 10 64 位上
    我不打印“句柄在这里”

    我尝试过的:
    根据堆栈溢出的一些答案,这可能是由于没有明确分配函数 argtypes 和 restype 造成的,所以我尝试并失败了。

  • 【问题讨论】:

    • 你能贴出代码吗?
    • 我重新编辑了帖子,看看有没有不清楚的地方?谢谢
    • 是的,你可能是对的。你的 python 是 64 位的?您能否同时发布 2 个函数原型(在 C 中)以及 FTRSCAN_IMAGE_SIZE?我怀疑hDevice = lib.ftrScanOpenDevice() 截断了这个值。 stackoverflow.com/questions/52268294/…。还请发布您分配 argtypesrestype 的代码块。
    • a) 请 no 图片。您必须将输出作为文本复制到您的问题中。第二个输出也与您的代码不匹配! (不打印句柄!)

    标签: python c python-3.x windows ctypes


    【解决方案1】:

    99% 的情况下,参数(和/或返回)类型不一致是原因(查看[SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) 了解更多详情)。

    在使用 ctypes 时,始终打开 [Python 3.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 个 int4 字节)成员对齐方式不会改变,但对于其他结构(使用“较小”的成员类型(例如 charshort))您可能想要添加:_pack_ = 1

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

    from ctypes import wintypes
    
    # ...
    
    lib = ctypes.WinDLL('ftrScanAPI.dll') # provided by fingerprint scanner
    class FTRSCAN_IMAGE_SIZE(ctypes.Structure):
        # _pack_ = 1
        _fields_ = [
            ("nWidth", ctypes.c_int),
            ("nHeight", ctypes.c_int),
            ("nImageSize", ctypes.c_int),
        ]
    
    PFTRSCAN_IMAGE_SIZE = ctypes.POINTER(FTRSCAN_IMAGE_SIZE)
    FTRHANDLE = ctypes.c_void_p
    
    print('Open device and get device handle...')
    
    lib.ftrScanOpenDevice.argtypes = []
    lib.ftrScanOpenDevice.restype = FTRHANDLE
    
    h_device = lib.ftrScanOpenDevice()
    print('handle is', h_device)
    print('Get image size...')
    image_size = FTRSCAN_IMAGE_SIZE(0, 0, 0)
    
    lib.ftrScanGetImageSize.argtypes = [FTRHANDLE, PFTRSCAN_IMAGE_SIZE]
    lib.ftrScanGetImageSize.restype = wintypes.BOOL
    
    if lib.ftrScanGetImageSize(h_device, 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...')
    

    【讨论】:

    • 应该不需要打包,因为那个结构已经正确打包了
    • @AnttiHaapala:感谢您的评论。我相应地更新了我的答案。
    【解决方案2】:

    问题似乎只是设备句柄是一个 64 位实体(一个 typedef 指向 void 的指针)。它在您的 Windows 7 中工作只是一个侥幸,因为句柄的高 33 位正确为零。

    ctypes中所有函数的返回类型默认为32位int。现在在 Windows 10 中,似乎设置了第 32 位(符号位),当句柄强制转换为 int 被推入 64 位堆栈以进行函数调用时,这会导致某处的符号扩展。结果地址设置了所有高位 (0xFFFFFFFFA...),指向内核空间而不是用户空间。

    因此,也许您可​​以让您的代码“正常工作”

    lib.ftrScanOpenDevice.restype = c_void_p
    

    这并不是说你不应该为 all 函数定义参数和返回类型 - 你应该,否则它们将遵循 C 语言的默认参数提升规则,并且可以工作... 或完全无法工作,具体取决于函数的原型本身是否与默认参数提升兼容。例如,任何接受 floats 作为参数的函数都不能在不定义原型的情况下正确调用。

    【讨论】:

      【解决方案3】:

      您应该显示您的 .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 链接,应该使用 __stdcallWinDLL

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-03
        • 2011-10-14
        • 1970-01-01
        • 2023-03-27
        • 1970-01-01
        相关资源
        最近更新 更多