tags:

views:

64

answers:

2

I'm trying to call an external library function that returns a NULL-terminated array of NULL-terminated strings.

kernel32 = ctypes.windll.kernel32
buf = ctypes.create_unicode_buffer(1024)
length = ctypes.c_int32()
if kernel32.GetVolumePathNamesForVolumeNameW(ctypes.c_wchar_p(volume),
    buf, ctypes.sizeof(buf), ctypes.pointer(length)):
    ## ???

In other words:

buf = ctypes.create_unicode_buffer(u'Hello\0StackOverflow\0World!\0')

How do I access all contents of buf as a Python list? buf.value only reaches up to the first NULL.

In C it would be something like this:

while (*sz) {; 
    doStuff(sz);
    sz += lstrlen(sz) + 1;
}
+1  A: 

It would be easier if you posted runnable code: getting a suitable volume name for this call is a bit of a pain. buf is an array containing length characters. The last two characters are nulls, so ignore them, convert the array to a string using ''.join() and split on the null characters.

import ctypes
kernel32 = ctypes.windll.kernel32

def volumes():
    buf = ctypes.create_unicode_buffer(1024)
    length = ctypes.c_int32()
    handle = kernel32.FindFirstVolumeW(buf, ctypes.sizeof(buf))
    if handle:
        yield buf.value
        while kernel32.FindNextVolumeW(handle, buf, ctypes.sizeof(buf)):
            yield buf.value
        kernel32.FindVolumeClose(handle)

def VolumePathNames(volume):
    buf = ctypes.create_unicode_buffer(1024)
    length = ctypes.c_int32()
    kernel32.GetVolumePathNamesForVolumeNameW(ctypes.c_wchar_p(volume),
        buf, ctypes.sizeof(buf), ctypes.pointer(length))
    return ''.join(buf[:length.value-2]).split('\0')

for volume in volumes():
    print volume
    print VolumePathNames(volume)

When I run this all the lists just contain a single name, but if you double check against length that's all they contained in returned buffer.

Duncan
"getting a suitable volume name for this call is a bit of a pain" - `mountvol` would have listed all volume GUIDs.
grawity
"When I run this all the lists just contain a single name, but if you double check against length that's all they contained in returned buffer." - That is almost always the case, yes, but some of my removable drives can be reached through a Unix-style mountpoint in addition to a drive letter. Either way, I was just looking for a generic solution, not necessarily `GetVolume...()`-specific. But `split('\0')` will be good enough for now.
grawity
A: 

After discovering ctypes.wstring_at() and ctypes.addressof(), I got this:

def wszarray_to_list(array):
    offset = 0
    while offset < ctypes.sizeof(array):
        sz = ctypes.wstring_at(ctypes.addressof(array) + offset*2)
        if sz:
            yield sz
            offset += len(sz)+1
        else:
            break
grawity