How to pass references of declared variables to C function in python using ctypes
Question:
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)
Answers:
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.
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: %dn", *pIn);
if (pOut) {
float f = 3.141593 * (pIn ? *pIn : 1);
printf("C - Setting out to: %.3fn", 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.
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)
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.
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: %dn", *pIn); if (pOut) { float f = 3.141593 * (pIn ? *pIn : 1); printf("C - Setting out to: %.3fn", 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.