1

I would like to call a C function from python. This C function is void, thus the "return parameters" (data I want to change) are defined as pointers in the C function's definition.

The function in C looks like this (note: I cannot and will not change it as it is generated automatically by MATLAB codegen):

void func(int   *input_var,    // input
          float *output_var    //output
         ) {
    ... 
}

from python, I am calling my function as so

import ctypes as C

func = C.CDLL('path/to/lib.so').func

input_var = C.c_int(5)
output_var = C.POINTER(C.c_float) # basically want to just declare a pointer here
func(C.byref(input_var), C.byref(output_var))

The error I get is

TypeError: byref() argument must be a ctypes instance, not '_ctypes.PyCPointerType'

if I remove bref() I get

ctypes.ArgumentError: argument 2: <class 'TypeError'>: Don't know how to convert parameter 2

I also tried to pass in output_var as C.byref(output_var()); this leads to a Segmentation fault (core dumped)

Brian Barry
  • 439
  • 2
  • 17

2 Answers2

2

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

Do things like you'd do it from C (and you're already doing for the input argument): declare a float variable and pass its address to the function (via byref).

One important thing: check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a common pitfall when working with CTypes (calling functions).

Example (simplest scenario: pointers wrapping single values, if there were arrays, or function performed some memory allocations, things would be a bit more complex).

  • dll00.c:

    #include <stdio.h>
    
    #if defined(_WIN32)
    #  define DLL00_EXPORT_API __declspec(dllexport)
    #else
    #  define DLL00_EXPORT_API
    #endif
    
    
    #if defined(__cplusplus)
    extern "C" {
    #endif
    
    DLL00_EXPORT_API void dll00Func00(int *pIn, float *pOut);
    
    #if defined(__cplusplus)
    }
    #endif
    
    
    void dll00Func00(int *pIn, float *pOut)
    {
        if (pIn)
            printf("C - In: %d\n", *pIn);
        if (pOut) {
            float f = 3.141593 * (pIn ? *pIn : 1);
            printf("C - Setting out to: %.3f\n", f);
            *pOut = f;
        }
    }
    
  • code00.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import sys
    
    
    DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    
    def main(*argv):
        dll = cts.CDLL(DLL_NAME)
        func = dll.dll00Func00
        func.argtypes = (cts.POINTER(cts.c_int), cts.POINTER(cts.c_float))
        func.restype = None
    
        i = cts.c_int(2)
        f = cts.c_float(0)
    
        res = func(cts.byref(i), cts.byref(f))
        print("Values after calling function: {:d}, {:.3f}".format(i.value, f.value))
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

Output:

(qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q075393602]> ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]> ls
code00.py  dll00.c
[064bit prompt]>
[064bit prompt]> gcc -shared -o dll00.so dll00.c
[064bit prompt]>
[064bit prompt]> python ./code00.py
Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] 064bit on linux

C - In: 2
C - Setting out to: 6.283
Values after calling function: 2, 6.283

Done.
CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • This makes sense, however reading your specific scenario was a little confusing wrt my situation since you are initializing your output variable `f` with a value (`0`). But in my case it turns out that with ctypes you can "declare" a variable by instantiating one without an argument/value, i.e., `c_float()` as explained by @Mark Tolonen – Brian Barry Feb 09 '23 at 17:54
  • 1
    :) It's like in *C*: you can have `float f;` or `float f = 0;`. I always initialize variables (it's a habit of mine not to rely on compiler defaults). Anyway in *CTypes* the 2 are equivalent, as if you print the "uninitialized" one it's still *0* (initialized by default). Also it's a good thing in your situation, as you can clearly see that the function actually changed the value. – CristiFati Feb 09 '23 at 18:21
  • I see; I was unaware of this compiler default behavior! – Brian Barry Feb 09 '23 at 18:24
  • Actually I misread the comment. `POINTER(c_float)()` creates a *float\** (which is ***NULL***). Now, I don't know what the function does inside (whether it handles that case or not, or if it allocates memory, and so on). Passing a *NULL* pointer in **my function** doesn't do anything with it, therefore there's no return value. – CristiFati Feb 09 '23 at 18:29
0

you can use the .byref directly after declaring the variable no need to make a pointer.

so in your example, you can declare the output as the variable type you need it to be and then pass that by reference to the function.

 output_var = C.c_float()
 func(C.byref(input_var), C.byref(output_var)

this should work.

Juan
  • 117
  • 6