Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом разделе объясняется, как создавать и регистрировать обработчики свойств для работы с системой свойств Windows.
Этот раздел организован следующим образом:
- обработчиков свойств
- Перед началом
- Инициализация обработчиков свойств
- In-Memory Property Store
- Управление значениями PROPVARIANT-Based
- Поддержка открытых метаданных
- содержимоеFull-Text
- Предоставление значений свойств
- Запись значений обратно
- Реализация IPropertyStoreCapabilities
- Регистрация и распределение обработчиков свойств
- Связанные статьи
Обработчики свойств
Обработчики свойств являются важной частью системы свойств. Они вызываются индексатором для чтения и индексирования значений свойств, а также вызываются встроенным обозревателем Windows для чтения и записи значений свойств непосредственно в файлах. Эти обработчики необходимо тщательно записывать и тестировать, чтобы предотвратить снижение производительности или потерю данных в затронутых файлах. Дополнительные сведения о рекомендациях по индексатору, влияющих на реализацию обработчика свойств, см. в статье "Разработка обработчиков свойств для поиска Windows".
В этом разделе рассматривается пример формата XML-файла, описывающего рецепт с расширением имени файла .recipe. Расширение имени файла рецепта регистрируется в качестве собственного формата файла, а не на основе более универсального формата файла .xml, обработчик которого использует дополнительный поток для хранения свойств. Рекомендуется зарегистрировать уникальные расширения имен файлов для типов файлов.
Перед началом работы
Обработчики свойств — это COM-объекты, создающие абстракцию IPropertyStore для определенного формата файла. Они считывают (синтаксический анализ) и записывают этот формат файла таким образом, что соответствует его спецификации. Некоторые обработчики свойств выполняют свою работу на основе API, которые абстрагируют доступ к определенному формату файла. Прежде чем разрабатывать обработчик свойств для формата файла, необходимо понять, как формат файла хранит свойства, а также как эти свойства (имена и значения) сопоставляются с абстракцией хранилища свойств.
При планировании реализации помните, что обработчики свойств — это низкоуровневые компоненты, загруженные в контексте таких процессов, как Проводник Windows, индексатор поиска Windows и сторонние приложения, использующие модель программирования элементов оболочки. В результате обработчики свойств не могут быть реализованы в управляемом коде и должны быть реализованы в C++. Если обработчик использует любые API или службы для выполнения своей работы, необходимо убедиться, что эти службы могут правильно работать в среде, в которой загружается обработчик свойств.
Замечание
Обработчики свойств всегда связаны с определенными типами файлов; Таким образом, если формат файла содержит свойства, требующие пользовательского обработчика свойств, всегда следует зарегистрировать уникальное расширение имени файла для каждого формата файла.
Инициализация обработчиков свойств
Перед использованием свойства системой он инициализируется путем вызова реализации IInitializeWithStream. Обработчик свойств следует инициализировать, назначив системе поток, а не оставляя это назначение реализации обработчика. Этот метод инициализации гарантирует следующее:
- Обработчик свойств может работать в ограниченном процессе (важная функция безопасности), не имея прав доступа к непосредственному чтению или записи файлов, а получая доступ к их содержимому через поток.
- Системе можно доверить правильную обработку файловых блокировок, что является важной мерой надежности.
- Система свойств предоставляет службу автоматического безопасного сохранения без дополнительных функциональных возможностей, необходимых для реализации обработчика свойств. Дополнительные сведения о потоках см. в разделе "Обратная запись значений ".
- Использование IInitializeWithStream абстрагирует реализацию из сведений о файловой системе. Это позволяет обработчику поддерживать инициализацию с помощью альтернативных хранилищ, таких как папка FTP или сжатый файл с расширением имени файла .zip.
Существуют случаи, когда инициализация с потоками невозможна. В этих ситуациях есть два дополнительных интерфейса, которые обработчики свойств могут реализовать: IInitializeWithFile и IInitializeWithItem. Если обработчик свойств не реализует IInitializeWithStream, он должен отказаться от выполнения в изолированном процессе, в котором системный индексатор по умолчанию помещал его в случае изменения потока. Чтобы отказаться от этой функции, задайте следующее значение реестра.
HKEY_CLASSES_ROOT
CLSID
{66742402-F9B9-11D1-A202-0000F81FEDEE}
DisableProcessIsolation = 1
Однако гораздо лучше реализовать IInitializeWithStream и выполнить инициализацию на основе потока. В результате ваш обработчик объектов станет более безопасным и надежным. Отключение изоляции процесса обычно предназначено только для устаревших обработчиков свойств и следует категорически избегать в любом новом коде.
Чтобы подробно изучить реализацию обработчика свойств, ознакомьтесь со следующим примером кода, который является реализацией IInitializeWithStream::Initialize. Обработчик инициализируется путем загрузки XML-документа рецепта с помощью указателя на связанный экземпляр IStream этого документа. Переменная _spDocEle , используемая ближе к концу примера кода, определена ранее в примере как MSXML2::IXMLDOMElementPtr.
Замечание
Следующие и все последующие примеры кода взяты из примера обработчика рецептов, включенных в пакет SDK для Windows. .
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 , используемый в примере для сопоставления свойств в XML-документе с существующими свойствами в системе свойств с помощью их значений PKEY.
Не следует предоставлять каждый элемент и атрибут в 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 },
};
Ниже приведена полная реализация метода _LoadProperties , вызываемого IInitializeWithStream::Initialize.
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 вызывает вспомогательные функции Оболочки PSCreateMemoryPropertyStore для создания хранилища свойств в памяти (кэша) для обрабатываемых свойств. При использовании кэша изменения отслеживаются для вас. Это освобождает вас от отслеживания того, было ли изменено значение свойства в кэше, но еще не сохранено в сохраненном хранилище. Он также освобождает вас от ненужного сохранения значений свойств, которые не изменились.
Метод _LoadProperties также вызывает _LoadProperty , реализация которого показана в следующем коде) один раз для каждого сопоставленного свойства. _LoadProperty получает значение свойства, указанного в элементе PropertyMap в XML-потоке, и назначает его кэшу в памяти с помощью вызова IPropertyStoreCache::SetValueAndState. Флаг PSC_NORMAL в вызове IPropertyStoreCache::SetValueAndState указывает, что значение свойства не было изменено с момента ввода кэша.
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. Набор API в пакете средств разработки программного обеспечения (SDK) предоставляется для преобразования примитивных типов, таких как PWSTR или int, в типы 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. Его схема может быть расширена для поддержки свойств, которые не были предусмотрены во время разработки, например. Эта система называется открытыми метаданными. В этом примере система свойств расширяется путем создания узла в элементе 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);
}
API сериализации, объявленные в Propsys.h, используются для сериализации и десериализации типов PROPVARIANT в BLOB-объекты, а затем кодировка Base64 используется для сериализации этих BLOB-ов в строки, которые можно сохранить в формате XML. Эти строки хранятся в атрибуте EncodedValue элемента ExtendedProperties . Следующий метод служебной программы, реализованный в файле Util.cpp примера, выполняет сериализацию. Он начинается с вызова функции StgSerializePropVariant для выполнения двоичной сериализации, как показано в следующем примере кода.
HRESULT SerializePropVariantAsString(const PROPVARIANT *ppropvar, PWSTR *pszOut)
{
SERIALIZEDPROPERTYVALUE *pBlob;
ULONG cbBlob;
HRESULT hr = StgSerializePropVariant(ppropvar, &pBlob, &cbBlob);
Затем функция CryptBinaryToString, объявленная в Wincrypt.h, выполняет преобразование 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 .
В следующей таблице перечислены преимущества каждого подхода с помощью IFilter или IPropertyStore.
Способность | 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), созданное для предоставления полнотекстового содержимого индексатору. Значение этого свойства никогда не отображается непосредственно в пользовательском интерфейсе; Текст из всех 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 (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 отражает текущее состояние обработчика свойств. Если свойство PROPERTYKEY добавляется или удаляется из файла через IPropertyStore::SetValue, эти два метода должны отражать это изменение при следующем вызове.
- 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 МБ, должны реализовать поддержку записи свойств на месте; в противном случае поведение производительности не соответствует ожиданиям клиентов системы свойств. В этом сценарии время, необходимое для записи свойств, не должно влиять на размер файла.
Для очень больших файлов, например видеофайла размером 1 ГБ или более, требуется другое решение. Если в файле недостаточно места для записи на месте, обработчик может не выполнить обновление свойства, если объем пространства, зарезервированного для записи, был исчерпан. Эта ошибка возникает, чтобы избежать плохой производительности, возникающей из-за 2 ГБ операций ввода-вывода (1 для чтения, 1 для записи). Из-за этого потенциального сбоя эти форматы файлов должны резервировать достаточно места для записи свойств непосредственно в самом файле.
Если файл имеет достаточно места в заголовке, чтобы записывать метаданные, а запись этих метаданных не приводит к увеличению или уменьшению размера файла, возможно безопасно писать непосредственно. Рекомендуется использовать 64 КБ в качестве отправной точки. Написание на месте эквивалентно тому, что обработчик запрашивает ManualSafeSave и вызывает IStream::Commit в реализации IPropertyStore::Commit, и имеет гораздо лучшую производительность, чем копирование при записи. Если размер файла изменяется из-за изменений значения свойства, запись на месте не должна быть предпринята из-за возможности поврежденного файла в случае ненормального завершения.
Замечание
По соображениям производительности рекомендуется использовать параметр ManualSafeSave с обработчиками свойств, работающими с файлами размером 100 КБ или больше.
Как показано в следующем примере реализации 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);
Затем спросите, поддерживает ли указанный идентификатор 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 получает состояние свойства в кэше. Флаг PSC_DIRTY, который был задан в реализации IPropertyStore::SetValue , помечает свойство как измененное.
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);
Сопоставьте свойство с узлом XML, как указано в массиве eg_rgPropertyMap.
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. Так как поддерживаются открытые метаданные, сохраните новое свойство в разделе ExtendedProperties XML.
// Otherwise, save as an extended property.
if (!fIsNativeProperty)
{
hr = _SaveExtendedProperty(key, propvar);
}
PropVariantClear(&propvar);
}
}
}
}
return hr;
Реализация "IPropertyStoreCapabilities"
IPropertyStoreCapabilities сообщает пользовательскому интерфейсу оболочки, можно ли изменить определенное свойство в пользовательском интерфейсе оболочки. Важно отметить, что это связано только с возможностью редактирования свойства в пользовательском интерфейсе, а не с возможностью успешного вызова IPropertyStore::SetValue в свойстве. Свойство, которое вызывает возвращаемое значение S_FALSE из IPropertyStoreCapabilities::IsPropertyWritable , может по-прежнему быть способным задаваться через приложение.
interface IPropertyStoreCapabilities : IUnknown
{
HRESULT IsPropertyWritable([in] REFPROPERTYKEY key);
}
IsPropertyWritable возвращает S_OK , чтобы указать, что конечные пользователи должны быть разрешены напрямую изменять свойство; S_FALSE указывает, что они не должны. S_FALSE может означать, что приложения отвечают за запись свойства, а не пользователей. Оболочка отключает элементы управления редактирования в соответствии с результатами вызовов этого метода. Предполагается, что обработчик, не реализующий IPropertyStoreCapabilities , поддерживает открытые метаданные через поддержку записи любого свойства.
Если вы создаете обработчик, который обрабатывает только свойства только для чтения, необходимо реализовать метод Initialize (IInitializeWithStream, IInitializeWithItem или IInitializeWithFile), чтобы он возвращал STG_E_ACCESSDENIED при вызове с флагом STGM_READWRITE.
Некоторые свойства имеют значение true для атрибута isInnate. У встроенных свойств имеются следующие характеристики:
- Свойство обычно вычисляется каким-то образом. Например,
System.Image.BitDepth
вычисляется из самого образа. - Изменение свойства не имеет смысла, не изменяя файл. Например, изменение
System.Image.Dimensions
не изменит размер изображения, поэтому не имеет смысла разрешать пользователю изменять его. - В некоторых случаях эти свойства предоставляются системой автоматически. К примерам относятся
System.DateModified
, которая предоставляется файловой системой, иSystem.SharedWith
, которая зависит от того, с кем пользователь делится файлом.
Из-за этих характеристик свойства, помеченные как IsInnate , предоставляются пользователю в пользовательском интерфейсе оболочки только как свойства только для чтения. Если свойство помечается как IsInnate, система свойств не сохраняет это свойство в обработчике свойств. Поэтому обработчики свойств не нуждаются в специальном коде для учета этих свойств в их реализации. Если значение атрибута IsInnate не указано явно для определенного свойства, значение по умолчанию равно false.
Регистрация и развертывание обработчиков свойств
После реализации обработчика свойств, его необходимо зарегистрировать и связать расширение имени файла с обработчиком. Дополнительные сведения см. в разделе Регистрация и распространение обработчиков свойств.
Связанные темы
-
Рекомендации по обработчику свойств и часто задаваемые вопросы