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

时间:2018-11-07 02:20:51

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

我正在尝试将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位上
    我不打印“句柄在这里” output2

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

  • 3 个答案:

    答案 0 :(得分:3)

    99%的情况下,参数(和/或返回)类型不一致之间的不一致是导致这种情况的原因([SO]: Python ctypes cdll.LoadLibrary, instantiate an object, execute its method, private variable address truncated (@CristiFati's answer)是这样的一个例子)。

    使用 ctypes 时,总是打开[Python 3]: ctypes - A foreign function library for Python

    我发现了[GitHub]: erikssm/futronics-fingerprint-reader - (master) futronics-fingerprint-reader/ftrScanAPI.h(我不知道它与您当前的有什么不同,但是到目前为止您发布的内容似乎匹配),并且我对代码做了一些更改:

    • 为函数定义 argtypes restype
    • 定义缺失的类型(仅出于清晰目的)
    • 其他一些微不足道的更改(重命名)
    • 我在上面的文件中注意到的另一件事是一个#pragma pack(push, 1)宏(有关更多详细信息,请检查[MSDN]: pack)。对于此结构,它没有任何区别(感谢@AnttiHaapala作为提示),因为3个 int 4 字节)成员的对齐方式没有变化,但是对于其他结构(拥有“较小”成员类型(例如 char short )的用户,可能需要添加:_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...')
    

    答案 1 :(得分:2)

    您应该显示.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 :(得分:2)

    问题似乎仅仅是设备句柄是64位实体(类型定义的指向void的指针)。它可以在Windows 7中正常工作只是偶然,因为句柄的高33位正确为零。

    ctypes中所有函数的返回类型默认为32位int。现在,在Windows 10中,似乎已经设置了第32位(符号位),当将handle-coerced-to-int推入64位堆栈进行函数调用时,这会导致符号扩展。结果地址的所有高位都设置为(0xFFFFFFFFA ...),它指向内核空间而不是用户空间。

    因此,也许您只需使用即可使代码“运行”

    lib.ftrScanOpenDevice.restype = c_void_p
    

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