How to Convert a Python List into a C Array by Using Ctypes

How do I convert a Python list into a C array by using ctypes?

The following code works on arbitrary lists:

import ctypes
py_values = [1, 2, 3, 4]
arr = (ctypes.c_int * len(py_values))(*py_values)

How do I convert a Python list of lists of lists into a C array by using ctypes?

It works with tuples if you don't mind doing a bit of conversion first:

from ctypes import *

list3d = [
[[0.0, 1.0, 2.0, 3.0], [4.0, 5.0, 6.0, 7.0]],
[[0.2, 1.2, 2.2, 3.2], [4.2, 5.2, 6.2, 7.2]],
[[0.4, 1.4, 2.4, 3.4], [4.4, 5.4, 6.4, 7.4]],
]

arr = (c_double * 4 * 2 * 3)(*(tuple(tuple(j) for j in i) for i in list3d))

Check that it's initialized correctly in row-major order:

>>> (c_double * 24).from_buffer(arr)[:]
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0,
0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2,
0.4, 1.4, 2.4, 3.4, 4.4, 5.4, 6.4, 7.4]

Or you can create an empty array and initialize it using a loop. enumerate over the rows and columns of the list and assign the data to a slice:

arr = (c_double * 4 * 2 * 3)()

for i, row in enumerate(list3d):
for j, col in enumerate(row):
arr[i][j][:] = col

Faster way to convert ctypes array to python list?

If you prefer, you can convert to an array with np.ndarray:

msX = np.ndarray((nPoints, ), 'f', mzP, order='C')    
msY = np.ndarray((nPoints, ), 'f', mzI, order='C')

How to pass lists into a ctypes function on python

How do I code a C function to accept a list as its arguments?

Short answer, you can't.

Long answer: C does not have lists, but has arrays and pointers.

You have several options then:

int c_func(const char *dir, float abcd[4]) { // using an array with explicit size

int c_func(const char *dir, float abcd[]) { // Using an array (will decay to a pointer at compile-time)

int c_func(const char *dir, float *abcd) { // Using a pointer.

If you will only ever receive 4 floats, I'd suggest the first form, which enforces the size of the array, any user (and mainly your future self) of your function will know to give only an array of four elements.

Calling your function from Python:

floats = [1.0, 2.0, 3.0, 4.0] # Or whatever values you want to pass, but be sure to give only 4
FloatArray4 = ctypes.c_float * 4 # Define a 4-length array of floats
parameter_array = FloatArray4(*floats) # Define the actual array to pass to your C function

I don't know if passing more than 4 floats to FloatArray4 raises an error -- I guess so, but I can't check right now.

As for your second question, if you want dynamic sized arrays (more than 4 elements), you'll have to you one of the other two profiles for your C function, in which case I advise you to put an extra int argument for the size of the array:

int c_func(const char *dir, float floats[], int size) {

or

int c_func(const char *dir, float *floats, int size) {

You can also use the standard size_t instead of int, it's designed just for that.

I you want to pass a multidimensional array, you add another pair of brackets:

int c_func(const char *dir, float floats[][4]) { // Note the explicit size for second dimension

but remember that for a C array, for all dimensions but the first, the dimensions must be explicitly specified. If the value is constant it wont be an issue, however if not you will have to use a pointer instead:

int c_func(const char *dir, float *floats[]) {

or

int c_func(const char *dir, float **floats) {

which are two identical syntaxs (the array will decay to a pointer). Once again, if your dimensions are dynamic, I suggest to add parameters for the size.

If you want supplementary dimensions, just repeat that last step.

Ctypes: fast way to convert a return pointer to an array or Python list

Slicing a ctypes array or pointer will automatically produce a list:

list_of_results = ret_ptr[:320000]

Depending on what you mean by "convert the pointer to an array" and what output types you can work with, you may be able to do better. For example, you can make a NumPy array backed by the buffer directly, with no data copying:

buffer_as_ctypes_array = ctypes.cast(ret_ptr, ctypes.POINTER(ctypes.c_double*320000))[0]
buffer_as_numpy_array = numpy.frombuffer(buffer_as_ctypes_array, numpy.float64)

This will of course break horribly if you deallocate the buffer while something still needs it.

Create an array of pointers in Python using ctypes

To convert a list some type into a ctypes array of that type, the straightforward idiom is:

(element_type * num_elements)(*list_of_elements)

In this case:

(c_char_p * len(array))(*array)

Note that (*array) expands the array as if each individual element was passed as a parameter, which is required to initialize the array.

Full example:

test.c - To verify the parameters are passed as expected.

#include <stdio.h>

#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif

API int foo(int numOfProp, const char** propName, const char** propValue) {
for(int i = 0; i < numOfProp; i++)
printf("name = %s value = %s\n", propName[i], propValue[i]);
return 1;
}

test.py

import ctypes as ct

dll = ct.CDLL('./test')
# Always define .argtypes and .restype to help ctypes error checking
dll.foo.argtypes = ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)
dll.foo.restype = ct.c_int

# helper function to build ctypes arrays
def make_charpp(arr):
return (ct.c_char_p * len(arr))(*(s.encode() for s in arr))

def foo(arr1, arr2):
if len(arr1) != len(arr2):
raise ValueError('arrays must be same length')
return dll.foo(len(arr1) ,make_charpp(arr1), make_charpp(arr2))

foo(['PropName1', 'PropName2'], ['10', '20'])

Output:

name = PropName1    value = 10
name = PropName2 value = 20

How to pass this numpy array to C with Ctypes?

You need to return the dynamically allocated memory, e.g. change your C code to something like:

#include <math.h>
#include <stdlib.h>
#include <stdio.h>

double tan1(double f) {
return sin(f)/cos(f);
}

double *loop(double *arr, int n) {
double *b = malloc(n * sizeof(double));
for(int i = 0; i < n; i++) {
b[i] = tan(arr[i]);
}
return b;
}

void freeArray(double *b) {
free(b);
}

On the Python side you have to declare parameter and return types. As mentioned by others in comments, you should also free dynamically allocated memory. Note that on the C side, arrays always decay into pointers. Therefore, you need an additional parameter which tells you the number of elements in the array.

Also if you return a pointer to double to the Python page, you must specify the size of the array. With np.frombuffer you can work with the data without making a copy of it.

import numpy as np
from ctypes import *

testlib = ctypes.CDLL('./testlib.so')

n = 500
dtype = np.float64
input_array = np.array(np.linspace(0, 4 * np.pi, n), dtype=dtype)
input_ptr = input_array.ctypes.data_as(POINTER(c_double))

testlib.loop.argtypes = (POINTER(c_double), c_int)
testlib.loop.restype = POINTER(c_double * n)
testlib.freeArray.argtypes = POINTER(c_double * n),

result_ptr = testlib.loop(input_ptr, n)
result_array = np.frombuffer(result_ptr.contents)

# ...do some processing
for value in result_array:
print(value)

# free buffer
testlib.freeArray(result_ptr)

How do you pass an array by reference from python to a C++ function using CTypes?

You are returning array_type. That’s a type, not an instance of the type. The instance is your array_type(*numbers) passed to the function, but it is not preserved. Assign it to a variable and return that, or better yet convert it back to a Python list as shown below:

test.cpp

#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif

extern "C" API void our_function(double * numbers) {
numbers[0] += 1;
numbers[1] += 1;
numbers[2] += 1;
}

test.py

import ctypes

_sum = ctypes.CDLL('./test')
_sum.our_function.argtypes = ctypes.POINTER(ctypes.c_double),

def our_function(numbers):
array_type = ctypes.c_double * 3 # equiv. to C double[3] type
arr = array_type(*numbers) # equiv. to double arr[3] = {...} instance
_sum.our_function(arr) # pointer to array passed to function and modified
return list(arr) # extract Python floats from ctypes-wrapped array

z = our_function([0, 1, 2])
print(z)

Output:

[1.0, 2.0, 3.0]

Passing a python list to c DLL function which returns array data using ctypes

The given C code doesn't match the Python wrapper. The function name doesn't match and the types don't match. Here's a working example for your learning:

test.c

#include <string.h>

#ifdef _WIN32
# define API __declspec(dllexport) // Windows-specific export
#else
# define API
#endif

/* This function takes pre-allocated inputs of an array of byte strings
* and an array of integers of the same length. The integers will be
* updated in-place with the lengths of the byte strings.
*/
API void epicsData(char** in_data, int* out_data, int size)
{
for(int i = 0; i < size; ++i)
out_data[i] = (int)strlen(in_data[i]);
}

test.py

from ctypes import *

dll = CDLL('test')
dll.epicsData.argtypes = POINTER(c_char_p),POINTER(c_int),c_int
dll.epicsData.restype = None

data = [b'A',b'BC',b'DEF'] # Must be byte strings.

# Create the Python equivalent of C 'char* in_data[3]' and 'int out_data[3]'.
in_data = (c_char_p * len(data))(*data)
out_data = (c_int * len(data))()

dll.epicsData(in_data,out_data,len(data))
print(list(out_data))

Output:

[1, 2, 3]


Related Topics



Leave a reply



Submit