5

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.

youR.Fate
  • 796
  • 4
  • 10
  • 29
  • Did you try iterating over objects that are in backends[i]? This is a 2-dimensional array, so you may need to use nested loop for this. I am not familiar with ctypes usage, just using my intuition. – Nebril May 19 '15 at 11:54
  • 1
    I can try that, but I don't know the ranges of the loops. That's why in C it just uses one iterator. Oh, I've just realized in C it uses %s, therefore printing until \0. Working on it. – youR.Fate May 19 '15 at 11:55
  • 3
    You had to add the `()` to `backends` because `POINTER(POINTER(c_char))` is a *type*, similar to `c_int` vs `c_int(0)`...first is type, second is instance of type. – Mark Tolonen May 19 '15 at 17:10

2 Answers2

6

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.

youR.Fate
  • 796
  • 4
  • 10
  • 29
  • 1
    `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. – Eryk Sun May 19 '15 at 19:23
  • Thank you, I will try that out tomorrow and update the answer if it works as expected. – youR.Fate May 19 '15 at 19:26
  • I had actually tried using `c_char_p` first, but then I still didn't have the parentheses at the end so I only got errors. – youR.Fate May 19 '15 at 19:37
  • Yes, I had tried using `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. – youR.Fate May 19 '15 at 19:44
-1

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")])
Nebril
  • 3,153
  • 1
  • 33
  • 50
  • Nope, that only gives `AttributeError: 'LP_c_char' object has no attribute 'index'`. – youR.Fate May 19 '15 at 12:32
  • Then I guess you will be good with your code. I cannot think of more elegant way that would use simple array-like syntax there. – Nebril May 19 '15 at 12:36