Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you convert a char * with 0-value bytes into a python string?

Tags:

python

ctypes

Using the ctypes module I can easily import a POINTER(c_char) or a c_char_p type into python, but neither of these provides a way to end up with a python string that contains zero value bytes.

c_char_p is zero terminated, meaning that a char * array from C is terminated at the first zero value.

POINTER(c_char) is the recommended way of importing binary data that can have 0 values, but there doesn't seem to be a way to directly convert this into a python string.

I can do this:

pixels = clibblah.get_pixels()
a = ""
for i in range(0, clibblah.get_pixel_length()):
    a += pixels[i]

...but this 1) doesn't seem very pythony, and 2) takes forever (converting a 640x480 block of pixel data takes about 2 seconds on my mac).

I've seen a bunch of questions regarding this on stack overflow, but darned if I can see one that isn't either people going "why do you need to do that?" or "c_char_p will do what you want" (it doesn't, as I've described above).

The only credible advice I've seen is to use the c api PyString_FromStringAndSize, as recommended here: http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/version/Doc/FAQ.html

Can't really see how that helps though, because afaik that's a cython feature, not a python one.

For the interested, the reason I need to do this is I'm working with panda3d and a kinect, and the kinect c api provides an array of unsigned char * values and the panda3d api lovingly provides a setPixels() call that only takes a python string as an argument.

like image 528
Doug Avatar asked Apr 11 '12 02:04

Doug


2 Answers

As you said, use a POINTER(c_char) to get a pointer to the array of binary data. To put that together into a string, you can just take a slice of it, since array indexing works as expected with ctypes pointers:

clibblah = ctypes.cdll.LoadLibrary('clibblah.dylib')
get_pixels = clibblah.get_pixels
get_pixels.restype = ctypes.POINTER(ctypes.c_char)

pixels = get_pixels()
num_pixels = clibblah.get_pixel_length()

# Slice the ctypes array into a Python string
a = pixels[:num_pixels]
like image 195
Adam Rosenfield Avatar answered Sep 20 '22 22:09

Adam Rosenfield


There are a few different methods. I like ctypes.string_at because it isn't finicky: it works regardless of whether you supply a c_char_p type, or a pointer-to-c_char, or a void pointer type, or even just an int address.

s = b'hello\x00world' # create a string containing null bytes
sz = len(s)
from ctypes import *

p = c_char_p(s) # obtain a pointer of various types
p2 = cast(p,POINTER(c_char))
address = cast(p,c_void_p).value

print p.value # by default it is interpreted as null-terminated

print p2[:sz] # various methods of explicitly specifying the full length
print string_at(p,size=sz)
print (c_char * sz).from_address(address).raw
like image 34
benjimin Avatar answered Sep 19 '22 22:09

benjimin