How can I call a dynamic library function in nopython mode

I want to call a C function from the dynamic library in no python mode, and the input and output of this function are Pointers to structs。

from numba import njit, cfunc, carray
import ctypes
import pathlib
from ctypes import *
#from numba.experiment import jitclass
libname = pathlib.Path().absolute() / "arrayadd.cpython-38-x86_64-linux-gnu.so"
c_lib = ctypes.CDLL(libname)

func = c_lib.add

class Array(Structure):
  __fields__ = [("meminfo", POINTER(c_int)),
                ("parent", POINTER(c_int)),
                ("nitems", c_int),
                ("itemsize", c_int),
                ("data", POINTER(c_int)),
                ("shape", c_int * 4),
                ("strides", c_int * 4)]
func.argtypes = [POINTER(Array), POINTER(Array)]
func.restype = POINTER(Array)
a = Array()
a.nitems = 10000
a.itemsize = 4
a.shape = (c_int * 4)(10, 10, 10, 10)
a.strides = (c_int * 4)(1000, 100, 10, 1)

py_values = [i for i in range(a.nitems)]

a.data = (c_int * a.nitems)(*py_values)


b = Array()
b.nitems = 10000
b.itemsize = 4
b.shape = (c_int * 4)(10, 10, 10, 10)
b.strides = (c_int * 4)(1000, 100, 10, 1)
b.data = (c_int * b.nitems)(*py_values)

pointer_a = pointer(a)
pointer_b = pointer(b)
@njit
def test_add():
  c = func(pointer_a, pointer_b)
  return c


test_add()

The above code will give me an error. How can I change it, or how can I call dynamic library functions in numba?

Dear @YiNANzhang

The easiest way (imho) is to create the arrays as NumPy arrays and pass them to the test_add function. From there you can convert them to a void pointer and pass them to your C function using intrinsics.
I do exactly this in one of my libraries. Here I define a C functions that take a Numba arystruct_t as an argument. I then make these C functions accessible for Numba in this file. Note that I do not use ctypes, but load the library with llvmlite in this file. This way you can cache the functions.

If this could be a solution for you as well, I would be happy to explain it in more detail. The shown example is admittedly not simple and not the most readable.

Dear @sschaer
Thanks for your advice and your code, i get it. So, it is fine to use either ctypes or llvmlite as binding. And it can only be converted from struct to void* using intrinsics, right?