Инициализация обработчиков свойств

В этом разделе объясняется, как создавать и регистрировать обработчики свойств для работы с системой свойств Windows.

Эта тема организована следующим образом:

Обработчики свойств

Обработчики свойств являются важной частью системы свойств. Они вызываются индексатором внутри процесса для чтения и индексирования значений свойств, а также вызываются Windows Обозреватель внутри процесса для чтения и записи значений свойств непосредственно в файлах. Эти обработчики должны быть тщательно написаны и протестированы, чтобы предотвратить снижение производительности или потерю данных в затронутых файлах. Дополнительные сведения о рекомендациях индексатора, влияющих на реализацию обработчика свойств, см. в разделе Разработка обработчиков свойств для Поиска Windows.

В этом разделе рассматривается пример формата xml-файла, который описывает рецепт с расширением имени файла .recipe. Расширение имени файла .recipe регистрируется как собственный отдельный формат файла, а не зависит от более универсального формата файла .xml, обработчик которого использует вторичный поток для хранения свойств. Рекомендуется регистрировать уникальные расширения имен файлов для типов файлов.

Перед началом

Обработчики свойств — это COM-объекты, которые создают абстракцию IPropertyStore для определенного формата файла. Они считывают (анализ) и записывают этот формат файла в соответствии со спецификацией. Некоторые обработчики свойств выполняют свою работу на основе ИНТЕРФЕЙСов API, которые абстрагируют доступ к определенному формату файлов. Перед разработкой обработчика свойств для формата файла необходимо понять, как формат файла хранит свойства и как эти свойства (имена и значения) сопоставляются с абстракцией хранилища свойств.

При планировании реализации помните, что обработчики свойств — это низкоуровневые компоненты, которые загружаются в контексте таких процессов, как Windows Обозреватель, индексатор Windows Search и сторонние приложения, использующие модель программирования элементов оболочки. В результате обработчики свойств не могут быть реализованы в управляемом коде и должны быть реализованы в 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 или из типа 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 в большие двоичные объекты данных, а затем для сериализации этих больших двоичных объектов в строки, которые можно сохранить в XML-коде, используется кодировка Base64. Эти строки хранятся в атрибуте 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? Да Нет
Реализация 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), которое было создано для предоставления индексатору полнотекстового содержимого. Значение этого свойства никогда не отображается непосредственно в пользовательском интерфейсе; Текст из всех УЗЛОВ 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;}

Предоставление значений для свойств

При использовании для чтения значений обработчики свойств обычно вызываются по одной из следующих причин:

  • Перечисление всех значений свойств.
  • Получение значения определенного свойства.

Для перечисления обработчику свойств предлагается перечислить его свойства во время индексирования или когда диалоговое окно свойств запрашивает свойства для отображения в группе Другие . Индексирование продолжается постоянно как фоновая операция. При каждом изменении файла индексатор получает уведомление и повторно индексирует файл, запрашивая у обработчика свойств перечисление его свойств. Поэтому крайне важно, чтобы обработчики свойств были эффективно реализованы и возвращали значения свойств как можно быстрее. Перечисляйте все свойства, для которых имеются значения, как и для любой коллекции, но не перечисляйте свойства, требующие большого объема памяти, или сетевые запросы, которые могут замедлить их получение.

При написании обработчика свойств обычно необходимо учитывать следующие два набора свойств.

  • Основные свойства: свойства, которые ваш тип файла поддерживает в собственном коде. Например, обработчик свойств photo для метаданных 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 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 получает состояние свойства в кэше. Флаг 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.

Для некоторых свойств атрибут isInnate имеет значение true. Врожденные свойства имеют следующие характеристики:

  • Свойство обычно вычисляется каким-то образом. Например, System.Image.BitDepth вычисляется на основе самого образа.
  • Изменение свойства не имело бы смысла без изменения файла. Например, изменение System.Image.Dimensions не приведет к изменению размера изображения, поэтому не имеет смысла разрешать пользователю изменять его.
  • В некоторых случаях эти свойства предоставляются системой автоматически. Примерами являются System.DateModified, предоставляемые файловой системой, и System.SharedWith, основанные на том, с кем пользователь предоставляет общий доступ к файлу.

В связи с этими характеристиками свойства, помеченные как IsInnate , предоставляются пользователю в пользовательском интерфейсе оболочки только как свойства только для чтения. Если свойство помечено как IsInnate, система свойств не сохраняет это свойство в обработчике свойств. Поэтому обработчикам свойств не требуется специальный код для учета этих свойств в своих реализациях. Если значение атрибута IsInnate явно не указано для определенного свойства, значение по умолчанию — false.

Регистрация и распределение обработчиков свойств

После реализации обработчика свойств он должен быть зарегистрирован, а его расширение имени файла связано с обработчиком. Дополнительные сведения см. в разделе Регистрация и распределение обработчиков свойств.

Основные сведения о обработчиках свойств

Использование имен типов

Использование списков свойств

Регистрация и распределение обработчиков свойств

Рекомендации и вопросы и ответы по обработчику свойств