初始化屬性處理常式
本主題說明如何建立和註冊屬性處理常式,以使用 Windows 屬性系統。
本主題的組織方式如下:
- 屬性處理常式
- 開始之前
- 初始化屬性處理常式
- 記憶體內部屬性存放區
- 處理PROPVARIANT-Based值
- 支援開啟中繼資料
- 全文檢索內容
- 提供屬性的值
- 回寫值
- 實作 IPropertyStoreCapabilities
- 註冊和散發屬性處理常式
- 相關主題
屬性處理常式
屬性處理常式是屬性系統的重要部分。 索引子會以進程方式叫用它們來讀取和編制屬性值,而且也會由 Windows 檔案總管內叫用,直接在檔案中讀取和寫入屬性值。 這些處理常式必須仔細撰寫並測試,以避免效能降低或受影響檔案中的資料遺失。 如需影響屬性處理常式實作之索引子特定考慮的詳細資訊,請參閱 開發 Windows 搜尋的屬性處理常式。
本主題討論以 XML 為基礎的範例檔案格式,描述副檔名為 .recipe 的配方。 .recipe 副檔名會註冊為自己的相異檔案格式,而不是依賴較一般.xml檔案格式,其處理常式會使用次要資料流程來儲存屬性。 建議您為檔案類型註冊唯一的副檔名。
開始之前
屬性處理常式是 COM 物件,可針對特定檔案格式建立 IPropertyStore 抽象概念。 他們會 (剖析) ,並以符合其規格的方式寫入此檔案格式。 某些屬性處理常式會根據抽象存取特定檔案格式的 API 來執行其工作。 開發檔案格式的屬性處理常式之前,您需要瞭解檔案格式儲存屬性的方式,以及這些屬性如何 (名稱和值) 對應到屬性存放區抽象概念。
規劃實作時,請記住,屬性處理常式是低階元件,這些元件會載入 Windows 檔案總管、Windows 搜尋服務索引子,以及使用 Shell 專案程式設計模型的協力廠商應用程式。 因此,屬性處理常式無法在 Managed 程式碼中實作,而且應該以 C++ 實作。 如果您的處理常式使用任何 API 或服務來執行其工作,您必須確定這些服務可以在載入屬性處理常式 (的) 環境中正常運作。
注意
屬性處理常式一律與特定檔案類型相關聯;因此,如果您的檔案格式包含需要自訂屬性處理常式的屬性,您應該一律為每個檔案格式註冊唯一的副檔名。
初始化屬性處理常式
在系統使用屬性之前,它會藉由呼叫 IInitializeWithStream的實作來初始化。 屬性處理常式應該藉由讓系統將資料流程指派給資料流程,而不是將該指派保留給處理常式實作來初始化。 這個初始化方法可確保下列事項:
- 屬性處理常式可以在受限制的進程中執行, (重要的安全性功能) ,而不需要直接讀取或寫入檔案的許可權,而是透過資料流程存取其內容。
- 系統可以信任以正確處理檔案 oplock,這是重要的可靠性量值。
- 屬性系統提供自動安全儲存服務,而不需要屬性處理常式實作所需的額外功能。 如需資料流程的詳細資訊,請參閱 回寫值 一節。
- 使用 IInitializeWithStream 會從檔案系統詳細資料中擷取您的實作。 這可讓處理常式支援透過替代儲存體進行初始化,例如檔案傳輸通訊協定 (FTP) 資料夾或副檔名為.zip的壓縮檔。
在某些情況下,無法使用資料流程進行初始化。 在這些情況下,屬性處理常式可以實作的兩個進一步介面: IInitializeWithFile 和 IInitializeWithItem。 如果屬性處理常式未實作 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,以從 PWSTR 或 int 等基本類型轉換成 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 期間未考慮的屬性,例如。 此系統稱為開放式中繼資料。 本範例會藉由在名為ExtendedProperties的Recipe元素下建立節點來擴充屬性系統,如下列程式碼範例所示。
<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 介面實作提供檔案的全文檢索。
下表摘要說明使用 IFilter 或 IPropertyStore之每個方法的優點。
功能 | 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::GetCount:如果沒有屬性,這個方法會傳回 S_OK。
- IPropertyStore::GetAt:如果 iProp 大於或等於 cProps,這個方法會傳回E_INVALIDARG,且 pkey 參數所指向的結構會填入零。
- IPropertyStore::GetCount 和 IPropertyStore::GetAt 會反映屬性處理常式的目前狀態。 如果透過IPropertyStore::SetValue將PROPERTYKEY新增至檔案或從檔案中移除,這兩種方法必須反映下次呼叫時變更。
- IPropertyStore::GetValue:如果系統要求這個方法提供不存在的值,則會傳回 S_OK ,並傳回回報為VT_EMPTY的值。
回寫值
當屬性處理常式使用 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 方法 (IInitializeWithStream、 IInitializeWithItem 或IInitializeWithFile) ,以便在使用 STGM_READWRITE 旗標呼叫時傳回STG_E_ACCESSDENIED。
某些屬性的 isInnate 屬性設定為 true。 內建屬性具有下列特性:
- 屬性通常是以某種方式計算。 例如,
System.Image.BitDepth
是從影像本身計算而來。 - 在變更檔案的情況下,變更屬性並無意義。 例如,變更
System.Image.Dimensions
並不會調整影像的大小,因此允許使用者變更它並不合理。 - 在某些情況下,系統會自動提供這些屬性。 範例包括
System.DateModified
檔案系統所提供的 ,以及System.SharedWith
,這是根據使用者與誰共用檔案。
由於這些特性,標示為 IsInnate 的屬性只會提供給 Shell UI 中的使用者做為唯讀屬性。 如果屬性標示為 IsInnate,則屬性系統不會將該屬性儲存在屬性處理常式中。 因此,屬性處理常式不需要特殊程式碼,即可在實作中考慮這些屬性。 如果未針對特定屬性明確陳述 IsInnate 屬性的值,預設值為 false。
註冊和散發屬性處理常式
實作屬性處理常式時,必須註冊它及其與處理常式相關聯的副檔名。 如需詳細資訊,請參閱 註冊和散發屬性處理常式。
相關主題