Home > OS >  Python Ctypes - String to c_char_p to c_void_p and back
Python Ctypes - String to c_char_p to c_void_p and back

Time:07-22

I'm being unable to create certain C data structures in Python using Ctypes and then read them back properly. Consider the following:

Setup:

from ctypes import Structure, c_void_p, c_char_p, POINTER, cdll, cast

class GSLIST(Structure):
    """GLib's GSList."""
    pass

GSLIST._fields_ = [
    ('data', c_void_p),
    ('next', POINTER(GSLIST))
]

lib = cdll.LoadLibrary('/path/to/mylib.o')

lib.g_slist_prepend.argtypes = [POINTER(GSLIST), c_char_p]
lib.g_slist_prepend.restype = POINTER(GSLIST)

Example:

py_str_in = bytes('hi', 'ascii')

agslist_ptr = POINTER(GSLIST)()  # NULL
agslist_ptr = lib.g_slist_prepend(agslist_ptr, c_char_p(py_str_in))

print(cast(agslist_ptr.contents.data, c_char_p).value.decode())
# 'hi'

The example may be a bit more complicated than it needs to be, but it's been hard to replicate at a smaller scale.

What the example does is to save the "hi" string in the data field of the first element of a GSList (the most common list type in the GLib library). This field is of type void* so that it can support whatever type. So, first we convert to a c_char_p to make it compatible with void*, and then to read it back we cast() it.

The example as shown works as expected. It breaks if we do instead:

agslist_ptr = lib.g_slist_prepend(agslist_ptr, c_char_p(bytes('hi', 'ascii')))

Simply skipping the assignment to the py_str_in variable results in just '' for an output.

Also, if instead of 'hi' I use any single character, it also works fine.

I can't really explain what is going on and the documentation doesn't go into too much depth. If I had to guess, something with string literals or garbage collection. But it could also be just buggy code.

Thanks for any insight.

CodePudding user response:

The 2nd instance that fails is due to c_char_p(bytes('hi', 'ascii') being the only reference to the object. Python doesn't know that C saved that pointer, so after that line executes the object is destroyed and the pointer that C saved is now invalid. Referencing it is undefined behavior.

In the 1st case the py_str_in reference still exists so the pointer C saved is still valid.

  • Related