I have neither experience in C nor in the ctypes library.
I have a program written in C with a DLL called image_lib
. In the DLL header, the following function is defined
int Generate_Hologram(unsigned char *Array, unsigned char* WFC, float *x_spots, float *y_spots, float *z_spots, float *I_spots, int N_spots, int ApplyAffine);
In Python, I have my array called x_spots
and some other arrays:
# coordinate locations
x_spots = np.arange(-1, 2, 1, dtype=float)
x_spots = np.arange(-1, 2, 1, dtype=float)
z_spots = np.arange([0] * num_tweezers.value, dtype=float)
# Intensities
int_spots = np.arange([1] * num_tweezers.value, dtype=float)
# Image for the GPU to compute the hologram on
Image = np.empty([width.value*height.value*bytpesPerPixel], np.uint8, 'C');
# Create a blank vector to hold the wavefront correction
WFC = np.empty([width.value*height.value*bytpesPerPixel], np.uint8, 'C');
I have (based on the single example the manufacturer gives)
image_lib.Generate_Hologram(Image.ctpyes.data_as(POINTER(c_ubyte)), WFC.ctypes.data_as(POINTER(c_ubyte)), ....
)
How do I pass x_floats to this function? I guess
x_spots.ctypes.data_as(POINTER(c_float))
CodePudding user response:
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_spots
thing. 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_spots
with 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
dtype
of your numpy arrays fromfloat
tonp.float32
CodePudding user response:
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