2

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)

asked Feb 9, 2023 at 2:24
2
  • 1
    POINTER(c_float) is a type. An instance would be POINTER(c_float)() but what you actually need is just a float instance: float(). Pass it byref Commented Feb 9, 2023 at 7:24
  • creating the instance works. Commented Feb 9, 2023 at 17:51

2 Answers 2

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.
answered Feb 9, 2023 at 6:27
Sign up to request clarification or add additional context in comments.

4 Comments

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
:) 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.
I see; I was unaware of this compiler default behavior!
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.
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.

answered Feb 9, 2023 at 2:36

1 Comment

No, it won’t work. c_float is a type. You need an instance: c_float()

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.