初始化属性处理程序

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

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

属性处理程序

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

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

开始之前

属性处理程序是 COM 对象,用于为特定文件格式创建 IPropertyStore 抽象。 它们读取 (分析) 并以符合其规范的方式写入此文件格式。 某些属性处理程序根据抽象访问特定文件格式的 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属性Microsoft Store

在查看 _LoadProperties的实现之前,应了解示例中用于通过属性系统中的属性映射到属性系统中的现有属性的 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 的文件格式。 其架构可以扩展,以支持在开发met 期间未想到的属性,例如。 此系统称为开放元数据。 此示例通过在名为 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>;}

在 Util.cpp 中找到 的 DeserializePropVariantFromString 函数反序列化 XML 文件中的值。

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

Full-Text内容

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

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

功能 IFilter IPropertyStore
允许写回文件?
提供内容和属性的组合?
多 语种?
MIME/Embedded?
文本边界? 句子、段落、章节
SPS/SQL Server支持的实现?
实现 Complex 简单

 

在食谱处理程序示例中,食谱文件格式没有任何复杂的要求,因此只有 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;}

为属性提供值

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

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

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

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

  • 主要属性:文件类型支持本机的属性。 例如,本机支持 System.Photo.FNumberExchangeable 图像文件 (EXIF) 元数据的照片属性处理程序。
  • 扩展属性:文件类型支持作为打开元数据的一部分的属性。

由于示例使用内存中缓存,因此实现 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 作为起点。 就地编写等效于在 IPropertyStore::Commit 实现中请求 ManualSafeSave 和调用 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 temprorary 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 Explorer 设置的新属性。 由于支持打开的元数据,因此请在 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 方法 (IInitializeWithStreamIInitializeWithItemIInitializeWithFile) ,以便在使用 STGM_READWRITE 标志调用时返回STG_E_ACCESSDENIED。

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

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

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

注册和分发属性处理程序

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

了解属性处理程序

使用种类名称

使用属性列表

注册和分发属性处理程序

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