const char **items;
unsigned nr_items;
result = test_char_ptr(&items, &nr_items);
for (unsigned i = 0; i < nr_items; i++) {
printf("string[%d]: %s\n", i, items[i]);
}
In ctypes, the type c_char means exactly that - a single character - and a POINTER to that really points specifically at that character and not anything subsequent. There is not a general “understanding” of how pointer decay, strings etc. work in C - that’s why, for example, the atomic types implement a * operator to give you array types. C allows arrays to decay to pointers implicitly all over the place, but Python is more strongly typed despite the dynamic typing - it maintains a strong distinction. So C.POINTER(C.c_char) doesn’t have access to any char values after the one directly pointed at; and it also doesn’t care if that char has a zero value.
To deal with the special case of C’s null-terminated strings, a separate c_char_p type is provided. (See also the full table of types).
You are actually really dealing with char**, not char*** in your ctypes code - in the C code, the third * is only being used in order to simulate pass-by-reference. (You’ll also want to check out the section on byref.) But what you really have is an array of count_ many “strings” (null-terminated C strings). That should look like c_char_p * count_ if I’m thinking clearly. (You might need to redesign the interface so that you get the count information separately, before the string data.)
Since I can not change the API of the real shared object (this C code was a test code), I think I can get away with changing the signature of the ctypes function.
First it expects just one c_char_p value and after the call the count_ will be valid. Then I change the signature to expect count_ * c_char_p values and make a second call. This time I get all the C strings out.