Python Ctypes Issue on Different Oses

python ctypes issue on different OSes

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...")

Python load library from different platform (Windows, Linux or OS X)

you can use the ctypes.cdll module to load the DLL/SO/DYLIB and the platform module to detect the system you are running on.

a minimal working example would be like this:

import platform
from ctypes import *

# get the right filename
if platform.uname()[0] == "Windows":
name = "win.dll"
elif platform.uname()[0] == "Linux":
name = "linux.so"
else:
name = "osx.dylib"

# load the library
lib = cdll.LoadLibrary(name)

please note that you will need an 64 bit python interpreter to load 64 bit libraries and an 32 bit python interpreter to load 32 bit libraries

Wrong ctypes assignation

[Python 3.Docs]: ctypes - A foreign function library for Python.

You misspelled restypes (it should be restype). By doing so, restype is not initialized, and defaults to int (this wouldn't be a problem on 32bit), and you ran into:

  • [SO]: Python ctypes cdll.LoadLibrary, instantiate an object, execute its method, private variable address truncated (@CristiFati's answer)
  • [SO]: python ctypes issue on different OSes (@CristiFati's answer)

Besides that, there are several problems in the code:

  • If the C function specifies a pointer (double* in this case), don't use ctypes.c_void_p (in argtypes or restype) to map it, as it might be too wide, use (for this case) ctypes.POINTER(ctypes.c_double) instead
  • For me this doesn't even compile (I wonder how were you able to run that code). I'm going to exemplify on XTrain only, but applies to YTrain and Xpredict as well. ctypes doesn't know to convert a Python list to a ctypes.POINTER(ctypes.c_double) (or ctypes.c_void_p), and the conversion must be made manually (to a ctypes.c_double array):

    XTrain = [1.0, 1.0, 1.0, 3.0, 3.0, 3.0]
    xtrain_ctypes = (ctypes.c_double * len(XTrain))(*XTrain)

    and pass xtrain_ctypes to the functions.

how to solve the os errors

Windows uses .dll and .exe for applications. The .so is compiled for a different system (like Linux).

Python ctypes uses regular Windows functions by the looks of it, and thus also only support .dll.

You need a version of gcc that produce Windows binaries to compile your C source code before you can load it on Windows.

There's a lot of different distributions of GCC for Windows, like for example Mingw. Just google "GCC for Windows".

Once you have a Windows based GCC installed, use that in place of your current gcc, but use libfun.dll for in place of libfun.so.

Python/ctypes file handle difference between Mac OS X and Ubuntu

Are you trying on a 32-bit Ubuntu versus a 64-bit OS/X? I think the issue is that your version of libc.fopen() returns a C "int", which is almost always a 32-bit value --- but the real fopen() returns a pointer. So on a 64-bit operating system, the c_file that you get is truncated to a 32-bit integer. On a 32-bit operating system, it works anyway because the 32-bit integer can be passed back to the fread() and fclose(), which will interpret it again as a pointer. To fix it, you need to declare the restype of libc.fopen().

(I can only recommend CFFI as an alternative to ctypes with saner defaults, but of course I'm partial there, being one of the authors :-)

C function called from Python via ctypes returns incorrect value

Listing [Python.Docs]: ctypes - A foreign function library for Python.

In order for everything to be properly converted (Python <=> C) when calling the function (residing in a .dll (.so)), 2 things need to be specified (leaving x86 (pc032) calling convention (Win) aside):

  1. Argument types

  2. Return type

In CTypes, this is achieved by specifying:

  1. argtypes - a sequence (tuple, list) containing each function's argument (CTypes) type, in the order they appear in the function declaration (if the function only has one argument, there should be one element sequence)

  2. restype - a single CTypes type (function's return type)

Side note: an alternative to the above, is prototyping foreign functions (CFUNCTYPE, WINFUNCTYPE, PYFUNCTYPE - check the Function prototypes section (in the URL at the beginning)).

Anyway:

  • Failing to specify

  • Misspelling (which basically is same thing as previous bullet)

any of them (where required (1)), will result in defaults being applied: all are treated (C89 style) as ints, which (on most systems) are 32 bits long.

This generates Undefined Behavior (2)
(also applies when incorrectly specifying them), especially on 064bit CPU / OS (and Python process), where values of larger types (e.g. pointers) could be truncated (check [SO]: Maximum and minimum value of C types integers from Python (@CristiFati's answer) for details (limits -> size) regarding C integral types).

The displayed errors can be numerous and sometimes misleading.

You misspelled argtype (lacking an s at the end).

Correct that, and you should be fine

Example:

For a function func exported by libdll.dll (libdll.so) with the following header:

double func(uint32_t ui32,
float f,
long long vll[8],
void *pv,
char *pc,
uint8_t *pui8);

The Python equivalent would be:

import ctypes as cts

func = libdll.func
func.argtypes = (cts.c_uint32,
cts.c_float,
cts.c_longlong * 8,
cts.c_void_p,
cts.c_char_p,
cts.POINTER(cts.c_ubyte))
'''
Last 2 argument types are almost the same (1st is signed while 2nd is unsigned).
- 1st is used for NUL terminated strings (that work with strcpy, strlen, ...).
Python automatically converts them to bytes

- 2nd is used for buffers in general (which may contain NUL (0x00) characters)
'''

func.restype = cts.c_double

Some (more destructive) outcomes of the same (or very similar) scenario (there are many others):

  • [SO]: Python ctypes cdll.LoadLibrary, instantiate an object, execute its method, private variable address truncated (@CristiFati's answer)

  • [SO]: python ctypes issue on different OSes (@CristiFati's answer)



Footnotes

  • #1: Technically, there are situations where it's not required to specify them. But even then, it's best to have them specified in order to eliminate any possible confusion:

    • Function with no arguments:

      function_from_dll.argtypes = ()
    • Function returning void:

      function_from_dll.restype = None
  • #2: Undefined Behavior ([Wikipedia]: Undefined behavior) as the name suggests, is a situation when the outcome of a piece of code can't be "predicted" (or guaranteed by the C (C++) standard). The main cases:

    • Works as expected

    • Doesn't work as expected

      • Has some funny outputs / side effects

      • Crashes


    The "beauty" of it is that sometimes it seems totally random, sometimes it "only reproduces" under some specific circumstances (different machines, different OSes, different environments, ...). Bottom line is that all of them are purely coincidental! The problem lies in the code (can be the current code (highest chances) or other code it uses (libraries, compilers)).

Access Violation when using Ctypes to Interface with Fortran DLL

I see 2 problems with the code (and a potential 3rd one):

  1. argtypes (and restype) not being specified. Check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for more details

  2. This may be a consequence (or at least it's closely related to) the previous. I can only guess without the Fortran (or better: C) function prototype, but anyway there is certainly something wrong. I assume that for the input data things should be same as for the output data, so the function would take 2 arrays (same size), and the input one's elements would be void *s (since their type is not consistent). Then, you'd need something like (although I can't imagine how would Fortran know which element contains an int and which a double):

    in_data (ctypes.c_void_p * len(self.data))()
    for idx, item in enumerate(self.data):
    item.convert_to_ctypes()
    in_data[index] = ctypes.addressof(item.c_val)
  3. Since you're on 032bit, you should also take calling convention into account (ctypes.CDLL vs ctypes.WinDLL)

But again, without the function prototype, everything is just a speculation.

Also, why "MyFunction_".lower() instead of "myfunction_"?

Getting segmentation fault when using ctypes on xlib

The error is because argtypes (and restype) not being specified, as described in [Python 3.Docs]: ctypes - Specifying the required argument types (function prototypes). There are many examples of what happens in this case, here are 2 of them:

  • [SO]: python ctypes issue on different OSes (@CristiFati's answer)
  • [SO]: Python ctypes cdll.LoadLibrary, instantiate an object, execute its method, private variable address truncated (@CristiFati's answer)

Declare them for each function, before calling it:

xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
xlib.XOpenDisplay.restype = ctypes.c_void_p # Actually, it's a Display pointer, but since the Display structure definition is not known (nor do we care about it), make it a void pointer

xlib.XDefaultRootWindow.argtypes = [ctypes.c_void_p]
xlib.XDefaultRootWindow.restype = ctypes.c_uint32

xss.XScreenSaverQueryInfo.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.POINTER(XScreenSaverInfo)]
xss.XScreenSaverQueryInfo.restype = ctypes.c_int


Related Topics



Leave a reply



Submit