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 Overflow
🌐
ProgramCreek
programcreek.com › python › example › 1881 › ctypes.c_float
Python Examples of ctypes.c_float
Parameters ---------- handle : ... : void_p Pointer to data of the tensor described by srcDesc descriptor. value : float Value that all elements of the tensor will be set to. """ dataType, _, _, _, _, _, _, _, _ = cudnnGetTensor4dDescriptor(srcDesc) if dataType == cudnnDataType['CUDNN_DATA_DOUBLE']: alphaRef = ctypes.byref(cty...
🌐
Fz-juelich
pgi-jcns.fz-juelich.de › portal › pages › using-c-from-python.html
Using C from Python: How to create a ctypes wrapper - Scientific IT-Systems
Thanks to that, it is also possible to wrap a Python function in a way that it becomes callable from C. When dealing with APIs that include events (e.g. a GUI library), this comes in very helpful, as it allows us to use Python functions as C callbacks. To do this, we need to create a function type with the method ctypes.CFUNCTYPE: callback_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_float, ctypes.c_float)
🌐
Stack Overflow
stackoverflow.com › questions › 60788664 › difficulty-returning-a-float-from-ctypes
python - Difficulty returning a float from Ctypes - Stack Overflow
Of interest, when I use the np.ctypeslib.ndpointer method of defining restypes, the python type function still claims the result is a numpy.ndarray but will complain if you treat it like one. I've tried alternatively defining the restypes of this function or variations on the function as ctypes.c_float*length or ctypes.POINTER(ctypes.c_float*length) but with no success.
Top answer
1 of 2
3

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
2 of 2
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.

🌐
Causeway
cvstuff.wordpress.com › 2014 › 11 › 27 › wraping-c-code-with-python-ctypes-memory-and-pointers
Wraping C code with Python CTypes: memory and pointers | Causeway
November 28, 2014 - After compiling this library with g++ or gcc (see my last post), I declare to Python using CTypes: # the fillprototype is a user-defined function, see my previous post... fillprototype(mylib.add_vector, ctypes.POINTER(ctypes.c_double), [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), ctypes.c_int]) We can pass numpy variables to this function by casting them as CTypes: >> a = np.array([1,2,3], dtype=float) >> b = np.array([4,5,6], dtype=float) >> p_a = a.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) >> p_b = b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) >> d = ctypes.c_int(len(a)) >> c = mylib.dot_product(p_a, p_b, d) Here the variable ‘c’ refer to a memory block allocated on the C side.
Top answer
1 of 2
2

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
2 of 2
1

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 this x_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 your x_spots with a dtype=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 * to double *
  • Or, change dtype of your numpy arrays from float to np.float32
🌐
Python
svn.python.org › projects › ctypes › trunk › ctypes › docs › manual › tutorial.html
ctypes tutorial
ctypes exports the byref function which is used to pass parameters by reference. The same effect can be achieved with the pointer function, although pointer does a lot more work since it constructs a real pointer object, so it is faster to use byref if you don't need the pointer object in Python itself: >>> i = c_int() >>> f = c_float...
Find elsewhere
🌐
SageMath
doc.sagemath.org › html › en › thematic_tutorials › numerical_sage › ctypes.html
Ctypes - Thematic Tutorials
which you want to call from python. First make a shared object library by doing (at the command line) ... Note that on OSX -shared should be replaced by -dynamiclib and sum.so should be called sum.dylib Then you can do · from ctypes import * my_sum=CDLL('sum.so') a=numpy.array(range(10),dtype=float) my_sum.sum(a.ctypes.data_as(c_void_p),int(10)) Note here that a.ctypes.data_as(c_void_p) returns a ctypes object that is void pointer to the underlying array of a.
🌐
Microsoft Community
techcommunity.microsoft.com › microsoft community hub › communities › topics › microsoft learn › microsoft learn
how to pass float* array to C method from python script whose memory allocation takes place in C lay | Microsoft Community Hub
October 12, 2020 - TypeError: byref() argument must be a ctypes instance, not '_ctypes.PyCPointerType' this is the error I get when I run the script. I am little confused on how to send argument for float *oresults in script.
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.ndarray.ctypes.html
numpy.ndarray.ctypes — NumPy v2.5 Manual
For example, calling self._as_parameter_ is equivalent to self.data_as(ctypes.c_void_p). Perhaps you want to use the data as a pointer to a ctypes array of floating-point data: self.data_as(ctypes.POINTER(ctypes.c_double)).
🌐
SciPy
scipy.github.io › old-wiki › pages › Cookbook › Ctypes.html
Cookbook/Ctypes - SciPy wiki dump
By allocating your buffers as NumPy arrays, the Python garbage collector can take care of this. ... 1 from ctypes import * 2 ALLOCATOR = CFUNCTYPE(c_long, c_int, POINTER(c_int)) 3 # load your library as lib 4 lib.baz.restype = None 5 lib.baz.argtypes = [c_float, c_int, ALLOCATOR]
🌐
GeeksforGeeks
geeksforgeeks.org › python › using-pointers-in-python-using-ctypes
Using Pointers in Python using ctypes - GeeksforGeeks
July 23, 2025 - Since in Python the concept of pointers is not properly available or used we can't properly create a Void Pointer, as the POINTER function requires one argument i.e the data type. But we can create a Void pointer of a certain data type and then don't point it to any memory address. We are just typecasting it like C/C++ early. We can also create some void pointers of a specific type that doesn't hold any address while creating. ... import ctypes as ct # Creating a variable ptr2 which will act as a void pointer # We will not point it to any other memory location ptr2 = ct.POINTER(ct.c_int) # Trying to print a void pointer print(ptr2)
🌐
GitHub
gist.github.com › lud4ik › 3403220
How to wrap C code in Python (ctypes version) · GitHub
Syntax Error yerr=np.array([sig_y]*N,'float64') yerr_p=y.ctypes.data_as(POINTER(c_double)) last line is supposed to be: yerr_p=yerr.ctypes.data_as(POINTER(c_double)) Maybe you just copy/paste the previous pointer assignment · Sign up for free to join this conversation on GitHub.
🌐
NumPy
numpy.org › doc › 2.2 › reference › generated › numpy.ndarray.ctypes.html
numpy.ndarray.ctypes — NumPy v2.2 Manual
For example, calling self._as_parameter_ is equivalent to self.data_as(ctypes.c_void_p). Perhaps you want to use the data as a pointer to a ctypes array of floating-point data: self.data_as(ctypes.POINTER(ctypes.c_double)).
Top answer
1 of 1
1

Listing:

  • [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) - a common pitfall when working with CTypes (calling functions)

  • [Python.Docs]: ctypes - A foreign function library for Python

Here's a piece of code exemplifying how to handle pointers and their addresses. The trick is to use ctypes.addressof (documented in the 2nd URL).

code00.py:

#!/usr/bin/env python

import ctypes as cts
import sys


CType = cts.c_float
CTypePtr = cts.POINTER(CType)


def ctype_pointer(seq):  # Helper
    CTypeArr = (CType * len(seq))
    ctype_arr = CTypeArr(*seq)
    return cts.cast(ctype_arr, CTypePtr)


def pointer_elements(addr, count):  # Helper
    return tuple(CType.from_address(addr + i * cts.sizeof(CType)).value for i in range(count))


def main(*argv):
    seq = (2.718182, -3.141593, 1.618034, -0.618034, 0)
    ptr = ctype_pointer(seq)
    print(f"Pointer: {ptr}")
    print(f"\nPointer elements: {tuple(ptr[i] for i in range(len(seq)))}")  # Check if pointer has correct data
    ptr_addr = cts.addressof(ptr.contents)  # @TODO - cfati: Straightforward
    print(f"\nAddress: {ptr_addr} (0x{ptr_addr:016X})\nElements from address: {pointer_elements(ptr_addr, len(seq))}")
    ptr_addr0 = cts.cast(ptr, cts.c_void_p).value  # @TODO - cfati: Alternative
    print(f"\nAddresses match: {ptr_addr == ptr_addr0}")


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)

Notes:

  • Although it adds a bit of complexity, I introduced the CType "layer" to show that it should work with any type, not just float (as long as the values in the sequence are of that type)

  • The only truly relevant lines are those marked with @TODO

Output:

(py_pc064_03.08_test0_lancer) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q078366208]> python ./code00.py 
Python 3.8.19 (default, Apr  6 2024, 17:58:10) [GCC 11.4.0] 064bit on linux

Pointer: <__main__.LP_c_float object at 0x7203e97e7d40>

Pointer elements: (2.71818208694458, -3.1415929794311523, 1.6180340051651, -0.6180340051651001, 0.0)

Address: 125361127594576 (0x00007203E97A9A50)
Elements from address: (2.71818208694458, -3.1415929794311523, 1.6180340051651, -0.6180340051651001, 0.0)

Addresses match: True

Done.