I've written this minimal reproducible example to calculate the Desktop folder on Windows "the hard way" (using SHGetKnownFolderPath
), but I seem to end up with a Success error code while the output buffer only yields b'C'
when dereferenced via the .result
property of c_char_p
. What am I doing wrong?
My code does this:
- Converts the desired GUID into the cursed
_GUID
struct format according to Microsoft's specification - Allocates
result_ptr = c_char_p()
which is initially a NULL pointer but will be overwritten with the pointer to the result - Calls
SHGetKnownFolderPath
with the desired GUID struct, no flags, on the current user, passing ourresult_ptr
by reference so its value can be overwritten - If
SHGetKnownFolderPath
indicated success, dereferencesresult_ptr
using.value
I'm getting a result which is only a single char long, but I thought that c_char_p
is supposed to be the pointer to the start of a null-terminated string.
Is Windows writing a bogus string into my pointer, am I reading its value out wrongly, or have I made some other error in building my function?
import contextlibimport ctypesimport ctypes.wintypesimport functoolsimport osimport pathlibimport typesimport uuidtry: wintypes_GUID = ctypes.wintypes.GUIDexcept AttributeError: class wintypes_GUID(ctypes.Structure): # https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid # https://github.com/enthought/comtypes/blob/1.3.1/comtypes/GUID.py _fields_ = [ ('Data1', ctypes.c_ulong), ('Data2', ctypes.c_ushort), ('Data3', ctypes.c_ushort), ('Data4', ctypes.c_ubyte * 8) ] @classmethod def _from_uuid(cls, u): u = uuid.UUID(u) u_str = f'{{{u!s}}}' result = wintypes_GUID() errno = ctypes.oledll.ole32.CLSIDFromString(u_str, ctypes.byref(result)) if errno == 0: return result else: raise RuntimeError(f'CLSIDFromString returned error code {errno}')DESKTOP_UUID = 'B4BFCC3A-DB2C-424C-B029-7FE99A87C641'def get_known_folder(uuid): # FIXME this doesn't work, seemingly returning just b'C' no matter what result_ptr = ctypes.c_char_p() with _freeing(ctypes.oledll.ole32.CoTaskMemFree, result_ptr): errno = ctypes.windll.shell32.SHGetKnownFolderPath( ctypes.pointer(wintypes_GUID._from_uuid(uuid)), 0, None, ctypes.byref(result_ptr) ) if errno == 0: result = result_ptr.value if len(result) < 2: import warnings warnings.warn(f'result_ptr.value == {result!r}') return pathlib.Path(os.fsdecode(result)) else: raise RuntimeError(f'Shell32.SHGetKnownFolderPath returned error code {errno}')@contextlib.contextmanagerdef _freeing(freefunc, obj): try: yield obj finally: freefunc(obj)assert get_known_folder(DESKTOP_UUID) ==\ pathlib.Path('~/Desktop').expanduser(),\ f'Result: {get_known_folder(DESKTOP_UUID)!r}; expcected: {pathlib.Path("~/Desktop").expanduser()!r}'