Python Ctypes passes data pointers
I’m accessing the API, but I can’t get the returned data. Two floating-point pointers point to an array of data. I have to assume that the API is working fine. A different function call provides the length of the data I’m retrieving. When attempted, this value is lower than length
.
The C header file for the function
int function(int, float * data1, float * data2)
ctypes setting
dll.function.argtypes = (c_int, POINTER(c_float), POINTER(c_float))
dll.function.restypes = c_int
Failed attempt 1:
x = c_float()
y = c_float()
status = dll.function(1, byref(x), byref(y))
Program crashes or access violation writes.
Failed attempt 2:
x = POINTER(c_float)()
y = POINTER(c_float)()
status = dll.function(1, x, y)
Null pointer error
Failed attempt 3:
dll.function.argtypes = (c_int, c_void_p, c_void_p)
x = c_void_p()
y = c_void_p()
status = dll.function(1, x, y)
Null pointer error
Failed attempt 4:
array = c_float * length
x = array()
y = array()
status = dll.function(1, byref(x), byref(y))
The program crashes
Failed attempt 5:
array = c_float * length
x = POINTER(array)()
y = POINTER(array)()
status = dll.function(1, x, y)
Null pointer error or ArgumentError: Expected LP_c_float instance instead of LP_c_float_Array_[length].
Failed attempt 6:
x = (c_float*length)()
y = (c_float*length)()
a = cast(x, POINTER(c_float))
b = cast(y, POINTER(c_float))
status = dll.function(1, a, b)
The program crashes
What am I missing and why?
I believe the parameter type is correct. I’m trying to meet with them appropriately, but I’m still having problems. Do I need to somehow “malloc” memory? (I’m sure I need to release after getting the data).
This is on Windows 7 with Python 2.7 32-bit.
I’ve looked at other similar issues but haven’t found a solution. I was wondering, at this point, if I could blame the API for this.
Solution
Pointers and arrays are handled in [Python.Docs]: ctypes – Type conversions There is an explanation in
I have prepared a virtual example for you.
main00.c:
#if defined(_WIN32)
# define DECLSPEC_DLLEXPORT __declspec(dllexport)
#else
# define DECLSPEC_DLLEXPORT
#endif
static int kSize = 5;
DECLSPEC_DLLEXPORT int size() {
return kSize;
}
DECLSPEC_DLLEXPORT int function(int dummy, float *data1, float *data2) {
for (int i = 0; i < kSize; i++) {
data1[i] = dummy * i;
data2[i] = -dummy * (i + 1);
}
return 0;
}
code00.py:
#!/usr/bin/env python
import sys
import ctypes as ct
c_float_p = ct. POINTER(ct.c_float)
def main(*argv):
dll = ct. CDLL("./dll00.so")
size = dll.size
size.argtypes = []
size.restype = ct.c_int
function = dll.function
function.argtypes = [ct.c_int, c_float_p, c_float_p]
function.restype = ct.c_int
sz = size()
print(sz)
data1 = (ct.c_float * sz)()
data2 = (ct.c_float * sz)()
res = function(1, ct.cast(data1, c_float_p), ct.cast(data2, c_float_p))
for i in range(sz):
print(data1[i], data2[i])
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
Notes:
- Part C tries to mimic what your .dll does (or at least what I understand):
- size
- – Gets the array size
- Function – Fill arrays (up to their size – assuming they are correctly allocated by the caller).
- The Python part is simple:
- Load .dll
- is 2 functions (not required for size_func
- Gets the length
- Initialize the array
- Use ctypes.cast to pass them to the function_func
).
Output (on Lnx, it’s much simpler to build C code, but works fine on Win):
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050043861]> gcc -shared -o dll00.so main00.c [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050043861]> python3 code00.py Python 3.8.5 (default, Jan 27 2021, 15:41:15) [GCC 9.3.0] 64bit on linux 5 0.0 -1.0 1.0 -2.0 2.0 -3.0 3.0 -4.0 4.0 -5.0