With python ctypes, how can I read a NUL-terminated array of NUL-terminated strings, e.g. ghostscript's gs_error_names
?
I know how to get the first value:
from ctypes import *
from ctypes.util import find_library
gs = CDLL(find_library("gs"))
print(c_char_p.in_dll(gs, 'gs_error_names').value)
I also know how to get a fixed number of values:
print(list((c_char_p * 10).in_dll(gs, 'gs_error_names')))
But how can I read all values until the end of the array?
With python ctypes, how can I read a NUL-terminated array of NUL-terminated strings, e.g. ghostscript's gs_error_names
?
I know how to get the first value:
from ctypes import *
from ctypes.util import find_library
gs = CDLL(find_library("gs"))
print(c_char_p.in_dll(gs, 'gs_error_names').value)
I also know how to get a fixed number of values:
print(list((c_char_p * 10).in_dll(gs, 'gs_error_names')))
But how can I read all values until the end of the array?
Share Improve this question asked Feb 8 at 1:51 mara004mara004 2,34213 silver badges30 bronze badges2 Answers
Reset to default 0from ctypes import *
from ctypes.util import find_library
gs = CDLL(find_library("gs"))
error_names_ptr = cast(gs.gs_error_names, POINTER(c_char_p))
error_names = []
i = 0
while val := error_names_ptr[i]:
error_names.append(val)
i += 1
print(error_names)
The key was to access the attribute directly and cast()
, rather than using in_dll()
, which segfaults.
How the global variable is declared matters. A char*
is exposed as the address of a pointer that points to the array. A char[]
is exposed as the address of the first element of the array.
Working example (Windows export syntax):
// Note C string constants have an additional nul added,
// So this is double nul-terminated.
__declspec(dllexport)
const char *data1 = "abc\0def\0ghi\0jkl\0";
// Also true if the array is unsized, double-nul terminated.
__declspec(dllexport)
const char data2[] = "abc\0def\0ghi\0jkl\0";
Reading each list of nul-terminated strings in Python
import ctypes as ct
dll = ct.CDLL('./test')
data1 = ct.POINTER(ct.c_char).in_dll(dll, 'data1') # unknown length
data2 = (ct.c_char * 17).in_dll(dll, 'data2') # if length known
data3 = ct.c_char.in_dll(dll, 'data2') # unknown length
print(data1[:17]) # slice to view multiple bytes
print(data2.raw) # Known length of 17
# If you don't know the length of char*, convert the
# pointer to a void pointer, the value of which is
# a Python integer that is the pointer address.
p = ct.cast(data1, ct.c_void_p).value
while s := ct.string_at(p):
print(s)
p += len(s) + 1
# If you don't know the length of char[], take the
# address of the array.
p = ct.addressof(data2)
while s := ct.string_at(p):
print(s)
p += len(s) + 1
Output:
b'abc\x00def\x00ghi\x00jkl\x00\x00'
b'abc\x00def\x00ghi\x00jkl\x00\x00'
b'abc'
b'def'
b'ghi'
b'jkl'
b'abc'
b'def'
b'ghi'
b'jkl'