3

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;
}

2 Answers 2

7

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
Sign up to request clarification or add additional context in comments.

Comments

3

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.

2 Comments

"getting a suitable volume name for this call is a bit of a pain" - mountvol would have listed all volume GUIDs.
"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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.