从Python调用SHGetKnownFolderPath?

问题描述 投票:0回答:1

我编写了这个最小的可重现示例来“艰难地”计算 Windows 上的桌面文件夹(使用

SHGetKnownFolderPath
),但我似乎最终得到了成功错误代码,而输出缓冲区在取消引用时仅产生
b'C'
通过
.result
c_char_p
属性。我做错了什么?

我的代码是这样做的:

  1. 根据 Microsoft 的规范将所需的 GUID 转换为被诅咒的
    _GUID
    结构格式
  2. 分配
    result_ptr = c_char_p()
    ,它最初是一个 NULL 指针,但将被指向结果的指针覆盖
  3. 在当前用户上使用所需的 GUID 结构(无标志)调用
    SHGetKnownFolderPath
    ,通过引用传递我们的
    result_ptr
    ,以便可以覆盖其值
  4. 如果
    SHGetKnownFolderPath
    指示成功,则使用
    result_ptr
     取消引用 
    .value

我得到的结果只有一个字符长,但我认为

c_char_p
应该是指向空终止字符串开头的指针。

Windows 是否在我的指针中写入了虚假字符串,我是否错误地读取了它的值,或者我在构建函数时犯了其他错误?

import contextlib
import ctypes
import ctypes.wintypes
import functools
import os
import pathlib
import types
import uuid

try:
    wintypes_GUID = ctypes.wintypes.GUID
except 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.contextmanager
def _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}'

python ctypes c-strings shell32.dll lpwstr
1个回答
0
投票

根据[MS.Learn]:SHGetKnownFolderPath函数(shlobj_core.h)强调是我的):

[out] ppszPath

类型:PWSTR*

此方法返回时,包含指向以 null 结尾的 Unicode 字符串的 指针的地址

函数以WIDE016bit)字符串形式返回路径,即wchar_t*,或[Python.Docs]:类ctypes.c_wchar_p
检查[SO]:将 utf-16 字符串传递给 Windows 函数(@CristiFati 的答案)了解更多详细信息。

因此,您需要更改的(在get_known_folder中)是:

result_ptr = ctypes.c_wchar_p()  # Note the 'w'
© www.soinside.com 2019 - 2024. All rights reserved.