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)
2 Answers 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.
4 Comments
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 Tolonenfloat 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.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.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.
1 Comment
c_float is a type. You need an instance: c_float()
POINTER(c_float)is a type. An instance would bePOINTER(c_float)()but what you actually need is just a float instance:float(). Pass itbyref