You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3仅使用ctypes调用Windows API为文件夹添加标签的问题求解

实现方案

纯用ctypes即可实现文件夹标签写入,不需要引入pywin32等第三方库,核心通过Windows原生COM与存储API获取接口、完成属性写入。

接口获取路径

IPropertySetStorage和IPropertyStorage是COM复合存储标准接口,不存在直接从系统DLL导出的可直接调用的函数,必须通过以下标准路径获取实例:

  1. 调用ole32.dll导出的CoInitialize初始化当前线程COM环境
  2. 调用ole32.dll导出的StgOpenStorageEx函数,传入目标文件夹路径、读写权限参数,打开文件系统存储对象,获取IPropertySetStorage接口指针
  3. 拿到IPropertySetStorage指针后,调用其虚表中的Create方法,传入摘要信息属性集GUID、PROPSETFLAG_ANSI标志,即可获取IPropertyStorage接口实例

原有代码错误修正

之前的测试代码存在几个核心错误,会直接导致调用失败:

  • COM接口方法存储在接口虚表中,必须拿到有效接口指针后按偏移调用,不存在shell32.IPropertySetStorage.Create这类直接导出的函数
  • 摘要属性集的标签属性ID是数值常量0x5(对应PID_KEYWORDS),不是字符串"prop5";系统预定义属性不需要调用WritePropertyNames做名称映射
  • 缺少COM初始化、属性修改提交、shell缓存刷新步骤,即使写入成功也不会在资源管理器立即显示

完整可运行代码

import ctypes
from ctypes import wintypes, Structure, POINTER, byref, c_void_p, c_ulong, c_long, c_wchar_p

# 常量定义
STGM_READWRITE = 0x00000002
STGM_SHARE_EXCLUSIVE = 0x00000010
STGM_DIRECT = 0x00000000
STGFMT_FILE = 0x00000003
PROPSETFLAG_ANSI = 0x00000002
PID_KEYWORDS = 0x00000005
VT_LPWSTR = 0x001F
SHCNE_ATTRIBUTES = 0x00000800
SHCNF_PATHW = 0x0005

# GUID结构定义
class GUID(Structure):
    _fields_ = [
        ("Data1", wintypes.DWORD),
        ("Data2", wintypes.WORD),
        ("Data3", wintypes.WORD),
        ("Data4", wintypes.BYTE * 8)
    ]
    def __init__(self, guid_str):
        super().__init__()
        ctypes.oledll.ole32.CLSIDFromString(c_wchar_p(guid_str), byref(self))

# PROPVARIANT结构(简化版,仅支持字符串写入)
class PROPVARIANT(Structure):
    _fields_ = [
        ("vt", wintypes.WORD),
        ("wReserved1", wintypes.WORD),
        ("wReserved2", wintypes.WORD),
        ("wReserved3", wintypes.WORD),
        ("pwszVal", c_wchar_p)
    ]
    def set_str(self, s):
        self.vt = VT_LPWSTR
        self.pwszVal = s

# IPropertyStorage虚表方法索引
class IPropertyStorageVtbl(Structure):
    _fields_ = [
        ("QueryInterface", c_void_p),
        ("AddRef", c_void_p),
        ("Release", c_void_p),
        ("ReadMultiple", c_void_p),
        ("WriteMultiple", c_void_p),
        ("DeleteMultiple", c_void_p),
        ("ReadPropertyNames", c_void_p),
        ("WritePropertyNames", c_void_p),
        ("DeletePropertyNames", c_void_p),
        ("SetClass", c_void_p),
        ("Commit", c_void_p),
        ("Revert", c_void_p),
        ("Enum", c_void_p),
        ("Stat", c_void_p),
        ("SetTimes", c_void_p)
    ]

# IPropertySetStorage虚表方法索引
class IPropertySetStorageVtbl(Structure):
    _fields_ = [
        ("QueryInterface", c_void_p),
        ("AddRef", c_void_p),
        ("Release", c_void_p),
        ("Create", c_void_p),
        ("Open", c_void_p),
        ("Exists", c_void_p),
        ("Delete", c_void_p),
        ("Enum", c_void_p)
    ]

class IPropertySetStorage(Structure):
    _fields_ = [("lpVtbl", POINTER(IPropertySetStorageVtbl))]

class IPropertyStorage(Structure):
    _fields_ = [("lpVtbl", POINTER(IPropertyStorageVtbl))]

def add_folder_tag(folder_path, tag_str):
    # 初始化COM
    ctypes.oledll.ole32.CoInitialize(None)
    ppss = POINTER(IPropertySetStorage)()
    FMTID_SummaryInformation = GUID("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}")
    CLSID_NULL = GUID("{00000000-0000-0000-0000-000000000000}")
    IID_IPropertySetStorage = GUID("{0000013A-0000-0000-C000-000000000046}")

    # 打开存储,获取IPropertySetStorage
    hr = ctypes.windll.ole32.StgOpenStorageEx(
        c_wchar_p(folder_path),
        STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT,
        STGFMT_FILE,
        0,
        None,
        None,
        byref(IID_IPropertySetStorage),
        byref(ppss)
    )
    if hr != 0:
        raise ctypes.WinError(hr)
    
    ppstg = POINTER(IPropertyStorage)()
    # 创建属性集,获取IPropertyStorage
    create_func = ctypes.WINFUNCTYPE(
        c_long, POINTER(IPropertySetStorage), POINTER(GUID), POINTER(GUID),
        c_ulong, c_ulong, POINTER(POINTER(IPropertyStorage))
    )(ppss.contents.lpVtbl.contents.Create)
    hr = create_func(
        ppss, byref(FMTID_SummaryInformation), byref(CLSID_NULL),
        PROPSETFLAG_ANSI, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, byref(ppstg)
    )
    if hr != 0:
        ctypes.windll.ole32.CoUninitialize()
        raise ctypes.WinError(hr)
    
    # 写入标签属性
    propvar = PROPVARIANT()
    propvar.set_str(tag_str)
    write_func = ctypes.WINFUNCTYPE(
        c_long, POINTER(IPropertyStorage), c_ulong, POINTER(c_ulong),
        POINTER(PROPVARIANT), c_ulong
    )(ppstg.contents.lpVtbl.contents.WriteMultiple)
    propid = c_ulong(PID_KEYWORDS)
    hr = write_func(ppstg, 1, byref(propid), byref(propvar), 0)
    if hr != 0:
        ctypes.windll.ole32.CoUninitialize()
        raise ctypes.WinError(hr)
    
    # 提交修改
    commit_func = ctypes.WINFUNCTYPE(c_long, POINTER(IPropertyStorage), c_ulong)(ppstg.contents.lpVtbl.contents.Commit)
    commit_func(ppstg, 0)

    # 释放COM接口
    release_pss = ctypes.WINFUNCTYPE(c_ulong, POINTER(IPropertySetStorage))(ppss.contents.lpVtbl.contents.Release)
    release_pstg = ctypes.WINFUNCTYPE(c_ulong, POINTER(IPropertyStorage))(ppstg.contents.lpVtbl.contents.Release)
    release_pstg(ppstg)
    release_pss(ppss)

    # 刷新资源管理器缓存
    ctypes.windll.shell32.SHChangeNotify(
        SHCNE_ATTRIBUTES, SHCNF_PATHW, c_wchar_p(folder_path), None
    )
    ctypes.oledll.ole32.CoUninitialize()

if __name__ == "__main__":
    add_folder_tag(r"C:\Users\Agustin\Desktop\New folder", "TestTag")

注意事项

  • 运行脚本需要目标文件夹有写入权限,系统受保护目录(如Windows安装目录)会因权限不足调用失败
  • 如果目标文件夹已存在摘要信息属性集,Create方法会返回STG_E_FILEALREADYEXISTS错误,此时需要替换为调用IPropertySetStorage的Open方法获取IPropertyStorage指针后再写入
  • 需要写入多标签时,将标签字符串用英文分号分隔即可,资源管理器会自动识别为多个独立标签

内容的提问来源于stack exchange,提问作者Agustin Castañeda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 06:12:15