Paul Lynn
Paul Lynn

Reputation: 99

CFUNCTYPE causes segmentation fault

I have a simple C code which takes some number and returns a string.

const char * get_err_string(const uint8_t errcode) {

    switch (errcode) {
        case 0:
            return "No errors";
            break;
        case 1:
            return "Some error";
            break;
        default:
            return "There is no such error code";
            break;
    }
}

I'm trying to execute it with ctypes using a function prototype, but I get segmentation fault every time.

import ctypes

libc = ctypes.cdll.LoadLibrary("lib.so")

get_err_string = ctypes.CFUNCTYPE(
    ctypes.c_char_p,  # restype
    ctypes.c_uint8  # 1st argument
)(libc.get_err_string)

get_err_string(ctypes.c_uint8(0))  # segmentation fault

What did surprised me, is that a slightly different code executes just ok.

import ctypes

libc = ctypes.cdll.LoadLibrary("genevo/c/bin/genevo.so")

get_err_string = libc.get_err_string
get_err_string.restype = ctypes.c_char_p
get_err_string.argtypes = [ctypes.c_uint8]

get_err_string(ctypes.c_uint8(0))  # b'No errors.'

So, why is that? Is there something I miss?

Upvotes: 1

Views: 297

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177891

CFUNCTYPE is not being called with correct parameters. See Function Prototypes in the ctypes documentation (excerpt):

Function prototypes created by these factory functions can be instantiated in different ways, depending on the type and number of the parameters in the call:

prototype(address)
Returns a foreign function at the specified address which must be an integer.

prototype(callable)
Create a C callable function (a callback function) from a Python callable.

prototype(func_spec[, paramflags])
Returns a foreign function exported by a shared library. func_spec must be a 2-tuple (name_or_ordinal, library). The first item is the name of the exported function as string, or the ordinal of the exported function as small integer. The second item is the shared library instance.

Use the 3rd version above in this case:

import ctypes as ct

libc = ct.CDLL('./test')
prototype = ct.CFUNCTYPE(ct.c_char_p, ct.c_uint8)
get_err_string = prototype(('get_err_string',libc))
print(get_err_string(0))
// Complete test.c for reference (Windows DLL)
#include <stdint.h>

__declspec(dllexport)
const char * get_err_string(const uint8_t errcode) {
    switch (errcode) {
        case 0:
            return "No errors";
            break;
        case 1:
            return "Some error";
            break;
        default:
            return "There is no such error code";
    }
}

Output:

b'No errors'

Note that doing it your 2nd way is the usual way to call a function:

import ctypes as ct

libc = ct.CDLL('./test')
libc.get_err_string.argtypes = ct.c_uint8,
libc.get_err_string.restype = ct.c_char_p

print(libc.get_err_string(0))

Output:

b'No errors'

But there are some advantages to the first way, namely specifying which parameters are input/output, and assigning parameter names and defaults to make it more "Pythonic".

Upvotes: 2

Related Questions