初始化属性处理程序

本主题介绍如何创建和注册属性处理程序以使用 Windows 属性系统。

本主题按如下所示进行组织:

属性处理程序

属性处理程序是属性系统的关键部分。 它们在进程内由索引器调用以读取和索引属性值,并且也在进程内由 Windows 资源管理器调用,以便直接在文件中读取和写入属性值。 需要仔细编写和测试这些处理程序,以防止性能下降或受影响的文件中数据丢失。 有关影响属性处理程序实现的索引器特定注意事项的详细信息,请参阅 开发适用于 Windows 搜索的属性处理程序

本主题讨论一个基于 XML 的示例文件格式,该格式描述具有 .recipe 文件扩展名的食谱。 .recipe 文件扩展名注册为其自己的非重复文件格式,而不是依赖于更通用的 .xml 文件格式,其处理程序使用辅助流来存储属性。 建议为文件类型注册唯一的文件扩展名。

在您开始之前

属性处理程序是为特定文件格式创建 IPropertyStore 抽象的 COM 对象。 它们以符合其规范的方式读取(分析)并写入此文件格式。 某些属性处理程序根据抽象访问特定文件格式的 API 执行其工作。 在为文件格式开发属性处理程序之前,需要了解文件格式如何存储属性,以及如何将这些属性(名称和值)映射到属性存储抽象中。

规划实现时,请记住,属性处理程序是在 Windows 资源管理器、Windows 搜索索引器和使用 Shell 项编程模型的第三方应用程序等进程的上下文中加载的低级别组件。 因此,属性处理程序不能在托管代码中实现,并且应在C++中实现。 如果处理程序使用任何 API 或服务来执行其工作,则必须确保这些服务可以在加载属性处理程序的环境中正常运行。

注释

属性处理程序始终与特定文件类型相关联;因此,如果文件格式包含需要自定义属性处理程序的属性,则应始终为每个文件格式注册唯一的文件扩展名。

 

初始化属性处理程序

在系统使用属性之前,通过调用 IInitializeWithStream 的实现来初始化该属性。 属性处理程序应通过让系统为其分配流而不是将该分配留给处理程序实现来初始化。 这种初始化方法可确保以下事项:

  • 属性处理程序可以在受限的进程(重要的安全功能)中运行,而无需直接读取或写入文件的权限,而是通过流访问其内容。
  • 可以信赖该系统能够正确处理文件锁定,这是一项重要的可靠性措施。
  • 属性系统提供自动安全保存服务,无需属性处理程序实现所需的任何额外功能。 有关流的详细信息,请参阅 “写回值 ”部分。
  • 使用 IInitializeWithStream 从文件系统详细信息中提取实现。 这样,处理程序就可以通过备用存储(例如文件传输协议(FTP)文件夹或具有 .zip 文件扩展名的压缩文件来支持初始化。

在某些情况下,无法用流进行初始化。 在这些情况下,属性处理程序可以实现两个进一步接口: IInitializeWithFileIInitializeWithItem。 如果属性处理程序未实现 IInitializeWithStream,则必须选择退出在系统索引器默认放置到的隔离进程中运行(如果有对流的更改)。 若要选择退出此功能,请设置以下注册表值。

HKEY_CLASSES_ROOT
   CLSID
      {66742402-F9B9-11D1-A202-0000F81FEDEE}
         DisableProcessIsolation = 1

但是,最好实现 IInitializeWithStream 并执行基于流的初始化。 属性处理程序因此将变得更安全、更可靠。 禁用进程隔离通常仅应用于旧版属性处理程序,新代码应尽量避免使用。

若要详细检查属性处理程序的实现,请查看以下代码示例,这是 IInitializeWithStream::Initialize 的实现。 为了初始化处理程序,需要通过指向与该文档相关联的 IStream 实例的指针来加载 XML 格式的食谱文档。 在代码示例末尾附近使用的 _spDocEle 变量在示例中前面定义为 MSXML2::IXMLDOMElementPtr。

注释

以下和所有后续代码示例均取自 Windows 软件开发工具包(SDK)中包含的食谱处理程序示例。 .

 

HRESULT CRecipePropertyStore::Initialize(IStream *pStream, DWORD grfMode)
{
    HRESULT hr = E_FAIL;
    
    try
    {
        if (!_spStream)
        {
            hr = _spDomDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60));
            
            if (SUCCEEDED(hr))
            {
                if (VARIANT_TRUE == _spDomDoc->load(static_cast<IUnknown *>(pStream)))
                {
                    _spDocEle = _spDomDoc->documentElement;
                }

Â

加载文档本身后,通过调用受保护的 _LoadProperties 方法加载要显示在 Windows 资源管理器中的属性,如以下代码示例所示。 下一部分将详细介绍此过程。

                if (_spDocEle)
                {
                    hr = _LoadProperties();
    
                    if (SUCCEEDED(hr))
                    {
                        _spStream = pStream;
                    }
                }
                else
                {
                    hr = E_FAIL;  // parse error
                }
            }
        }
        else
        {
            hr = E_UNEXPECTED;
        }
    }
    
    catch (_com_error &e)
    {
        hr = e.Error();
    }
    
    return hr;
}

如果流是只读的,但 grfMode 参数包含STGM_READWRITE标志,则初始化应失败并返回STG_E_ACCESSDENIED。 如果没有此检查,Windows 资源管理器会将属性值显示为可修改,尽管属性实际上不能被修改,这导致用户最终体验混乱。

属性处理程序在其生存期内仅初始化一次。 如果请求第二次初始化,则处理程序应返回 HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED)

In-Memory 属性存储

在查看 _LoadProperties 的实现之前,您应该了解示例中用来将 XML 文档中的属性通过属性系统的 PKEY 值映射到现有属性的 PropertyMap 数组。

不应将 XML 文件中的每个元素和属性公开为属性。 而是仅选择那些您认为在组织文档(在本例中为食谱)时对最终用户有用的内容。 在开发属性处理程序时,需要牢记这一重要概念:真正适用于组织方案的信息与属于文件详细信息的信息之间的差异,可通过打开文件本身来查看。 属性不应是 XML 文件的完整重复。

PropertyMap c_rgPropertyMap[] =
{
{ L"Recipe/Title", PKEY_Title, 
                   VT_LPWSTR, 
                   NULL, 
                   PKEY_Null },
{ L"Recipe/Comments", PKEY_Comment, 
                      VT_LPWSTR, 
                      NULL, 
                      PKEY_Null },
{ L"Recipe/Background", PKEY_Author, 
                        VT_VECTOR | VT_LPWSTR, 
                        L"Author", 
                        PKEY_Null },
{ L"Recipe/RecipeKeywords", PKEY_Keywords, 
                            VT_VECTOR | VT_LPWSTR, 
                            L"Keyword", 
                            PKEY_KeywordCount },
};

下面是 IInitializeWithStream::Initialize 调用的 _LoadProperties 方法的完整实现。

HRESULT CRecipePropertyStore::_LoadProperties()
{
    HRESULT hr = E_FAIL;    
    
    if (_spCache)
    {
        hr = <mark type="const">S_OK</mark>;
    }
    else
    {
        // Create the in-memory property store.
        hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&_spCache));
    
        if (SUCCEEDED(hr))
        {
            // Cycle through each mapped property.
            for (UINT i = 0; i < ARRAYSIZE(c_rgPropertyMap); ++i)
            {
                _LoadProperty(c_rgPropertyMap[i]);
            }
    
            _LoadExtendedProperties();
            _LoadSearchContent();
        }
    }
    return hr;
}

_LoadProperties方法调用 Shell 帮助程序函数 PSCreateMemoryPropertyStore,为已处理的属性创建内存中属性存储(缓存)。 通过使用缓存,会为你跟踪更改。 这样,就无需跟踪缓存中是否已更改属性值,但尚未保存到持久存储。 它还使你免于不必要地保留尚未更改的属性值。

_LoadProperties方法还会针对每个映射属性调用_LoadProperty,其实现如下代码所示。 _LoadProperty 获取 XML 流中 PropertyMap 元素中指定的属性的值,并通过调用 IPropertyStoreCache::SetValueAndState 将其分配给内存中缓存。 调用 IPropertyStoreCache::SetValueAndState 时PSC_NORMAL标志指示属性值自进入缓存以来尚未更改。

HRESULT CRecipePropertyStore::_LoadProperty(PropertyMap &map)
{
    HRESULT hr = S_FALSE;
    
    MSXML2::IXMLDOMNodePtr spXmlNode(_spDomDoc->selectSingleNode(map.pszXPath));
    if (spXmlNode)
    {
        PROPVARIANT propvar = { 0 };
        propvar.vt = map.vt;
        
        if (map.vt == (VT_VECTOR | VT_LPWSTR))
        {
            hr = _LoadVectorProperty(spXmlNode, &propvar, map);
        }
        else
        {
            // If there is no value, set to VT_EMPTY to indicate
            // that it is not there. Do not return failure.
            if (spXmlNode->text.length() == 0)
            {
                propvar.vt = VT_EMPTY;
                hr = <mark type="const">S_OK</mark>;
            }
            else
            {
                // SimplePropVariantFromString is a helper function.
                // particular to the sample. It is found in Util.cpp.
                hr = SimplePropVariantFromString(spXmlNode->text, &propvar);
            }
        }
    
        if (S_OK == hr)
        {
            hr = _spCache->SetValueAndState(map.key, &propvar, PSC_NORMAL);
            PropVariantClear(&propvar);
        }
    }
    return hr;
}

处理 PROPVARIANT-Based 值

_LoadProperty的实现中,以 PROPVARIANT 的形式提供属性值。 提供软件开发工具包(SDK)中的一组 API,用于从 PWSTRint 等基元类型转换为 PROPVARIANT 类型或从 PROPVARIANT 类型转换。 这些 API 位于 Propvarutil.h 中。

例如,若要将 PROPVARIANT 转换为字符串,可以使用 PropVariantToString ,如下所示。

PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);

若要从字符串初始化 PROPVARIANT,可以使用 InitPropVariantFromString

InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);

如示例中包含的任何食谱文件中所示,每个文件中可以有多个关键字。 为此,属性系统支持多值字符串,这些字符串表示为字符串向量(例如“VT_VECTOR | VT_LPWSTR”)。 示例中 的 _LoadVectorProperty 方法使用基于矢量的值。

HRESULT CRecipePropertyStore::_LoadVectorProperty
                                (MSXML2::IXMLDOMNode *pNodeParent,
                                 PROPVARIANT *ppropvar,
                                 struct PropertyMap &map)
{
    HRESULT hr = S_FALSE;
    MSXML2::IXMLDOMNodeListPtr spList = pNodeParent->selectNodes(map.pszSubNodeName);
    
    if (spList)
    {
        UINT cElems = spList->length;
        ppropvar->calpwstr.cElems = cElems;
        ppropvar->calpwstr.pElems = (PWSTR*)CoTaskMemAlloc(sizeof(PWSTR)*cElems);
    
        if (ppropvar->calpwstr.pElems)
        {
            for (UINT i = 0; (SUCCEEDED(hr) && i < cElems); ++i)
            {
                hr = SHStrDup(spList->item[i]->text, 
                              &(ppropvar->calpwstr.pElems[i]));
            }
    
            if (SUCCEEDED(hr))
            {
                if (!IsEqualPropertyKey(map.keyCount, PKEY_Null))
                {
                    PROPVARIANT propvarCount = { VT_UI4 };
                    propvarCount.uintVal = cElems;
                    
                    _spCache->SetValueAndState(map.keyCount,
                                               &propvarCount, 
                                               PSC_NORMAL);
                }
            }
            else
            {
                PropVariantClear(ppropvar);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    
    return hr;
}

如果文件中不存在值,请不要返回错误。 而是将值设置为VT_EMPTY并返回 S_OK。 VT_EMPTY指示属性值不存在。

支持开放元数据

此示例使用基于 XML 的文件格式。 例如,可以扩展其架构以支持开发期间未想到的属性。 此系统称为开放元数据。 此示例通过在名为 ExtendedPropertiesRecipe 元素下创建节点来扩展属性系统,如下面的代码示例所示。

<ExtendedProperties>
    <Property 
        Name="{65A98875-3C80-40AB-ABBC-EFDAF77DBEE2}, 100"
        EncodedValue="HJKHJDHKJHK"/>
</ExtendedProperties>

若要在初始化期间加载持久化扩展属性,请实现 _LoadExtendedProperties 方法,如以下代码示例所示。

HRESULT CRecipePropertyStore::_LoadExtendedProperties()
{
    HRESULT hr = S_FALSE;
    MSXML2::IXMLDOMNodeListPtr spList = 
                  _spDomDoc->selectNodes(L"Recipe/ExtendedProperties/Property");
    
    if (spList)
    {
        UINT cElems = spList->length;
        
        for (UINT i = 0; i < cElems; ++i)
        {
            MSXML2::IXMLDOMElementPtr spElement;
            
            if (SUCCEEDED(spList->item[i]->QueryInterface(IID_PPV_ARGS(&spElement))))
            {
                PROPERTYKEY key;
                _bstr_t bstrPropName = spElement->getAttribute(L"Name").bstrVal;
    
                if (!!bstrPropName &&
                    (SUCCEEDED(PropertyKeyFromString(bstrPropName, &key))))
                {
                    PROPVARIANT propvar = { 0 };
                    _bstr_t bstrEncodedValue = 
                               spElement->getAttribute(L"EncodedValue").bstrVal;
                   
                    if (!!bstrEncodedValue)
                    {
                        // DeserializePropVariantFromString is a helper function
                        // particular to the sample. It is found in Util.cpp.
                        hr = DeserializePropVariantFromString(bstrEncodedValue, 
                                                              &propvar);
                    }

Propsys.h 中声明的序列化 API 用于将 PROPVARIANT 类型序列化和反序列化为数据 blob,然后使用 Base64 编码将这些 blob 序列化为可在 XML 中保存的字符串。 这些字符串存储在 ExtendedProperties 元素的 EncodedValue 属性中。 在示例的 Util.cpp 文件中实现的以下实用工具方法执行序列化。 它首先调用 StgSerializePropVariant 函数来执行二进制序列化,如以下代码示例所示。

HRESULT SerializePropVariantAsString(const PROPVARIANT *ppropvar, PWSTR *pszOut)
{
    SERIALIZEDPROPERTYVALUE *pBlob;
    ULONG cbBlob;
    HRESULT hr = StgSerializePropVariant(ppropvar, &pBlob, &cbBlob);

接下来,Wincrypt.h 中声明的 CryptBinaryToString 函数执行 Base64 转换。

    if (SUCCEEDED(hr))
    {
        hr = E_FAIL;
        DWORD cchString;
        
        if (CryptBinaryToString((BYTE *)pBlob, 
                                cbBlob,
                                CRYPT_STRING_BASE64, 
                                NULL, 
                                &cchString))
        {
            *pszOut = (PWSTR)CoTaskMemAlloc(sizeof(WCHAR) *cchString);
    
            if (*pszOut)
            {
                if (CryptBinaryToString((BYTE *)pBlob, 
                                         cbBlob,
                                         CRYPT_STRING_BASE64,
                                         *pszOut, 
                                         &cchString))
                {
                    hr = <mark type="const">S_OK</mark>;
                }
                else
                {
                    CoTaskMemFree(*pszOut);
                }
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }
        }
    }
    
    return <mark type="const">S_OK</mark>;}

DeserializePropVariantFromString 函数也在 Util.cpp 中出现,用于从 XML 文件中反序列化值。

有关对打开元数据的支持的信息,请参阅文件类型中的“支持打开元数据 的文件类型”。

Full-Text 内容

属性处理程序还可以促进对文件内容的全文搜索,并且如果文件格式不过于复杂,则它们是提供该功能的一种简单方法。 通过实现 IFilter 接口,可以使用一种更强大的替代方法来提供文件全文。

下表总结了使用 IFilterIPropertyStore 的每个方法的优点。

能力 IFilter IPropertyStore
允许写回文件? 是的
提供丰富的内容和属性组合吗? 是的 是的
多种语言? 是的
MIME/Embedded? 是的
文本边界? 句子、段落、章节 没有
实施是否支持 SPS/SQL Server? 是的
执行 复杂 简单

 

在食谱处理程序示例中,食谱文件格式没有任何复杂的要求,因此只有 IPropertyStore 已实现全文支持。 为以下数组中命名的 XML 节点实现全文搜索。

const PWSTR c_rgszContentXPath[] = {
    L"Recipe/Ingredients/Item",
    L"Recipe/Directions/Step",
    L"Recipe/RecipeInfo/Yield",
    L"Recipe/RecipeKeywords/Keyword",
};

属性系统包含 System.Search.Contents 为索引器提供全文内容而创建的 (PKEY_Search_Contents) 属性。 此属性的值永远不会直接显示在 UI 中;上面数组中命名的所有 XML 节点的文本都串联成一个字符串。 然后,通过调用 IPropertyStoreCache::SetValueAndState ,将该字符串作为脚本文件的全文内容提供给索引器,如以下代码示例所示。

HRESULT CRecipePropertyStore::_LoadSearchContent()
{
    HRESULT hr = S_FALSE;
    _bstr_t bstrContent;
    
    for (UINT i = 0; i < ARRAYSIZE(c_rgszContentXPath); ++i)
    {
        MSXML2::IXMLDOMNodeListPtr spList = 
                                  _spDomDoc->selectNodes(c_rgszContentXPath[i]);
    
        if (spList)
        {
            UINT cElems = spList->length;
            
            for (UINT elt = 0; elt < cElems; ++elt)
            {
                bstrContent += L" ";
                bstrContent += spList->item[elt]->text;
            }
        }
    }
    
    if (bstrContent.length() > 0)
    {
        PROPVARIANT propvar = { VT_LPWSTR };
        hr = SHStrDup(bstrContent, &(propvar.pwszVal));
    
        if (SUCCEEDED(hr))
        {
            hr = _spCache->SetValueAndState(PKEY_Search_Contents, 
                                            &propvar, 
                                            PSC_NORMAL);
            PropVariantClear(&propvar);
        }
    }
    
    return hr;}

为属性提供值

用于读取值时,通常出于以下原因之一调用属性处理程序:

  • 枚举所有属性值。
  • 获取特定属性的值。

对于枚举,要求属性处理程序在编制索引期间枚举其属性,或者当属性对话框要求在“其他”组中显示属性时。 索引作为后台操作不断地进行。 每当文件发生更改时,将通知索引器,并通过请求属性处理程序枚举其属性来重新为文件编制索引。 因此,必须有效地实现属性处理程序,并尽快返回属性值。 枚举具有值的所有属性,就像对任何集合一样,但不枚举涉及内存密集型计算或网络请求的属性,这些属性可能会使它们检索速度缓慢。

编写属性处理程序时,通常需要考虑以下两组属性。

  • 主要属性:文件类型原生支持的属性。 例如,可交换图像文件(EXIF)元数据的照片属性处理程序原生支持 System.Photo.FNumber
  • 扩展属性:文件类型支持的、作为元数据的一部分的属性。

由于示例使用内存中缓存,因此实现 IPropertyStore 方法只是委托给该缓存的问题,如以下代码示例所示。

IFACEMETHODIMP GetCount(__out DWORD *pcProps)
{ return _spCache->GetCount(pcProps); }

IFACEMETHODIMP GetAt(DWORD iProp, __out PROPERTYKEY *pkey)
{ return _spCache->GetAt(iProp, pkey); }

IFACEMETHODIMP GetValue(REFPROPERTYKEY key, __out PROPVARIANT *pPropVar)
{ return _spCache->GetValue(key, pPropVar); }

如果选择不委托内存中缓存,则必须实现方法以提供> 以下预期行为:

回写值

当属性处理程序使用 IPropertyStore::SetValue 写入属性的值时,在调用 IPropertyStore::Commit 之前,它不会将该值写入文件。 内存中缓存可用于实现此方案。 在示例代码中, IPropertyStore::SetValue 实现只需在内存中缓存中设置新值,并将该属性的状态设置为PSC_DIRTY。

HRESULT CRecipePropertyStore::SetValue(REFPROPERTYKEY key, const PROPVARIANT *pPropVar)
    {
    
    HRESULT hr = E_FAIL;
    
    if (IsEqualPropertyKey(key, PKEY_Search_Contents))
    {
        // This property is read-only
        hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);  
    }
    else
    {
        hr = _spCache->SetValueAndState(key, pPropVar, PSC_DIRTY);
    }
    
    return hr;
}

在任何 IPropertyStore 实现中, IPropertyStore::SetValue 预期会出现以下行为:

  • 如果该属性已存在,则设置该属性的值。
  • 如果该属性不存在,则会添加新属性及其值集。
  • 如果属性值不能保持与给定的准确性相同(例如,由于文件格式的大小限制而截断),则会尽可能设置该值并返回INPLACE_S_TRUNCATED。
  • 如果属性处理程序不支持该属性, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) 则返回。
  • 如果无法设置属性值的另一个原因,例如文件被锁定或缺少通过访问控制列表(ACL)编辑的权限,则返回STG_E_ACCESSDENIED。

使用流作为示例的主要优点是可靠性。 属性处理程序必须始终认为,在发生灾难性故障时,它们不能使文件处于不一致的状态。 显然应该避免损坏用户的文件,执行此作的最佳方法是使用“写入复制”机制。 如果您的属性处理程序使用流来访问文件,您会自动获得此行为。系统将任何更改写入流,仅在提交操作期间将文件替换为新的副本。

要覆盖此行为并手动控制文件保存过程,可以在处理程序的注册表项中设置 ManualSafeSave 值,以确保绕过安全保存行为,如下所示。

HKEY_CLASSES_ROOT
   CLSID
      {66742402-F9B9-11D1-A202-0000F81FEDEE}
         ManualSafeSave = 1

当处理程序指定 ManualSafeSave 值时,初始化它的流不是事务处理流(STGM_TRANSACTED)。 处理程序本身必须实现安全保存函数,以确保在保存作中断时文件未损坏。 如果处理程序实现直接就地写入,它将会写入给定的流。 如果处理程序不支持该功能,则必须检索一个用于使用 IDestinationStreamFactory::GetDestinationStream 写入更新文件副本的流。 处理程序编写完成后,它应在原始流上调用 IPropertyStore::Commit 以完成该作,并将原始流的内容替换为文件的新副本。

如果您没有使用流初始化处理程序,则 ManualSafeSave 是默认选项。 如果没有原始流来接收临时流的内容,则必须使用 ReplaceFile 执行源文件的原子替换。

使用会生成大于 1 MB 文件的大型文件格式应支持对属性的就地写入,否则,性能表现将不符合属性系统客户端的预期。 在此方案中,写入属性所需的时间不应受到文件大小的影响。

对于非常大的文件(例如 1 GB 或更多视频文件)需要不同的解决方案。 如果文件中没有足够的空间来执行就地写入,如果为就地属性写入保留的空间量已用尽,处理程序可能会失败属性更新。 此故障的出现是为了避免性能下降产生的 2 GB 的 IO(1 用于读取,1 用于写入)。 由于这种潜在的故障,这些文件格式应保留足够的空间进行就地属性写入。

如果文件的标头中有足够的空间来写入元数据,并且该元数据的写入不会导致文件增长或收缩,则就地写入可能很安全。 建议以 64 KB 作为起点。 原地写入等效于请求 ManualSafeSave 的处理程序,并在 IPropertyStore::Commit 的实现中调用 IStream::Commit,并且性能远胜于写时复制。 如果文件大小因属性值变化而发生变化,则不应尝试直接覆盖写入,因为在异常终止事件中可能对文件造成损坏。

注释

出于性能原因,我们建议将 ManualSafeSave 选项与处理 100 KB 或更大文件的属性处理程序一起使用。

 

如以下 IPropertyStore::Commit 方法的示例实现所示,注册了 ManualSafeSave 的处理程序以说明手动安全保存选项。 _SaveCacheToDom方法将存储在内存中缓存中的属性值写入 XMLdocument 对象。

HRESULT CRecipePropertyStore::Commit()
{
    HRESULT hr = E_UNEXPECTED;
    if (_pCache)
    {
        // Check grfMode to ensure writes are allowed.
        hr = STG_E_ACCESSDENIED;
        if (_grfMode & STGM_READWRITE)
        {
            // Save the internal value cache to XML DOM object.
            hr = _SaveCacheToDom();
            if (SUCCEEDED(hr))
            {
                // Reset the output stream.
                LARGE_INTEGER liZero = {};
                hr = _pStream->Seek(liZero, STREAM_SEEK_SET, NULL);

接下来,询问 pecified 是否支持 IDestinationStreamFactory

                        if (SUCCEEDED(hr))
                        {
                            // Write the XML out to the temporary stream and commit it.
                            VARIANT varStream = {};
                            varStream.vt = VT_UNKNOWN;
                            varStream.punkVal = pStreamCommit;
                            hr = _pDomDoc->save(varStream);
                            if (SUCCEEDED(hr))
                            {
                                hr = pStreamCommit->Commit(STGC_DEFAULT);_

接下来,提交原始流,以安全的方式将数据写回到原始文件。

                        if (SUCCEEDED(hr))
                                {
                                    // Commit the real output stream.
                                    _pStream->Commit(STGC_DEFAULT);
                                }
                            }

                            pStreamCommit->Release();
                        }

                        pSafeCommit->Release();
                    }
                }
            }
        }
    }

然后检查 _SaveCacheToDom 实现。

// Saves the values in the internal cache back to the internal DOM object.
HRESULT CRecipePropertyStore::_SaveCacheToDom()
{
    // Iterate over each property in the internal value cache.
    DWORD cProps;  

接下来,获取内存中缓存中存储的属性数。

HRESULT hr = _pCache->GetCount(&cProps);          
            

现在遍历属性,以确定自属性加载到内存后其值是否已更改。

    for (UINT i = 0; SUCCEEDED(hr) && (i < cProps); ++i)
    {
        PROPERTYKEY key;
        hr = _pCache->GetAt(i, &key); 

IPropertyStoreCache::GetState 方法获取缓存中属性的状态。 在 IPropertyStore::SetValue 实现中设置的 PSC_DIRTY 标志将属性标记为已更改。

        if (SUCCEEDED(hr))
        {
            // check the cache state; only save dirty properties
            PSC_STATE psc;
            hr = _pCache->GetState(key, &psc);
            if (SUCCEEDED(hr) && psc == PSC_DIRTY)
            {
                // get the cached value
                PROPVARIANT propvar = {};
                hr = _pCache->GetValue(key, &propvar); 

将属性映射到 eg_rgPropertyMap 数组中指定的 XML 节点。

if (SUCCEEDED(hr))
                {
                    // save as a native property if the key is in the property map
                    BOOL fIsNativeProperty = FALSE;
                    for (UINT i = 0; i < ARRAYSIZE(g_rgPROPERTYMAP); ++i)
                    {
                        if (IsEqualPropertyKey(key, *g_rgPROPERTYMAP[i].pkey))
                        {
                            fIsNativeProperty = TRUE;
                            hr = _SaveProperty(propvar, g_rgPROPERTYMAP[i]);
                            break;
                        }     

如果属性不在地图中,则它是由 Windows 资源管理器设置的新属性。 因为支持开放的元数据,请在 XML 的 ExtendedProperties 节中保存新属性。

                    
                    // Otherwise, save as an extended property.
                    if (!fIsNativeProperty)
                    {
                        hr = _SaveExtendedProperty(key, propvar);
                    }

                    PropVariantClear(&propvar);
                }
            }
        }
    }

    return hr;    

实现 IPropertyStoreCapabilities

IPropertyStoreCapabilities 告知 Shell UI 是否可以在 Shell UI 中编辑特定属性。 请务必注意,这仅指的是在 UI 中对属性进行编辑的能力,与能否成功调用该属性的 IPropertyStore::SetValue 无关。 从 IPropertyStoreCapabilities::IsPropertyWritable 引发返回值S_FALSE的属性仍可通过应用程序进行设置。

interface IPropertyStoreCapabilities : IUnknown
{
    HRESULT IsPropertyWritable([in] REFPROPERTYKEY key);
}

IsPropertyWritable 返回 S_OK ,指示应允许最终用户直接编辑属性;S_FALSE指示不应。 S_FALSE可能意味着应用程序负责编写属性,而不是用户。 Shell 根据对此方法的调用结果,禁用编辑控件。 假定不实现 IPropertyStoreCapabilities 的处理程序通过支持写入任何属性来支持开放元数据。

如果要生成只处理只读属性的处理程序,则应实现 Initialize 方法(IInitializeWithStream、IInitializeWithItemIInitializeWithFile),以便在使用 STGM_READWRITE 标志调用时返回STG_E_ACCESSDENIED。

某些属性的 isInnate 属性设置为 true。 内在属性具有以下特征:

  • 该属性通常以某种方式计算。 例如, System.Image.BitDepth 从图像本身计算。
  • 如果不更改文件,更改属性将没有意义。 例如,更改 System.Image.Dimensions 不会重设图像的大小,因此允许用户更改它没有意义。
  • 在某些情况下,系统会自动提供这些属性。 示例包括由文件系统提供的System.DateModified,以及基于用户共享对象的System.SharedWith

由于这些特征,标记为 IsInnate 的属性仅作为只读属性提供给 Shell UI 中的用户。 如果属性标记为 IsInnate,则属性系统不会将该属性存储在属性处理程序中。 因此,属性处理程序不需要特殊代码来考虑其实现中的这些属性。 如果未为特定属性显式声明 IsInnate 属性的值,则默认值为 false

登记和分配属性处理器

实现属性处理程序后,必须注册它及其与处理程序关联的文件扩展名。 有关详细信息,请参阅 “注册和发布属性处理程序”

了解属性处理程序

使用种类名称

使用属性列表

注册和分配属性处理程序

属性处理程序最佳做法和常见问题解答