Share via


Initialisation des gestionnaires de propriétés

Cette rubrique explique comment créer et inscrire des gestionnaires de propriétés pour fonctionner avec le système de propriétés Windows.

Cette rubrique est organisée comme suit :

Gestionnaires de propriétés

Les gestionnaires de propriétés sont une partie cruciale du système de propriétés. Ils sont appelés en cours par l’indexeur pour lire et indexer les valeurs de propriété, et sont également appelés par Windows Explorer en cours pour lire et écrire des valeurs de propriété directement dans les fichiers. Ces gestionnaires doivent être écrits et testés avec soin pour éviter une dégradation des performances ou la perte de données dans les fichiers affectés. Pour plus d’informations sur les considérations spécifiques à l’indexeur qui affectent l’implémentation du gestionnaire de propriétés, consultez Développement de gestionnaires de propriétés pour Windows Search.

Cette rubrique décrit un exemple de format de fichier XML qui décrit une recette avec une extension de nom de fichier .recette. L’extension de nom de fichier .recette est inscrite comme son propre format de fichier distinct au lieu de s’appuyer sur le format de fichier plus générique .xml, dont le gestionnaire utilise un flux secondaire pour stocker les propriétés. Nous vous recommandons d’inscrire des extensions de nom de fichier uniques pour vos types de fichiers.

Avant de commencer

Les gestionnaires de propriétés sont des objets COM qui créent l’abstraction IPropertyStore pour un format de fichier spécifique. Ils lisent (analyse) et écrivent ce format de fichier d’une manière conforme à sa spécification. Certains gestionnaires de propriétés effectuent leur travail en fonction des API qui abstraitnt l’accès à un format de fichier spécifique. Avant de développer un gestionnaire de propriétés pour votre format de fichier, vous devez comprendre comment votre format de fichier stocke les propriétés et comment ces propriétés (noms et valeurs) sont mappées à l’abstraction du magasin de propriétés.

Lors de la planification de votre implémentation, n’oubliez pas que les gestionnaires de propriétés sont des composants de bas niveau chargés dans le contexte de processus tels que Windows Explorer, l’indexeur Recherche Windows et les applications tierces qui utilisent le modèle de programmation d’éléments Shell. Par conséquent, les gestionnaires de propriétés ne peuvent pas être implémentés dans le code managé et doivent être implémentés en C++. Si votre gestionnaire utilise des API ou des services pour effectuer son travail, vous devez vous assurer que ces services peuvent fonctionner correctement dans les environnements dans lesquels votre gestionnaire de propriétés est chargé.

Notes

Les gestionnaires de propriétés sont toujours associés à des types de fichiers spécifiques ; Par conséquent, si votre format de fichier contient des propriétés qui nécessitent un gestionnaire de propriétés personnalisé, vous devez toujours inscrire une extension de nom de fichier unique pour chaque format de fichier.

 

Initialisation des gestionnaires de propriétés

Avant qu’une propriété ne soit utilisée par le système, elle est initialisée en appelant une implémentation de IInitializeWithStream. Le gestionnaire de propriétés doit être initialisé en faisant en sorte que le système lui attribue un flux plutôt que de laisser cette affectation à l’implémentation du gestionnaire. Cette méthode d’initialisation garantit les éléments suivants :

  • Le gestionnaire de propriétés peut s’exécuter dans un processus restreint (une fonctionnalité de sécurité importante) sans disposer de droits d’accès pour lire ou écrire directement des fichiers, au lieu d’accéder à leur contenu via le flux.
  • Le système peut être approuvé pour gérer correctement les oplocks de fichiers, ce qui est une mesure de fiabilité importante.
  • Le système de propriétés fournit un service d’enregistrement sécurisé automatique sans aucune fonctionnalité supplémentaire requise à partir de l’implémentation du gestionnaire de propriétés. Pour plus d’informations sur les flux, consultez la section Écriture de valeurs en arrière .
  • L’utilisation de IInitializeWithStream extrait votre implémentation des détails du système de fichiers. Cela permet au gestionnaire de prendre en charge l’initialisation via d’autres stockages, tels qu’un dossier FTP (File Transfer Protocol) ou un fichier compressé avec une extension de nom de fichier .zip.

Il existe des cas où l’initialisation avec des flux n’est pas possible. Dans ces situations, les gestionnaires de propriétés peuvent implémenter deux autres interfaces : IInitializeWithFile et IInitializeWithItem. Si un gestionnaire de propriétés n’implémente pas IInitializeWithStream, il doit refuser de s’exécuter dans le processus isolé dans lequel l’indexeur système le place par défaut en cas de modification du flux. Pour désactiver cette fonctionnalité, définissez la valeur de Registre suivante.

HKEY_CLASSES_ROOT
   CLSID
      {66742402-F9B9-11D1-A202-0000F81FEDEE}
         DisableProcessIsolation = 1

Toutefois, il est préférable d’implémenter IInitializeWithStream et d’effectuer une initialisation basée sur le flux. Par conséquent, votre gestionnaire de propriétés sera plus sûr et plus fiable. La désactivation de l’isolation des processus est généralement destinée uniquement aux gestionnaires de propriétés hérités et doit être fortement évitée par tout nouveau code.

Pour examiner en détail l’implémentation d’un gestionnaire de propriétés, examinez l’exemple de code suivant, qui est une implémentation de IInitializeWithStream::Initialize. Le gestionnaire est initialisé en chargeant un document de recette XML via un pointeur vers le instance IStream associé de ce document. La variable _spDocEle utilisée près de la fin de l’exemple de code est définie précédemment dans l’exemple en tant que MSXML2::IXMLDOMElementPtr.

Notes

Les exemples de code suivants et tous les exemples de code suivants sont extraits de l’exemple de gestionnaire de recettes inclus dans le Kit de développement logiciel (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;
                }

Â

Une fois le document lui-même chargé, les propriétés à afficher dans Windows Explorer sont chargées en appelant la méthode _LoadProperties protégée, comme illustré dans l’exemple de code suivant. Ce processus est examiné en détail dans la section suivante.

                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;
}

Si le flux est en lecture seule, mais que le paramètre grfMode contient l’indicateur STGM_READWRITE, l’initialisation doit échouer et retourner STG_E_ACCESSDENIED. Sans cette case activée, Windows Explorer affiche les valeurs de propriété comme pouvant être accessibles en écriture, même si elles ne le sont pas, ce qui entraîne une expérience déroutante pour l’utilisateur final.

Le gestionnaire de propriétés n’est initialisé qu’une seule fois dans sa durée de vie. Si une deuxième initialisation est demandée, le gestionnaire doit retourner HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED).

magasin de propriétés In-Memory

Avant d’examiner l’implémentation de _LoadProperties, vous devez comprendre le tableau PropertyMap utilisé dans l’exemple pour mapper les propriétés du document XML aux propriétés existantes du système de propriétés via leurs valeurs PKEY.

Vous ne devez pas exposer tous les éléments et attributs du fichier XML en tant que propriété. Au lieu de cela, sélectionnez uniquement ceux qui, selon vous, seront utiles aux utilisateurs finaux dans le organization de leurs documents (dans ce cas, les recettes). Il s’agit d’un concept important à garder à l’esprit lorsque vous développez vos gestionnaires de propriétés : la différence entre les informations vraiment utiles pour les scénarios d’organisation et les informations qui appartiennent aux détails de votre fichier et qui peuvent être consultées en ouvrant le fichier lui-même. Les propriétés ne sont pas destinées à être une duplication complète d’un fichier 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 },
};

Voici l’implémentation complète de la méthode _LoadProperties appelée par 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;
}

La méthode _LoadProperties appelle la fonction d’assistance de l’interpréteur de commandes PSCreateMemoryPropertyStore pour créer un magasin de propriétés en mémoire (cache) pour les propriétés gérées. En utilisant un cache, les modifications sont suivies pour vous. Cela vous permet de ne pas savoir si une valeur de propriété a été modifiée dans le cache, mais pas encore enregistrée dans le stockage persistant. Il vous libère également des valeurs de propriété inutilement persistantes qui n’ont pas changé.

La méthode _LoadProperties appelle également _LoadProperty dont l’implémentation est illustrée dans le code suivant) une fois pour chaque propriété mappée. _LoadProperty obtient la valeur de la propriété telle que spécifiée dans l’élément PropertyMap du flux XML et l’affecte au cache en mémoire via un appel à IPropertyStoreCache::SetValueAndState. L’indicateur PSC_NORMAL dans l’appel à IPropertyStoreCache::SetValueAndState indique que la valeur de la propriété n’a pas été modifiée depuis son entrée dans le cache.

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;
}

Gestion des valeurs PROPVARIANT-Based

Dans l’implémentation de _LoadProperty, une valeur de propriété est fournie sous la forme d’un PROPVARIANT. Un ensemble d’API dans le kit de développement logiciel (SDK) est fourni pour convertir des types primitifs tels que PWSTR ou int vers ou à partir de types PROPVARIANT . Ces API se trouvent dans Propvarutil.h.

Par exemple, pour convertir un PROPVARIANT en chaîne, vous pouvez utiliser PropVariantToString comme illustré ici.

PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);

Pour initialiser un PROPVARIANT à partir d’une chaîne, vous pouvez utiliser InitPropVariantFromString.

InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);

Comme vous pouvez le voir dans l’un des fichiers de recette inclus dans l’exemple, il peut y avoir plusieurs mot clé dans chaque fichier. Pour tenir compte de cela, le système de propriétés prend en charge les chaînes à valeurs multiples représentées en tant que vecteur de chaînes (pour instance « VT_VECTOR | VT_LPWSTR »). La méthode _LoadVectorProperty dans l’exemple utilise des valeurs vectorielles.

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;
}

Si une valeur n’existe pas dans le fichier, ne retournez pas d’erreur. Au lieu de cela, définissez la valeur sur VT_EMPTY et retournez S_OK. VT_EMPTY indique que la valeur de la propriété n’existe pas.

Prise en charge des métadonnées ouvertes

Cet exemple utilise un format de fichier XML. Son schéma peut être étendu pour prendre en charge des propriétés qui n’ont pas été pensées pendant le développement, par exemple. Ce système est appelé métadonnées ouvertes. Cet exemple étend le système de propriétés en créant un nœud sous l’élément Recipe appelé ExtendedProperties, comme illustré dans l’exemple de code suivant.

<ExtendedProperties>
    <Property 
        Name="{65A98875-3C80-40AB-ABBC-EFDAF77DBEE2}, 100"
        EncodedValue="HJKHJDHKJHK"/>
</ExtendedProperties>

Pour charger des propriétés étendues persistantes pendant l’initialisation, implémentez la méthode _LoadExtendedProperties , comme illustré dans l’exemple de code suivant.

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);
                    }

Les API de sérialisation déclarées dans Propsys.h sont utilisées pour sérialiser et désérialiser les types PROPVARIANT dans des objets blob de données, puis l’encodage base64 est utilisé pour sérialiser ces objets blob dans des chaînes qui peuvent être enregistrées dans le xml. Ces chaînes sont stockées dans l’attribut EncodedValue de l’élément ExtendedProperties . La méthode d’utilitaire suivante, implémentée dans le fichier Util.cpp de l’exemple, effectue la sérialisation. Il commence par un appel à la fonction StgSerializePropVariant pour effectuer la sérialisation binaire, comme illustré dans l’exemple de code suivant.

HRESULT SerializePropVariantAsString(const PROPVARIANT *ppropvar, PWSTR *pszOut)
{
    SERIALIZEDPROPERTYVALUE *pBlob;
    ULONG cbBlob;
    HRESULT hr = StgSerializePropVariant(ppropvar, &pBlob, &cbBlob);

Ensuite, la fonction CryptBinaryToStringÂ, déclarée dans Wincrypt.h, effectue la conversion 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>;}

La fonction DeserializePropVariantFromString , également présente dans Util.cpp, inverse l’opération, désérialisant les valeurs du fichier XML.

Pour plus d’informations sur la prise en charge des métadonnées ouvertes, consultez « Types de fichiers qui prennent en charge les métadonnées ouvertes » dans Types de fichiers.

contenu Full-Text

Les gestionnaires de propriétés peuvent également faciliter la recherche en texte intégral du contenu du fichier, et ils constituent un moyen simple de fournir cette fonctionnalité si le format de fichier n’est pas trop compliqué. Il existe une autre méthode plus puissante pour fournir le texte intégral du fichier via l’implémentation de l’interface IFilter .

Le tableau suivant récapitule les avantages de chaque approche utilisant IFilter ou IPropertyStore.

Fonctionnalité Ifilter Ipropertystore
Autorise l’écriture différée dans des fichiers ? Non Oui
Fournit une combinaison de contenu et de propriétés ? Oui Oui
Multilingue? Oui Non
MIME/Incorporé ? Oui Non
Limites de texte ? Phrase, paragraphe, chapitre None
Implémentation prise en charge pour SPS/SQL Server ? Oui Non
Implémentation Complex Simple

 

Dans l’exemple de gestionnaire de recettes, le format de fichier de recette n’a pas d’exigences complexes. Par conséquent, seul IPropertyStore a été implémenté pour la prise en charge de texte intégral. La recherche en texte intégral est implémentée pour les nœuds XML nommés dans le tableau suivant.

const PWSTR c_rgszContentXPath[] = {
    L"Recipe/Ingredients/Item",
    L"Recipe/Directions/Step",
    L"Recipe/RecipeInfo/Yield",
    L"Recipe/RecipeKeywords/Keyword",
};

Le système de propriétés contient la System.Search.Contents propriété (PKEY_Search_Contents), qui a été créée pour fournir du contenu de texte intégral à l’indexeur. La valeur de cette propriété n’est jamais affichée directement dans l’interface utilisateur ; Le texte de tous les nœuds XML nommés dans le tableau ci-dessus est concaténé en une seule chaîne. Cette chaîne est ensuite fournie à l’indexeur en tant que contenu de texte intégral du fichier de recette par le biais d’un appel à IPropertyStoreCache::SetValueAndState , comme illustré dans l’exemple de code suivant.

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;}

Fourniture de valeurs pour les propriétés

Lorsqu’ils sont utilisés pour lire des valeurs, les gestionnaires de propriétés sont généralement appelés pour l’une des raisons suivantes :

  • Pour énumérer toutes les valeurs de propriété.
  • Pour obtenir la valeur d’une propriété spécifique.

Pour l’énumération, un gestionnaire de propriétés est invité à énumérer ses propriétés lors de l’indexation ou lorsque la boîte de dialogue propriétés demande l’affichage des propriétés dans le groupe Autre . L’indexation se poursuit constamment en tant qu’opération en arrière-plan. Chaque fois qu’un fichier change, l’indexeur est averti et il réindexe le fichier en demandant au gestionnaire de propriétés d’énumérer ses propriétés. Par conséquent, il est essentiel que les gestionnaires de propriétés soient implémentés efficacement et retournent des valeurs de propriété aussi rapidement que possible. Énumérez toutes les propriétés pour lesquelles vous avez des valeurs, comme vous le feriez pour n’importe quelle collection, mais n’énumérez pas les propriétés qui impliquent des calculs gourmands en mémoire ou des requêtes réseau susceptibles de ralentir leur récupération.

Lors de l’écriture d’un gestionnaire de propriétés, vous devez généralement prendre en compte les deux ensembles de propriétés suivants.

  • Propriétés principales : propriétés que votre type de fichier prend en charge en mode natif. Par exemple, un gestionnaire de propriétés photo pour les métadonnées EXIF (Exchangeable Image File) prend en charge System.Photo.FNumberen mode natif .
  • Propriétés étendues : propriétés que votre type de fichier prend en charge dans le cadre des métadonnées ouvertes.

Étant donné que l’exemple utilise un cache en mémoire, l’implémentation des méthodes IPropertyStore consiste simplement à déléguer à ce cache, comme illustré dans l’exemple de code suivant.

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); }

Si vous choisissez de ne pas déléguer au cache en mémoire, vous devez implémenter vos méthodes pour fournir> le comportement attendu suivant :

Écriture en arrière des valeurs

Lorsque le gestionnaire de propriétés écrit la valeur d’une propriété à l’aide de IPropertyStore::SetValue, il n’écrit pas la valeur dans le fichier tant que IPropertyStore::Commit n’est pas appelé. Le cache en mémoire peut être utile pour implémenter ce schéma. Dans l’exemple de code, l’implémentation IPropertyStore::SetValue définit simplement la nouvelle valeur dans le cache en mémoire et définit l’état de cette propriété sur 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;
}

Dans toute implémentation IPropertyStore , le comportement suivant est attendu de la part de IPropertyStore::SetValue :

  • Si la propriété existe déjà, la valeur de la propriété est définie.
  • Si la propriété n’existe pas, la nouvelle propriété est ajoutée et sa valeur est définie.
  • Si la valeur de propriété ne peut pas être conservée avec la même précision que celle indiquée (pour instance, troncation en raison de limitations de taille dans le format de fichier), la valeur est définie autant que possible et INPLACE_S_TRUNCATED est retourné.
  • Si la propriété n’est pas prise en charge par le gestionnaire de propriétés, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) est retournée.
  • S’il existe une autre raison pour laquelle la valeur de propriété ne peut pas être définie, comme le fichier verrouillé ou l’absence de droits de modification via des listes de contrôle d’accès (ACL), STG_E_ACCESSDENIED est retournée.

L’un des principaux avantages de l’utilisation de flux, comme exemple, est la fiabilité. Les gestionnaires de propriétés doivent toujours considérer qu’ils ne peuvent pas laisser un fichier dans un état incohérent en cas de défaillance catastrophique. L’endommagement des fichiers d’un utilisateur doit évidemment être évité, et la meilleure façon de le faire est d’utiliser un mécanisme de « copie sur écriture ». Si votre gestionnaire de propriétés utilise un flux pour accéder à un fichier, vous obtenez ce comportement automatiquement ; le système écrit toutes les modifications apportées au flux, en remplaçant le fichier par la nouvelle copie uniquement pendant l’opération de validation.

Pour remplacer ce comportement et contrôler manuellement le processus d’enregistrement de fichiers, vous pouvez désactiver le comportement d’enregistrement sécurisé en définissant la valeur ManualSafeSave dans l’entrée de Registre de votre gestionnaire, comme illustré ici.

HKEY_CLASSES_ROOT
   CLSID
      {66742402-F9B9-11D1-A202-0000F81FEDEE}
         ManualSafeSave = 1

Lorsqu’un gestionnaire spécifie la valeur ManualSafeSave, le flux avec lequel il est initialisé n’est pas un flux traité (STGM_TRANSACTED). Le gestionnaire lui-même doit implémenter la fonction d’enregistrement sécurisé pour s’assurer que le fichier n’est pas endommagé si l’opération d’enregistrement est interrompue. Si le gestionnaire implémente l’écriture sur place, il écrit dans le flux qui lui est donné. Si le gestionnaire ne prend pas en charge cette fonctionnalité, il doit récupérer un flux avec lequel écrire la copie mise à jour du fichier à l’aide de IDestinationStreamFactory::GetDestinationStream. Une fois l’écriture terminée, le gestionnaire doit appeler IPropertyStore::Commit sur le flux d’origine pour terminer l’opération, puis remplacer le contenu du flux d’origine par la nouvelle copie du fichier.

ManualSafeSave est également la situation par défaut si vous n’initialisez pas votre gestionnaire avec un flux. Sans flux d’origine pour recevoir le contenu du flux temporaire, vous devez utiliser ReplaceFile pour effectuer un remplacement atomique du fichier source.

Les formats de fichiers volumineux qui seront utilisés de manière à produire des fichiers supérieurs à 1 Mo doivent implémenter la prise en charge de l’écriture de propriétés sur place ; dans le cas contraire, le comportement des performances ne répond pas aux attentes des clients du système immobilier. Dans ce scénario, le temps nécessaire à l’écriture des propriétés ne doit pas être affecté par la taille du fichier.

Pour les fichiers très volumineux, par exemple un fichier vidéo de 1 Go ou plus, une solution différente est nécessaire. S’il n’y a pas suffisamment d’espace dans le fichier pour effectuer une écriture sur place, le gestionnaire peut échouer la mise à jour de la propriété si la quantité d’espace réservée à l’écriture de propriétés sur place a été épuisée. Cet échec se produit pour éviter les performances médiocres résultant de 2 Go d’E/S (1 à lire, 1 à écrire). En raison de cet échec potentiel, ces formats de fichier doivent réserver suffisamment d’espace pour l’écriture de propriétés sur place.

Si le fichier a suffisamment d’espace dans son en-tête pour écrire des métadonnées, et si l’écriture de ces métadonnées n’entraîne pas la croissance ou la réduction du fichier, il peut être sûr d’écrire sur place. Nous recommandons 64 Ko comme point de départ. L’écriture sur place équivaut au gestionnaire demandant ManualSafeSave et appelant IStream::Commit dans l’implémentation de IPropertyStore::Commit, et offre de meilleures performances que la copie sur écriture. Si la taille du fichier change en raison de modifications de valeur de propriété, l’écriture sur place ne doit pas être tentée en raison du risque d’endommagement d’un fichier en cas d’arrêt anormal.

Notes

Pour des raisons de performances, nous vous recommandons d’utiliser l’option ManualSafeSave avec des gestionnaires de propriétés qui utilisent des fichiers de 100 Ko ou plus.

 

Comme illustré dans l’exemple d’implémentation suivant d’IPropertyStore::Commit, le gestionnaire de ManualSafeSave est inscrit pour illustrer l’option d’enregistrement sécurisé manuel. La méthode _SaveCacheToDom écrit les valeurs de propriété stockées dans le cache en mémoire dans l’objet 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);

Ensuite, demandez si le pecified prend en charge 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);_

Ensuite, validez le flux d’origine, qui réécrit les données dans le fichier d’origine de manière sécurisée.

                        if (SUCCEEDED(hr))
                                {
                                    // Commit the real output stream.
                                    _pStream->Commit(STGC_DEFAULT);
                                }
                            }

                            pStreamCommit->Release();
                        }

                        pSafeCommit->Release();
                    }
                }
            }
        }
    }

Examinez ensuite l’implémentation _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;  

Ensuite, obtenez le nombre de propriétés stockées dans le cache en mémoire.

HRESULT hr = _pCache->GetCount(&cProps);          
            

À présent, effectuez une itération au sein des propriétés pour déterminer si la valeur d’une propriété a été modifiée depuis son chargement en mémoire.

    for (UINT i = 0; SUCCEEDED(hr) && (i < cProps); ++i)
    {
        PROPERTYKEY key;
        hr = _pCache->GetAt(i, &key); 

La méthode IPropertyStoreCache::GetState obtient l’état de la propriété dans le cache. L’indicateur PSC_DIRTY, qui a été défini dans l’implémentation IPropertyStore::SetValue , marque une propriété comme modifiée.

        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); 

Mappez la propriété au nœud XML comme spécifié dans le tableau 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;
                        }     

Si une propriété n’est pas dans la carte, il s’agit d’une nouvelle propriété qui a été définie par Windows Explorer. Étant donné que les métadonnées ouvertes sont prises en charge, enregistrez la nouvelle propriété dans la section ExtendedProperties du code XML.

                    
                    // Otherwise, save as an extended property.
                    if (!fIsNativeProperty)
                    {
                        hr = _SaveExtendedProperty(key, propvar);
                    }

                    PropVariantClear(&propvar);
                }
            }
        }
    }

    return hr;    

Implémentation d’IPropertyStoreCapabilities

IPropertyStoreCapabilities indique à l’interface utilisateur shell si une propriété particulière peut être modifiée dans l’interface utilisateur shell. Il est important de noter que cela concerne uniquement la possibilité de modifier la propriété dans l’interface utilisateur, et non pas si vous pouvez appeler avec succès IPropertyStore::SetValue sur la propriété. Une propriété qui provoque une valeur de retour de S_FALSE de IPropertyStoreCapabilities::IsPropertyWritable peut toujours être définie via une application.

interface IPropertyStoreCapabilities : IUnknown
{
    HRESULT IsPropertyWritable([in] REFPROPERTYKEY key);
}

IsPropertyWritable retourne S_OK pour indiquer que les utilisateurs finaux doivent être autorisés à modifier la propriété directement ; S_FALSE indique qu’ils ne devraient pas. S_FALSE peut signifier que les applications sont responsables de l’écriture de la propriété, et non les utilisateurs. L’interpréteur de commandes désactive les contrôles de modification en fonction des résultats des appels à cette méthode. Un gestionnaire qui n’implémente pas IPropertyStoreCapabilities est supposé prendre en charge les métadonnées ouvertes via la prise en charge de l’écriture d’une propriété.

Si vous créez un gestionnaire qui gère uniquement les propriétés en lecture seule, vous devez implémenter votre méthode Initialize (IInitializeWithStream, IInitializeWithItem ou IInitializeWithFile) afin qu’elle retourne STG_E_ACCESSDENIED lorsqu’elle est appelée avec l’indicateur STGM_READWRITE.

L’attribut isInnate est défini sur true pour certaines propriétés. Les propriétés innées présentent les caractéristiques suivantes :

  • La propriété est généralement calculée d’une manière ou d’une autre. Pour instance, System.Image.BitDepth est calculé à partir de l’image elle-même.
  • La modification de la propriété n’aurait pas de sens sans modifier le fichier. Par instance, modifier System.Image.Dimensions ne redimensionne pas l’image. Il n’est donc pas logique de permettre à l’utilisateur de la modifier.
  • Dans certains cas, ces propriétés sont fournies automatiquement par le système. Les exemples incluent System.DateModified, qui est fourni par le système de fichiers, et System.SharedWith, qui est basé sur les personnes avec lesquelles l’utilisateur partage le fichier.

En raison de ces caractéristiques, les propriétés marquées comme IsInnate sont fournies à l’utilisateur dans l’interface utilisateur shell uniquement en tant que propriétés en lecture seule. Si une propriété est marquée comme IsInnate, le système de propriétés ne stocke pas cette propriété dans le gestionnaire de propriétés. Par conséquent, les gestionnaires de propriétés n’ont pas besoin d’un code spécial pour tenir compte de ces propriétés dans leurs implémentations. Si la valeur de l’attribut IsInnate n’est pas explicitement indiquée pour une propriété particulière, la valeur par défaut est false.

Inscription et distribution de gestionnaires de propriétés

Une fois le gestionnaire de propriétés implémenté, il doit être inscrit et son extension de nom de fichier associée au gestionnaire. Pour plus d’informations, consultez Inscription et distribution de gestionnaires de propriétés.

Présentation des gestionnaires de propriétés

Utilisation de noms de type

Utilisation de listes de propriétés

Inscription et distribution de gestionnaires de propriétés

Meilleures pratiques et FAQ sur le gestionnaire de propriétés