python ctypes issue on different OSes
Question:
I’m trying to convert C function for python 3.6 use.
code as below:
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...')
function definition:
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);
But different OSes with the same code seems to have different result:
On Windows 7 64 bit
On Windows 10 64 bit
I don’t print “handle is here”
What I’ve tried:
According to some answers on stack overflow, this may be caused by not assigning function argtypes and restype explicitly, so I tried and failed.
Answers:
You should show your .argtypes and .restype attempts, but give this a try:
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
Check the use of WinDLL
vs. CDLL
. It won’t matter if your Python is 64-bit, but it makes a difference on 32-bit Python. Use CDLL
if the functions use C calling convention (__cdecl) and WinDLL
if the functions use __stdcall calling convention. If the header file isn’t clear, the default is usually __cdecl. Edit: from the API link in another answer, it is __stdcall
and WinDLL
should be used.
In 99% of the cases, inconsistencies between arguments (and / or return) type inconsistencies are the cause (check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati’s answer) for more details).
Always have [Python.Docs]: ctypes – A foreign function library for Python open when working with CTypes.
I found [GitHub]: erikssm/futronics-fingerprint-reader – (master) futronics-fingerprint-reader/ftrScanAPI.h (I don’t know how different it’s from what you currently have, but things you posted so far seem to match), and I did some changes to your code:
-
Define argtypes and restype for functions
-
Define missing types (for clarity only)
-
Some other insignificant changes (renames)
-
One other thing that I noticed in the above file, is a #pragma pack(push, 1)
macro (check [MS.Docs]: pack for more details). For this structure it makes no difference (thanks @AnttiHaapala for the hint), as the 3 int (4 byte) members alignment doesn’t change, but for other structures (with "smaller" member types (e.g. char, short)) you might want to add: _pack_ = 1
Your modified code (needless to say, I didn’t run it as I don’t have the .dll):
#!/usr/bin/env python
import ctypes as ct
from ctypes import wintypes as wt
# ...
class FTRSCAN_IMAGE_SIZE(ct.Structure):
# _pack_ = 1
_fields_ = (
("nWidth", ct.c_int),
("nHeight", ct.c_int),
("nImageSize", ct.c_int),
)
PFTRSCAN_IMAGE_SIZE = ct.POINTER(FTRSCAN_IMAGE_SIZE)
FTRHANDLE = ct.c_void_p
lib = ct.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 = wt.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, ct.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...")
The problem seems to simply be that the device handle is a 64-bit entity (a typedef’d pointer to void). That it works in your Windows 7 was just a fluke, as the upper 33 bits of the handle were correctly zero.
The return type of all functions in ctypes defaults to 32-bit int
. Now in Windows 10 it seems that the 32th bit (sign bit) was set, which causes a sign extension somewhere when the handle-coerced-to-int is pushed on the 64-bit stack for function call. The resulting address has all upper bits set (0xFFFFFFFFA…) which points to kernel space and not in the user space.
Thus perhaps you can get your code “working” with just
lib.ftrScanOpenDevice.restype = c_void_p
That is not to say that you shouldn’t define the argument and return types for all functions – you should, otherwise they will follow just the default argument promotion rules of the C language, and work… or totally fail to work, depending on whether the function’s prototype itself is compatible with default argument promotions. For example any function that accepts float
s as arguments cannot be called correctly without defining the prototype.
I’m trying to convert C function for python 3.6 use.
code as below:
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...')
function definition:
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);
But different OSes with the same code seems to have different result:

I don’t print “handle is here”

What I’ve tried:
According to some answers on stack overflow, this may be caused by not assigning function argtypes and restype explicitly, so I tried and failed.
You should show your .argtypes and .restype attempts, but give this a try:
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
Check the use of WinDLL
vs. CDLL
. It won’t matter if your Python is 64-bit, but it makes a difference on 32-bit Python. Use CDLL
if the functions use C calling convention (__cdecl) and WinDLL
if the functions use __stdcall calling convention. If the header file isn’t clear, the default is usually __cdecl. Edit: from the API link in another answer, it is __stdcall
and WinDLL
should be used.
In 99% of the cases, inconsistencies between arguments (and / or return) type inconsistencies are the cause (check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati’s answer) for more details).
Always have [Python.Docs]: ctypes – A foreign function library for Python open when working with CTypes.
I found [GitHub]: erikssm/futronics-fingerprint-reader – (master) futronics-fingerprint-reader/ftrScanAPI.h (I don’t know how different it’s from what you currently have, but things you posted so far seem to match), and I did some changes to your code:
-
Define argtypes and restype for functions
-
Define missing types (for clarity only)
-
Some other insignificant changes (renames)
-
One other thing that I noticed in the above file, is a
#pragma pack(push, 1)
macro (check [MS.Docs]: pack for more details). For this structure it makes no difference (thanks @AnttiHaapala for the hint), as the 3 int (4 byte) members alignment doesn’t change, but for other structures (with "smaller" member types (e.g. char, short)) you might want to add:_pack_ = 1
Your modified code (needless to say, I didn’t run it as I don’t have the .dll):
#!/usr/bin/env python
import ctypes as ct
from ctypes import wintypes as wt
# ...
class FTRSCAN_IMAGE_SIZE(ct.Structure):
# _pack_ = 1
_fields_ = (
("nWidth", ct.c_int),
("nHeight", ct.c_int),
("nImageSize", ct.c_int),
)
PFTRSCAN_IMAGE_SIZE = ct.POINTER(FTRSCAN_IMAGE_SIZE)
FTRHANDLE = ct.c_void_p
lib = ct.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 = wt.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, ct.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...")
The problem seems to simply be that the device handle is a 64-bit entity (a typedef’d pointer to void). That it works in your Windows 7 was just a fluke, as the upper 33 bits of the handle were correctly zero.
The return type of all functions in ctypes defaults to 32-bit int
. Now in Windows 10 it seems that the 32th bit (sign bit) was set, which causes a sign extension somewhere when the handle-coerced-to-int is pushed on the 64-bit stack for function call. The resulting address has all upper bits set (0xFFFFFFFFA…) which points to kernel space and not in the user space.
Thus perhaps you can get your code “working” with just
lib.ftrScanOpenDevice.restype = c_void_p
That is not to say that you shouldn’t define the argument and return types for all functions – you should, otherwise they will follow just the default argument promotion rules of the C language, and work… or totally fail to work, depending on whether the function’s prototype itself is compatible with default argument promotions. For example any function that accepts float
s as arguments cannot be called correctly without defining the prototype.