C function called from Python via ctypes returns incorrect value

Posted on

Solving problem is about exposing yourself to as many situations as possible like C function called from Python via ctypes returns incorrect value and practice these strategies over and over. With time, it becomes second nature and a natural way you approach any problems in general. Big or small, always start with a plan, use other strategies mentioned here till you are confident and ready to code the solution.
In this post, my aim is to share an overview the topic about C function called from Python via ctypes returns incorrect value, which can be followed any time. Take easy to follow this discuss.

C function called from Python via ctypes returns incorrect value

I have written a simple function in C that raises a given number to a given power. The function returns the correct value when I call it in C, but when I call it in Python it returns a different, incorrect value.

I created the shared file using the command:
$ gcc -fPIC -shared -o test.so test.c

I have tried different configurations of the C function, some of which return the expected value and some of which do not. For example, when my function a simple square using return x*x, without a for loop, it returned the correct value in Python.

I hope to eventually be able to call a C function in python that will return a two-dimensional C array.

#include <stdio.h>
float power(float x, int exponent)
{
    float val = x;
    for(int i=1; i<exponent; i++){
        val = val*x;
    }
    return val;
}
from ctypes import *
so_file = '/Users/.../test.so'
functions = CDLL(so_file)
functions.power.argtype = [c_float, c_int]
functions.power.restype = c_float
print(functions.power(5,3))

I obtain the expected output of 125.0 when I call the function in C, but when I call the function in python, it returns a value of 0.0.

This is my first time using ctypes. Have I made an obvious error that is causing the function to miscalculate?

Asked By: trueDGP

||

Answer #1:

Listing [Python 3.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 calling convention (Win) aside):

  1. Argument types
  2. Return type

In CTypes, this is achieved by specifying:

  1. argtypes – a list (actually a sequence) containing each argument (CTypes) type (in the order they appear in the function header)
  2. restype – a single CTypes 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 (basically, 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 64bit CPU / OS, where values of larger types (e.g. pointers) could be truncated.
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 func00 exported by libdll00.dll (libdll00.so) with the following header:

The Python equivalent would be:

func00 = libdll00.func00
func00.argtypes = [ctypes.c_uint32, ctypes.c_float, ctypes.c_longlong * 8, ctypes.c_void_p, ctypes.POINTER(ctypes.c_char)]
'''
It would be a lot easier (nicer, and in most cases recommended)
  the last element to be ctypes.c_char_p,
  but I chose this form to illustrate pointers in general.
'''
func00.restype = ctypes.c_double

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


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

Answered By: CristiFati
The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .

Leave a Reply

Your email address will not be published.