# How to get null terminated strings from a buffer?

**URL:** <https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904>\
**Category:** Python Help\
**Created:** [May 19, 2023, 11:28am UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904 "2023-05-19T11:28:17Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![kcvinker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kcvinker/32/12540_2.png) [@kcvinker](https://discuss.python.org/u/kcvinker)\
**Post date:** [May 19, 2023, 11:28am UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/1 "2023-05-19T11:28:17Z")

</div>

Hi all,  
I am writing a gui library in Python with ctypes. So far so good. Currently I am writing the common dialog boxes like file open, file save and folder browser. Windows will allow us to open more than one file with `OFN_ALLOWMULTISELECT` flag. But if we use this flag, the `lpstrFile` member of the `OPENFILENAMEW` struct behaves quite differently. The `lpstrFile` member is an array with 260 wchar character length. The documentation says this ;

```auto
If the user selects more than one file, the lpstrFile buffer returns the path to the current directory
followed by the file names of the selected files. 
The nFileOffset member is the offset, in bytes or characters, 
to the first file name, and the nFileExtension member is not used. 
For Explorer-style dialog boxes, the directory and file name strings 
are NULL separated, with an extra NULL character after the last file name. 

```

So this is what i am using for D programming language.

```plaintext
void extractFileNames(wchar[] buff, int startPos) {
        int offset = startPos;
        for (int i = startPos; i < MAX_PATH; i++) {
            wchar wc = buff[i];
            if (wc == '\u0000') {
                wchar[] slice = buff[offset..i];
                offset = i + 1;
                this.mSelFiles ~= slice.to!string; // Adding sliced file name to array.
                if (buff[offset] == '\u0000') break;
            }
        }
    }

```

I am using Explorer-style dialog box. So I have a buffer which should contain the directory path and all selected file names separated by null character. But `buffer.value` only returns the directory path. Is the rest of the data missing because of the null character after the directory path? So how to retrieve the data after the first null character?

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [May 19, 2023, 1:25pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/2 "2023-05-19T13:25:52Z")

</div>

Do you have a small example that shows what you can so far?

Once you have ctypes returning the array of 260 word (520 bytes) buffer  
to python you can decode it with buf.decode(‘utf-16’) and then look for ‘\0’  
in the unicode string. I would be tempted to use

```auto
parts = buf.decode('utf-16').split('\0')
cur_dir = parts[0]
assert parts[-1] == ''
all_files = parts[1:-1]

```

---

<div class="post-metadata">

**Author:** ![eryksun](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/eryksun/32/697_2.png) [@eryksun](https://discuss.python.org/u/eryksun)\
**Post date:** [May 19, 2023, 6:47pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/3 "2023-05-19T18:47:23Z")

</div>

Here’s a basic example that sets a buffer in the struct, with the field type set to `POINTER(WCHAR)` in order to avoid having to cast the buffer. Then after calling `GetOpenFileNameW()`, it simply converts the buffer to a string using a slice; strips off the trailing nulls; splits out the directory path and filenames; and returns the joined paths.

```auto
import os
import ctypes
from ctypes import wintypes

comdlg32 = ctypes.WinDLL('comdlg32', use_last_error=True)

OFN_ALLOWMULTISELECT = 0x00000200
OFN_PATHMUSTEXIST = 0x00000800
OFN_FILEMUSTEXIST = 0x00001000
OFN_EXPLORER = 0x00080000

FNERR_SUBCLASSFAILURE = 0x3001
FNERR_INVALIDFILENAME = 0x3002
FNERR_BUFFERTOOSMALL = 0x3003

UINT_PTR = wintypes.WPARAM
LPOFNHOOKPROC = ctypes.WINFUNCTYPE(UINT_PTR, wintypes.HWND, wintypes.UINT,
                    wintypes.WPARAM, wintypes.LPARAM)

class OPENFILENAMEW(ctypes.Structure):
    _fields_ = (
        ('lStructSize', wintypes.DWORD),
        ('hwndOwner', wintypes.HWND),
        ('hInstance', wintypes.HINSTANCE),
        ('lpstrFilter', wintypes.LPWSTR),
        ('lpstrCustomFilter', wintypes.LPWSTR),
        ('nMaxCustFilter', wintypes.DWORD),
        ('nFilterIndex', wintypes.DWORD),
        ('lpstrFile', ctypes.POINTER(wintypes.WCHAR)),
        ('nMaxFile', wintypes.DWORD),
        ('lpstrFileTitle', wintypes.LPWSTR),
        ('nMaxFileTitle', wintypes.DWORD),
        ('lpstrInitialDir', wintypes.LPCWSTR),
        ('lpstrTitle', wintypes.LPCWSTR),
        ('Flags', wintypes.DWORD),
        ('nFileOffset', wintypes.WORD),
        ('nFileExtension', wintypes.WORD),
        ('lpstrDefExt', wintypes.LPCWSTR),
        ('lCustData', wintypes.LPARAM),
        ('lpfnHook', LPOFNHOOKPROC),
        ('lpTemplateName', wintypes.LPCWSTR),
        ('pvReserved', wintypes.LPVOID),
        ('dwReserved', wintypes.DWORD),
        ('FlagsEx', wintypes.DWORD),
    )

LPOPENFILENAMEW = ctypes.POINTER(OPENFILENAMEW)

comdlg32.GetOpenFileNameW.argtypes = (LPOPENFILENAMEW,)

class ComDlgError(Exception):
    def __init__ (self, error, function_name=''):
        self.error = error
        self.function_name = function_name

    def __str__ (self):
        if self.function_name:
            return f'{self.function_name} [Error {self.error}]'
        return f'[Error {self.error}]'

def get_open_filename():
    ofn = OPENFILENAMEW()
    ofn.lStructSize = ctypes.sizeof(ofn)
    ofn.lpstrFilter = 'Text documents\0*.txt\0All files\0*.*\0\0'
    ofn.nFilterIndex = 1
    ofn.Flags = (OFN_EXPLORER | OFN_ALLOWMULTISELECT |
                 OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST)
    ofn.nMaxFile = 32768 + 256 * 100 + 1 # 1 long path + 100 base filenames
    buf = (ctypes.c_wchar * ofn.nMaxFile)()
    ofn.lpstrFile = buf
    if not comdlg32.GetOpenFileNameW(ctypes.byref(ofn)):
        raise ComDlgError(comdlg32.CommDlgExtendedError(), 'GetOpenFileNameW')
    # If a single file is selected, the full path is stored without a null
    # after the path of the directory. If multiple files are selected, the
    # path of the directory is terminated by a null, followed by the null-
    # terminated filenames. In either case, the first filename begins at
    # nFileOffset.
    s = buf[:].rstrip('\0')
    path = s[:ofn.nFileOffset].rstrip('\0')
    filenames = s[ofn.nFileOffset:].split('\0')
    return [os.path.join(path, f) for f in filenames] 

```

For example:

```plaintext
>>> get_open_filename()
['C:\\Program Files\\Python311\\LICENSE.txt', 'C:\\Program Files\\Python311\\NEWS.txt']

```

---

<div class="post-metadata">

**Author:** ![kcvinker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kcvinker/32/12540_2.png) [@kcvinker](https://discuss.python.org/u/kcvinker)\
**Post date:** [May 19, 2023, 7:59pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/4 "2023-05-19T19:59:05Z")

</div>

Hi,  
Thanks for the reply. I tried the `decode` function. But python says `AttributeError: 'c_wchar_Array_260' object has no attribute 'decode'`. So this is my code.

```python
# Parameters
# 1. obj - may be a FileOpenDialog class or FileSaveDialog class.
# 2. isOpen - bool
# 3. hwnd - HWND (A window handle)
def _showDialogHelper(obj, isOpen, hwnd):
    ofn = OPENFILENAMEW()
    ofn.hwndOwner = hwnd
    buffer = create_unicode_buffer(MAX_PATH)
    idBuff = None if obj._initDir == "" else cast(create_unicode_buffer(obj._initDir), c_wchar_p)
    ofn.lStructSize = sizeof(OPENFILENAMEW)
    ofn.lpstrFilter = cast(create_unicode_buffer(obj._filter), c_wchar_p)
    ofn.lpstrFile = cast(buffer, c_wchar_p)
    ofn.lpstrInitialDir = idBuff
    ofn.lpstrTitle = obj._title
    ofn.nMaxFile = MAX_PATH
    ofn.nMaxFileTitle = MAX_PATH
    ofn.lpstrDefExt = '\u0000'
    retVal = 0
    if isOpen:
        ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
        if obj._multiSel: ofn.Flags |= OFN_ALLOWMULTISELECT | OFN_EXPLORER
        if obj._showHidden: ofn.Flags |= OFN_FORCESHOWHIDDEN
        retVal = GetOpenFileName(byref(ofn))
        if retVal > 0 and obj._multiSel:
            parts = buffer.decode('utf-16').split('\0')
            print(parts)
    else:
        ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT
        retVal = GetSaveFileName(byref(ofn))

    if retVal != 0:
        obj._fileNameStart = ofn.nFileOffset
        obj._extStart = ofn.nFileExtension
        obj._selPath = buffer.value
        return True
    return False

```

---

<div class="post-metadata">

**Author:** ![kcvinker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kcvinker/32/12540_2.png) [@kcvinker](https://discuss.python.org/u/kcvinker)\
**Post date:** [May 19, 2023, 8:04pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/5 "2023-05-19T20:04:47Z")

</div>

Hi, Thanks for the reply. Let me try your suggestion. And yeah, your code looks great. I think there is some valuable points I can learn from that.

---

<div class="post-metadata">

**Author:** ![kcvinker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kcvinker/32/12540_2.png) [@kcvinker](https://discuss.python.org/u/kcvinker)\
**Post date:** [May 19, 2023, 8:08pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/6 "2023-05-19T20:08:24Z")

</div>

> [@eryksun](#):
>
> ```auto
> s = buf[:].rstrip('\0')
> path = s[:ofn.nFileOffset].rstrip('\0')
> filenames = s[ofn.nFileOffset:].split('\0')
> 
> ```

Wow !! This worked like a charm. Thank you once again.

---

<div class="post-metadata">

**Author:** ![kcvinker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kcvinker/32/12540_2.png) [@kcvinker](https://discuss.python.org/u/kcvinker)\
**Post date:** [May 19, 2023, 8:16pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/7 "2023-05-19T20:16:31Z")

</div>

So I ended up on this.

```python
def _extractFileNames(self, buff, startPos):
        parts = buff[:].rstrip('\0')
        dirPath = parts[:startPos].rstrip('\0')
        names = parts[startPos:].split('\0')
        for name in names:
            self._fNames.append(f"{dirPath}\{name}")

```

---

<div class="post-metadata">

**Author:** ![eryksun](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/eryksun/32/697_2.png) [@eryksun](https://discuss.python.org/u/eryksun)\
**Post date:** [May 19, 2023, 8:24pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/8 "2023-05-19T20:24:30Z")

</div>

> [@kcvinker](#):
>
> ` buffer = create_unicode_buffer(MAX_PATH)`

You should use a larger buffer. The open dialog can return a long path to a directory, which can have up to 32767 characters, plus each filename can have up to 255 characters.

---

<div class="post-metadata">

**Author:** ![kcvinker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kcvinker/32/12540_2.png) [@kcvinker](https://discuss.python.org/u/kcvinker)\
**Post date:** [May 19, 2023, 8:26pm UTC](https://discuss.python.org/t/how-to-get-null-terminated-strings-from-a-buffer/26904/9 "2023-05-19T20:26:09Z")

</div>

Yes. I changed that to something similar in your code. I thought it should be only 260 chars. My bad.
