初始化属性处理程序

本主题说明如何创建和注册属性处理程序以使用 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 属性存储

在查看 _LoadProperties 的实现之前,应了解示例中用于通过属性的 PKEY 值将 XML 文档中的属性映射到属性系统中的现有属性的 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);

如示例中包含的任何食谱文件中所示,每个文件中可以有多个关键字 (keyword) 。 为此,属性系统支持表示为字符串向量的多值字符串, (例如“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 的文件格式。 例如,可以扩展其架构以支持在开发过程中未想到的属性。 此系统称为开放元数据。 此示例通过在 Recipe 元素下创建名为 ExtendedProperties 的节点来扩展属性系统,如以下代码示例所示。

<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是否支持实现?
实现 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;}

为属性提供值

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

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

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

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

  • 主要属性:文件类型本机支持的属性。 例如,可交换图像文件的照片属性处理程序 (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 作为起点。 就地写入等效于在 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 资源管理器设置的新属性。 由于支持开放元数据,因此请将新属性保存在 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 的处理程序通过支持写入任何属性来支持开放元数据。

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

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

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

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

注册和分发属性处理程序

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

了解属性处理程序

使用种类名称

使用属性列表

注册和分发属性处理程序

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