Listing [Python.Docs]: ctypes - A foreign function library for Python and [NumPy]: C-Types Foreign Function Interface (numpy.ctypeslib). The latter states about the following 2 items (emphasis is mine):
as_array
Create a numpy array from a ctypes array or POINTER.
ndpointer
An ndpointer instance is used to describe an ndarray in restypes and argtypes specifications. This approach is more flexible than using, for example, POINTER(c_double)
, since several restrictions can be specified, which are verified upon calling the ctypes function.
imageBuffer_ptr is not a "ctypes array or POINTER" (literally: it's not a ctypes.POINTER instance, but ctypes.c_void_p one (there's a subtle difference between the 2)).
The C layer (which in our case lies in the middle) doesn't know about NumPy arrays and so on, so from its PoV, that argument is simply an unsigned short* (doesn't have information about shape, ...). So, on it's other side (in the Python callback), you have to reconstruct the array from the plain C pointer. That can be achieved in 2 ways, like I did in the example below:
callback_ct: Use good-old C way: modify the callback's signature to use ct.POINTER(ct.c_ushort). That is the most straightforward, but the drawback is that it loses all the extra checks when called, meaning that it takes any pointer not just a ndarray one
callback_np: Manually recreate the array from the argument (using a series of conversions). It's more advanced (some might even consider it a bit hackish)
code00.py:
#!/usr/bin/env python
import sys
import ctypes as ct
import numpy as np
Base = ct.c_ushort
NpPtr = np.ctypeslib.ndpointer(
dtype=Base,
flags='C_CONTIGUOUS',
#ndim=1,
)
CtPtr = ct.POINTER(Base)
@ct.CFUNCTYPE(None, ct.c_int, ct.c_int, NpPtr)
def callback_np(w, h, data):
print("\ncallback_np:")
#print(data, type(data), type(data).__mro__, hex(data.value))
#print(data._ndim_, data._dtype_, data._shape_, data._type_, data._objects)
img = np.ctypeslib.as_array(ct.POINTER(Base).from_address(ct.addressof(data)), shape=(h, w))
#img = np.ctypeslib.as_array(data, shape=(h, w)) # Original form
print(img)
@ct.CFUNCTYPE(None, ct.c_int, ct.c_int, CtPtr)
def callback_ct(w, h, data):
print("\ncallback_ct:")
img = np.ctypeslib.as_array(data, shape=(h, w))
print(img)
def main(*argv):
w = 2
h = 3
arr = np.array(range(h * w), dtype=Base)
print("Array argument: ", arr, type(arr), arr.shape, arr.dtype, arr.ndim)
callback_ct(w, h, np.ctypeslib.as_ctypes(arr))
callback_np(w, h, arr)
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2: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.")
sys.exit(rc)
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q067323177]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py
Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 64bit on win32
Array argument: [0 1 2 3 4 5] <class 'numpy.ndarray'> (6,) uint16 1
callback_ct:
[[0 1]
[2 3]
[4 5]]
callback_np:
[[0 1]
[2 3]
[4 5]]
Done.