You can either create a c_float instance and initialize with a pointer to that instance, or create a c_float array and pass it, which in ctypes imitates a decay to a pointer to its first element.
Note that ctypes.pointer() creates pointers to existing instances of ctypes objects while ctypes.POINTER() creates pointer types.
test.c - for testing
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
typedef struct Input {
float* number1;
float number2;
} Input;
API void expose_init(Input* input) {
printf("%f %f\n",*input->number1, input->number2);
}
test.py
import ctypes
class input_struct (ctypes.Structure):
_fields_ = (('number1', ctypes.POINTER(ctypes.c_float)),
('number2', ctypes.c_float))
c_instance = ctypes.CDLL('./test')
init_func = c_instance.expose_init
# Good habit to fully define arguments and return type
init_func.argtypes = ctypes.POINTER(input_struct),
init_func.restype = None
inp_str_ptr = input_struct()
num = ctypes.c_float(20) # instance of c_float, similar to C "float num = 20;"
inp_str_ptr.number1 = ctypes.pointer(num) # similar to C "inp_str_ptr.number1 = #"
inp_str_ptr.number2 = 100
c_instance.expose_init(ctypes.byref(inp_str_ptr))
# similar to C "float arr[1] = {30}; inp_str_ptr = arr;"
inp_str_ptr.number1 = (ctypes.c_float * 1)(30)
c_instance.expose_init(ctypes.byref(inp_str_ptr))
Output:
20.000000 100.000000
30.000000 100.000000
Answer from Mark Tolonen on Stack OverflowNot fully tested, but I think it's something along this line:
buffer_size = 720 * 288 * ctypes.sizeof(ctypes.c_float)
rgb_buffer = ctypes.create_string_buffer(buffer_size)
ctypes.memmove(rgb_buffer, getRgbBuffer(), buffer_size)
Key is the ctypes.memmove() function. From the ctypes documentation:
memmove(dst, src, count)
Same as the standard C memmove library function: copiescountbytes fromsrctodst.dstandsrcmust be integers or ctypes instances that can be converted to pointers.
After the above snippet is run, rgb_buffer.value will return the content up until the first '\0'. To get all bytes as a python string, you can slice the whole thing: buffer_contents = rgb_buffer[:].
It's been a while since I used ctypes and I don't have something which returns a "double *" handy enough to test this out, but if you want a c_float_p:
c_float_p = ctypes.POINTER(ctypes.c_float)
Reading BastardSaint's answer, you just want the raw data, but I wasn't sure if you're doing that as a workaround to not having a c_float_p.
Dealing with pointers and arrays is explained in [Python.Docs]: ctypes - Type conversions.
I prepared a dummy 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:
- The C part tries to mimic what your .dll does (or at least what I understood):
- size - gets the arrays sizes
- function - populates the arrays (till their size - assuming that they were properly allocated by the caller)
- Python part is straightforward:
- Load the .dll
- Define argtypes and restype (in your code it's restype's) for the 2 functions (for size_func not necessary)
- Get the lengths
- Initialize the arrays
- Pass them to function_func using ctypes.cast
Output (on Lnx, as building the C code is much simpler, but works on Win as well):
[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
It really depends on what you are doing with these float pointers.
If you are trying to traverse it, i.e.
for(int i = 0; i < size; i++)
printf("%f\n%, data1[i])
then for sure this is problematic as no array was allocated. You simply passed a pointer pointing to a float. That is all.
You need to first allocate that memory. To this end Attempt 4 looks like the more promising, but I suspect you have a problem inside your C function leading to the crash.
Difficult to say without seeing the implementation of that function.
Here is a minimal example to pass C float as a parameter using numpy. It is an exercise for the OP to apply it to their function. Note that the C function must assume a fixed size or be passed the length of the array in some form.
Also note that the dtype is declared as ct.c_float since Python float is typically 64-bit and C float is typically 32-bit. Make sure the type sizes agree.
test.c
#include <stdio.h>
__declspec(dllexport) // for Windows
void func(float* p, size_t size) {
for(size_t i = 0; i < size; ++i)
printf("p[%zu] = %f\n", i, p[i]);
}
test.py
import ctypes as ct
import numpy as np
dll = ct.CDLL('./test')
# The helper function "ndpointer" can declare the expected type
# and either number of dimensions expected or the shape of the
# numpy array. ctypes will then require that array and type check
# the parameter.
dll.func.argtypes = np.ctypeslib.ndpointer(dtype=ct.c_float, ndim=1),
dll.func.restype = None
x_spots = np.arange(-1, 2, 1, dtype=ct.c_float)
dll.func(x_spots, len(x_spots))
Output:
p[0] = -1.000000
p[1] = 0.000000
p[2] = 1.000000
One problem I can spot in your code is the size of your floats.
Note that it doesn't really matter as a pointer to what you pass pointers to your numpy data to your function. I, for one, am used to pass data as arr.ctypes.data_as(ctypes.c_void_p).
This is dynamic, so it is not like C compiler could check anything. And, at least in this case, there is no dynamic checking neither that in makes senses to consider this numpy array data as a pointer to this type.
So, saying correctly or not as POINTER to what you want to pass a numpy array to a function that expect a float * doesn't protect you against error.
It doesn't neither imply any conversion. The pointer is just passed to the C function, it is up to it to interpret correctly, and that has been done statically, when you typed the parameter float *.
So, it is not there (in the code you seem to be wondering about) that you can ensure that the C function get the bunch of float it expect. Whatever, the C function will get a pointer to the numpy data.
The problem you have in your code is that data in x_spots are not float (as in the C meaning of the word float; that is 32 bits floating point numbers). They are most likely double
I say "most likely" because I am not an expert in different python interpreters. I know that, depending on the interpreter, native float type of python may be float32, or float64 or even something else. In the most classical CPython, they are float64. And I am not sure neither what it means for numpy when dtypes is the native type float. But well, with my cpython, a np.array([1,2,3], dtype=float) is a numpy array made of float64. So, not 100% sure if that is a sure thing, or if it exists python interpreter in which that array would be made of float32. It is because I am unsure of that, that, anyway, I never ever use float as an argument for dtype. I use np.float32 or np.float64.
But, well, most likely (or even surely) your x_spots is made of float64.
And then, it doesn't matter a pointer to what you say it is when you pass x_spots.ctypes.data_as(POINTER(...)), the pointer will be a pointer to those float64. That is, in C wording, to double. And your C function will treat it as a pointer to float (with no warning whatsoever: warning occur at compilation time, and we are past that).
So, long story. But conclusion is, either you
- Change your C function to accept
double *as parameter for thisx_spotsthing. That's probably the best way, with your, probably 64 bits computer. But you seem to say that you can't really modify the C function - So, alternatively, you can ensure that your data is made of
float(in C meaning), that is create yourx_spotswith adtype=np.float32.
Note that even if you choose the first case (double *), it would be better to change also dtype to explicitly set it to np.float64.
And once you have done that, it doesn't matter if you pass your argument as x_spots.ctypes.data_as(c_void_p), as x_spots.ctypes.data_as(POINTER(c_float)), as x_spots.ctypes.data_as(POINTER(c_double)), or even x_spots.ctypes.data_as(POINTER(c_char)).
(I strongly encourage you, of course, not to use a false type as POINTER() arg, but that is for human readability of your code; from execution change point of view, it wouldn't change the result)
So tl;dr
- Change type of parameter of C function from
float *todouble * - Or, change
dtypeof your numpy arrays fromfloattonp.float32
The problem was that I missed restype to setup:
lib.test_ret_float.restype = c_float
That solves the problem
I know it is way too late but to whoever searches this and might be looking to the same issue I had.
Usage: if you passed a pointer to a variable as and argument and the C function changes replaces it with a floating point, this way you can repackage the python interpretation (integer) value to a signaled 32bit floating point
def ieee754(value):
"""Repackages values to 32bit floating point"""
packed_v = struct.pack('>L', value)
return struct.unpack('>f', packed_v)[0]
My ctypes is rusty, but I believe you want POINTER(c_float) instead of c_void_p.
So try this:
null_ptr = POINTER(c_float)()
pa_stream_peek(stream, null_ptr, ctypes.c_ulong(length))
null_ptr[0]
null_ptr[5] # etc
To use ctypes in a way that mimics your C code, I would suggest (and I'm out-of-practice and this is untested):
vdata = ctypes.c_void_p()
length = ctypes.c_ulong(0)
pa_stream_peek(stream, ctypes.byref(vdata), ctypes.byref(length))
fdata = ctypes.cast(vdata, POINTER(float))