Python3仅使用ctypes调用Windows API为文件夹添加标签的问题求解
实现方案
纯用ctypes即可实现文件夹标签写入,不需要引入pywin32等第三方库,核心通过Windows原生COM与存储API获取接口、完成属性写入。
接口获取路径
IPropertySetStorage和IPropertyStorage是COM复合存储标准接口,不存在直接从系统DLL导出的可直接调用的函数,必须通过以下标准路径获取实例:
- 调用ole32.dll导出的
CoInitialize初始化当前线程COM环境 - 调用ole32.dll导出的
StgOpenStorageEx函数,传入目标文件夹路径、读写权限参数,打开文件系统存储对象,获取IPropertySetStorage接口指针 - 拿到
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
相关产品推荐
相关产品推荐

