The Problem
I'm trying to use a function in a c library with the following prototype:
int glip_get_backends(const char ***name, size_t *count);
The name argument here is the problem. It is a 2 dimmensional char array passed by reference. In C the function is used as follows:
const char** name;
size_t count;
glip_get_backends(&name, &count);
for (size_t i = 0; i < count; i++) {
printf("- %s\n", name[i]);
}
Now I want to use this function from python using ctypes.
What I've tried
The most logical approach to me was doing this in python:
lglip = CDLL("libglip.so")
count = c_int(0)
backends = POINTER(POINTER(c_char))
lglip.glip_get_backends(byref(backends), byref(count))
which resulted in the error message
TypeError: byref() argument must be a ctypes instance, not '_ctypes.PyCPointerType'
The next approach was to use the POINTER() function three times and omitting the byref(), but that results in the following error:
ctypes.ArgumentError: argument 1: : Don't know how to convert parameter 1
Then I took inspiration from this question and came up with the following:
lglip = CDLL("libglip.so")
count = c_int(0)
backends = POINTER(POINTER(c_char))()
lglip.glip_get_backends(byref(backends), byref(count))
for i in range(0, count.value):
print backends[i]
Adding () after the definition of backends for some reason I don't fully comprehend has fixed the call, but the output I get is:
< ctypes.LP_c_char object at 0x7f4592af8710>
< ctypes.LP_c_char object at 0x7f4592af8710>
It seems I can't itterate over a 2 dimensional c array with one itterator in python, as it does not properly dereference it.
2 Answers 2
After realizing that in the C implementation %s is used to itterate over the substrings I came up with the following working solution:
lglip = CDLL("libglip.so")
count = c_int(0)
backends_c = POINTER(c_char_p)()
lglip.glip_get_backends(byref(backends_c), byref(count))
backends = []
for i in range(0, count.value):
backends.append(backends_c[i])
print backends
Update: changed POINTER(POINTER(c_char))() to POINTER(c_char_p)(), as eryksun suggested. This way I can easily access the strings.
4 Comments
c_char_p is a null-terminated string in ctypes (specifically this simple C type is a subclass of ctypes._SimpleCData defined with _type_ == 'z'). Use count = c_int(); backends = POINTER(c_char_p)() . That's a single pointer to a null-terminated string. Pass those by reference, as you're currently doing, for the function to fill in the initial pointer and array count. ctypes implements pointer arithmetic indexing, so backends[i] gets the i'th pointer in the array.c_char_p first, but then I still didn't have the parentheses at the end so I only got errors.backends = POINTER(c_char_p), but that didn't work since, as Mark Tolonen has also explained, that returns a type, not an instance. But I didn't know that was the problem so I switched to backends = POINTER(POINTER(c_char)) and eventually to backends = POINTER(POINTER(c_char))(). Using c_char_p again simply had not occured to me.If backends members can use index method, you can use this code:
lglip = CDLL("libglip.so")
count = c_int(0)
backends = POINTER(POINTER(c_char))()
lglip.glip_get_backends(byref(backends), byref(count))
for i in range(0, count.value):
print ''.join(backends[i][:backends[i].index("0")])
()tobackendsbecausePOINTER(POINTER(c_char))is a type, similar toc_intvsc_int(0)...first is type, second is instance of type.