初始化屬性處理常式

本主題說明如何建立和註冊屬性處理常式,以使用 Windows 屬性系統。

本主題的組織方式如下:

屬性處理常式

屬性處理常式是屬性系統的重要部分。 索引子會以進程方式叫用它們來讀取和編制屬性值,而且也會由 Windows 檔案總管內叫用,直接在檔案中讀取和寫入屬性值。 這些處理常式必須仔細撰寫並測試,以避免效能降低或受影響檔案中的資料遺失。 如需影響屬性處理常式實作之索引子特定考慮的詳細資訊,請參閱 開發 Windows 搜尋的屬性處理常式

本主題討論以 XML 為基礎的範例檔案格式,描述副檔名為 .recipe 的配方。 .recipe 副檔名會註冊為自己的相異檔案格式,而不是依賴較一般.xml檔案格式,其處理常式會使用次要資料流程來儲存屬性。 建議您為檔案類型註冊唯一的副檔名。

開始之前

屬性處理常式是 COM 物件,可針對特定檔案格式建立 IPropertyStore 抽象概念。 他們會 (剖析) ,並以符合其規格的方式寫入此檔案格式。 某些屬性處理常式會根據抽象存取特定檔案格式的 API 來執行其工作。 開發檔案格式的屬性處理常式之前,您需要瞭解檔案格式儲存屬性的方式,以及這些屬性如何 (名稱和值) 對應到屬性存放區抽象概念。

規劃實作時,請記住,屬性處理常式是低階元件,這些元件會載入 Windows 檔案總管、Windows 搜尋服務索引子,以及使用 Shell 專案程式設計模型的協力廠商應用程式。 因此,屬性處理常式無法在 Managed 程式碼中實作,而且應該以 C++ 實作。 如果您的處理常式使用任何 API 或服務來執行其工作,您必須確定這些服務可以在載入屬性處理常式 (的) 環境中正常運作。

注意

屬性處理常式一律與特定檔案類型相關聯;因此,如果您的檔案格式包含需要自訂屬性處理常式的屬性,您應該一律為每個檔案格式註冊唯一的副檔名。

 

初始化屬性處理常式

在系統使用屬性之前,它會藉由呼叫 IInitializeWithStream的實作來初始化。 屬性處理常式應該藉由讓系統將資料流程指派給資料流程,而不是將該指派保留給處理常式實作來初始化。 這個初始化方法可確保下列事項:

  • 屬性處理常式可以在受限制的進程中執行, (重要的安全性功能) ,而不需要直接讀取或寫入檔案的許可權,而是透過資料流程存取其內容。
  • 系統可以信任以正確處理檔案 oplock,這是重要的可靠性量值。
  • 屬性系統提供自動安全儲存服務,而不需要屬性處理常式實作所需的額外功能。 如需資料流程的詳細資訊,請參閱 回寫值 一節。
  • 使用 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;
                }

Â

載入檔本身之後,Windows 檔案總管中顯示的屬性會藉由呼叫受保護的 _LoadProperties 方法載入,如下列程式碼範例所示。 下一節會詳細檢查此程式。

                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實作之前,您應該先瞭解範例中使用的 PropertyMap 陣列,以透過其 PKEY 值,將 XML 檔中的屬性對應至屬性系統中的現有屬性。

您不應該將 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;}

提供屬性的值

當用來讀取值時,通常會基於下列其中一個原因叫用屬性處理常式:

  • 列舉所有屬性值。
  • 若要取得特定屬性的值。

針對列舉,系統會要求屬性處理常式在編制索引期間列舉其屬性,或當屬性對話方塊要求屬性顯示在 [其他 ] 群組時。 索引會持續以背景作業的形式進行。 每當檔案變更時,索引子就會收到通知,並要求屬性處理常式列舉其屬性,以重新編制檔案的索引。 因此,屬性處理常式必須有效率地實作,並儘快傳回屬性值。 列舉您擁有值的所有屬性,就像您對於任何集合一樣,但不會列舉牽涉到大量記憶體計算或網路要求的屬性,讓這些屬性擷取速度很慢。

撰寫屬性處理常式時,您通常需要考慮下列兩組屬性。

  • 主要屬性:檔案類型支援原生的屬性。 例如,Exchangeable Image File (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 的處理常式會假設透過支援寫入任何屬性來支援開啟中繼資料。

如果您要建置只處理唯讀屬性的處理常式,則您應該實作 Initialize 方法 (IInitializeWithStreamIInitializeWithItem 或IInitializeWithFile) ,以便在使用 STGM_READWRITE 旗標呼叫時傳回STG_E_ACCESSDENIED。

某些屬性的 isInnate 屬性設定為 true。 內建屬性具有下列特性:

  • 屬性通常是以某種方式計算。 例如, System.Image.BitDepth 是從影像本身計算而來。
  • 在變更檔案的情況下,變更屬性並無意義。 例如,變更 System.Image.Dimensions 並不會調整影像的大小,因此允許使用者變更它並不合理。
  • 在某些情況下,系統會自動提供這些屬性。 範例包括 System.DateModified 檔案系統所提供的 ,以及 System.SharedWith ,這是根據使用者與誰共用檔案。

由於這些特性,標示為 IsInnate 的屬性只會提供給 Shell UI 中的使用者做為唯讀屬性。 如果屬性標示為 IsInnate,則屬性系統不會將該屬性儲存在屬性處理常式中。 因此,屬性處理常式不需要特殊程式碼,即可在實作中考慮這些屬性。 如果未針對特定屬性明確陳述 IsInnate 屬性的值,預設值為 false

註冊和散發屬性處理常式

實作屬性處理常式時,必須註冊它及其與處理常式相關聯的副檔名。 如需詳細資訊,請參閱 註冊和散發屬性處理常式

瞭解屬性處理常式

使用種類名稱

使用屬性清單

註冊和散發屬性處理常式

屬性處理常式最佳做法和常見問題