# 第 ⑥ 章 · 属性处理器（IPropertyStore）

> 用一个 IPropertyStore 统一供数据给详细信息列、信息提示、分组和搜索——这是老“列处理器 IColumnProvider（Vista 已移除）”的现代替代；讲 PROPERTYKEY、系统属性与自定义 .propdesc schema。

- 作者：David（道雾轩）
- 专栏：Windows Shell 扩展指南（https://daiw.org/manual/windows-shell-ext.md）
- 最后更新：2026-09-13
- 原文：https://daiw.org/manual/windows-shell-ext/property-handler
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

老教程里有一章叫“列处理器”（`IColumnProvider`），用来往资源管理器的详细信息视图加自定义列。**这个接口在 Windows Vista 就被彻底移除了**。它的现代替代——而且能力大得多——是**属性处理器 `IPropertyStore`**。

关键变化：过去详细信息列、信息提示、属性页各写一套；现在**一个属性处理器统一供数据**，同时喂给：详细信息列、信息提示（infotip）、分组、排序、搜索索引、属性页。写一处，处处生效。

本例给自定义扩展 `.shdemo` 暴露一个属性。配套代码：[`06-property`](https://github.com/davidapp/shell-extension-demos/tree/main/06-property)。

## 属性 = PROPERTYKEY + 值

属性系统里每个属性由一个 **`PROPERTYKEY`**（一个 `{FMTID}` + 一个整数 PID）唯一标识，值是 **`PROPVARIANT`**。系统预定义了几百个规范属性（`System.Author`、`System.Comment`、`System.Size`…），头文件 `propkey.h` 里是 `PKEY_Author`、`PKEY_Comment` 这些常量。

`IPropertyStore` 就五个方法：

```cpp title="PropertyStore.cpp（节选）"
IFACEMETHODIMP PropertyStore::GetCount(DWORD* c) { *c = 1; return S_OK; }          // 我暴露几个属性
IFACEMETHODIMP PropertyStore::GetAt(DWORD i, PROPERTYKEY* k) {                     // 第 i 个是哪个键
    if (i != 0) return E_INVALIDARG;
    *k = PKEY_Comment; return S_OK;
}
IFACEMETHODIMP PropertyStore::GetValue(REFPROPERTYKEY key, PROPVARIANT* pv) {      // 取值
    PropVariantInit(pv);
    if (IsEqualPropertyKey(key, PKEY_Comment)) {
        wchar_t buf[96];
        StringCchPrintfW(buf, ARRAYSIZE(buf), L"演示属性：该文件 %llu 字节", m_size);
        return InitPropVariantFromString(buf, pv);
    }
    return S_OK;   // 未知属性返回 VT_EMPTY
}
IFACEMETHODIMP PropertyStore::SetValue(REFPROPERTYKEY, REFPROPVARIANT) { return STG_E_ACCESSDENIED; } // 只读
IFACEMETHODIMP PropertyStore::Commit() { return STG_E_ACCESSDENIED; }
```

内容还是通过 `IInitializeWithStream` 以流的形式拿到（和缩略图、属性同一套现代初始化方式）。

<Callout type="info">
  用**系统预定义属性**（如 `PKEY_Comment`）拿来即用。要暴露**自定义属性**（比如“项目编号”），得写一份 `.propdesc` XML schema，用 [`PSRegisterPropertySchema`](https://learn.microsoft.com/zh-cn/windows/win32/api/propsys/nf-propsys-psregisterpropertyschema) 注册，属性才有规范名、类型和本地化显示名，才能作为列/分组项出现。
</Callout>

## 注册

属性处理器注册在 **HKLM 的属性系统**里，按扩展名绑定：

```
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\PropertyHandlers\.shdemo
    (默认) = {CLSID}
```

```cpp
RegWriteString(HKEY_LOCAL_MACHINE,
    L"SOFTWARE\\...\\PropertySystem\\PropertyHandlers\\.shdemo", nullptr, clsidStr);
```

<Callout type="warn">
  属性处理器跑在**索引器 / 属性系统宿主**里、且会被系统缓存。别在里面做重活；写属性（可写处理器）时要正确实现 `SetValue`/`Commit` 的事务语义。改扩展名绑定后可能要重启资源管理器或重建索引才生效。
</Callout>

## 迁移提示：从列处理器到属性处理器

如果你在维护老代码里的 `IColumnProvider`：它在 Vista 起就不再被调用了。把“每列一个值”改写成“每个 `PROPERTYKEY` 一个值”的属性处理器，再在文件类型上配置要显示哪些列（`System.PropList.*` 或属性 schema），即可恢复并增强原来的详细信息列功能。

## 参考

- [IPropertyStore](https://learn.microsoft.com/zh-cn/windows/win32/api/propsys/nn-propsys-ipropertystore) · [构建属性处理器](https://learn.microsoft.com/zh-cn/windows/win32/properties/building-property-handlers)
- [属性 schema（.propdesc）](https://learn.microsoft.com/zh-cn/windows/win32/properties/propdesc-schema-entry) · [IdealPropertyHandler 官方示例](https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/winui/shell/appshellintegration/IdealPropertyHandler)
