Inizializzazione dei gestori delle proprietà

In questo argomento viene illustrato come creare e registrare i gestori delle proprietà per l'uso con il sistema di proprietà Di Windows.

Questo argomento è organizzato come segue:

Gestori delle proprietà

I gestori delle proprietà sono una parte fondamentale del sistema di proprietà. Vengono richiamati in-process dal indicizzatore per leggere e indicizzare i valori delle proprietà e vengono richiamati anche da Esplora risorse di Windows per leggere e scrivere i valori delle proprietà direttamente nei file. Questi gestori devono essere scritti attentamente e testati per evitare prestazioni degradate o la perdita di dati nei file interessati. Per altre informazioni sulle considerazioni specifiche dell'indicizzatore che influiscono sull'implementazione del gestore delle proprietà, vedere Sviluppo di gestori di proprietà per Windows Search.

In questo argomento viene illustrato un formato di file basato su XML di esempio che descrive una ricetta con estensione nome file con estensione ricetta. L'estensione del nome file con estensione ricetta è registrata come formato di file distinto anziché basarsi sul formato file .xml più generico, il cui gestore usa un flusso secondario per archiviare le proprietà. È consigliabile registrare estensioni di file univoche per i tipi di file.

Prima di iniziare

I gestori delle proprietà sono oggetti COM che creano l'astrazione IPropertyStore per un formato di file specifico. Leggono (analizza) e scrivono questo formato di file in modo conforme alla relativa specifica. Alcuni gestori di proprietà svolgono il loro lavoro in base alle API che astrattano l'accesso a un formato di file specifico. Prima di sviluppare un gestore delle proprietà per il formato di file, è necessario comprendere in che modo il formato di file archivia le proprietà e come vengono mappate le proprietà (nomi e valori) nell'astrazione dell'archivio delle proprietà.

Quando si pianifica l'implementazione, tenere presente che i gestori delle proprietà sono componenti di basso livello caricati nel contesto di processi come Esplora risorse, l'indicizzatore di Windows Search e le applicazioni di terze parti che usano il modello di programmazione degli elementi shell. Di conseguenza, i gestori delle proprietà non possono essere implementati nel codice gestito e devono essere implementati in C++. Se il gestore usa qualsiasi API o servizi per eseguire il proprio lavoro, è necessario assicurarsi che tali servizi possano funzionare correttamente negli ambienti in cui viene caricato il gestore delle proprietà.

Nota

I gestori delle proprietà sono sempre associati a tipi di file specifici; pertanto, se il formato di file contiene proprietà che richiedono un gestore di proprietà personalizzate, è consigliabile registrare sempre un'estensione di nome file univoca per ogni formato di file.

 

Inizializzazione dei gestori delle proprietà

Prima di usare una proprietà dal sistema, viene inizializzata chiamando un'implementazione di IInitializeWithStream. Il gestore delle proprietà deve essere inizializzato mediante l'assegnazione del sistema a un flusso anziché lasciare tale assegnazione all'implementazione del gestore. Questo metodo di inizializzazione garantisce le operazioni seguenti:

  • Il gestore delle proprietà può essere eseguito in un processo con restrizioni (una funzionalità di sicurezza importante) senza avere diritti di accesso ai file di lettura o scrittura diretta, anziché accedere al contenuto tramite il flusso.
  • Il sistema può essere attendibile per gestire correttamente i file oplock, che è una misura di affidabilità importante.
  • Il sistema delle proprietà offre un servizio di salvataggio sicuro automatico senza alcuna funzionalità aggiuntiva richiesta dall'implementazione del gestore delle proprietà. Per altre informazioni sui flussi, vedere la sezione Scrittura di valori indietro .
  • L'uso di IInitializeWithStream astrae l'implementazione dai dettagli del file system. Ciò consente al gestore di supportare l'inizializzazione tramite archiviazioni alternative, ad esempio una cartella FTP (File Transfer Protocol) o un file compresso con un'estensione del nome file .zip.

Esistono casi in cui l'inizializzazione con flussi non è possibile. In queste situazioni sono disponibili due interfacce aggiuntive che i gestori delle proprietà possono implementare: IInitializeWithFile e IInitializeWithItem. Se un gestore delle proprietà non implementa IInitializeWithStream, deve rifiutare l'esecuzione nel processo isolato in cui l'indicizzatore di sistema lo inserisce per impostazione predefinita se si verifica una modifica al flusso. Per rifiutare esplicitamente questa funzionalità, impostare il valore del Registro di sistema seguente.

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

Tuttavia, è molto meglio implementare IInitializeWithStream e eseguire un'inizializzazione basata su flusso. Il gestore delle proprietà sarà più sicuro e più affidabile di conseguenza. La disabilitazione dell'isolamento del processo è in genere destinata solo ai gestori delle proprietà legacy e deve essere evitato in modo strenuo da qualsiasi nuovo codice.

Per esaminare l'implementazione di un gestore di proprietà in dettaglio, vedere l'esempio di codice seguente, ovvero un'implementazione di IInitializeWithStream::Initialize. Il gestore viene inizializzato caricando un documento di ricetta basato su XML tramite un puntatore all'istanza IStream associata del documento. La variabile _spDocEle utilizzata alla fine dell'esempio di codice viene definita in precedenza nell'esempio come MSXML2::IXMLDOMElementPtr.

Nota

Gli esempi di codice seguenti e tutti gli esempi di codice successivi vengono acquisiti dall'esempio di gestore di ricette incluso nel Kit di sviluppo software Windows (SDK). .

 

HRESULT CRecipePropertyStore::Initialize(IStream *pStream, DWORD grfMode)
{
    HRESULT hr = E_FAIL;
    
    try
    {
        if (!_spStream)
        {
            hr = _spDomDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60));
            
            if (SUCCEEDED(hr))
            {
                if (VARIANT_TRUE == _spDomDoc->load(static_cast<IUnknown *>(pStream)))
                {
                    _spDocEle = _spDomDoc->documentElement;
                }

Â

Dopo il caricamento del documento stesso, le proprietà da visualizzare in Esplora risorse vengono caricate chiamando il metodo _LoadProperties protetto, come illustrato nell'esempio di codice seguente. Questo processo viene esaminato in dettaglio nella sezione successiva.

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

Se il flusso è di sola lettura, ma il parametro grfMode contiene il flag di STGM_READWRITE, l'inizializzazione deve avere esito negativo e restituire STG_E_ACCESSDENIED. Senza questo controllo, Esplora risorse mostra i valori delle proprietà come scrivibili anche se non sono, causando un'esperienza utente finale confusa.

Il gestore delle proprietà viene inizializzato una sola volta nella sua durata. Se viene richiesta una seconda inizializzazione, il gestore deve restituire HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED).

Archivio proprietà In-Memory

Prima di esaminare l'implementazione di _LoadProperties, è necessario comprendere la matrice PropertyMap usata nell'esempio per eseguire il mapping delle proprietà nel documento XML alle proprietà esistenti nel sistema delle proprietà tramite i valori PKEY.

Non è consigliabile esporre ogni elemento e attributo nel file XML come proprietà. Selezionare invece solo quelli che si ritiene siano utili per gli utenti finali nell'organizzazione dei propri documenti (in questo caso, ricette). Questo è un concetto importante da tenere presente quando si sviluppano i gestori delle proprietà: la differenza tra le informazioni che è veramente utile per gli scenari aziendali e le informazioni che appartengono ai dettagli del file e possono essere visualizzate aprendo il file stesso. Le proprietà non devono essere una duplicazione completa di un file 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 },
};

Ecco l'implementazione completa del metodo _LoadProperties chiamato da 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;
}

Il metodo _LoadProperties chiama la funzione helper di Shell PSCreateMemoryPropertyStore per creare un archivio proprietà in memoria (cache) per le proprietà gestite. Usando una cache, le modifiche vengono rilevate per l'utente. Ciò consente di tenere traccia del fatto che un valore della proprietà sia stato modificato nella cache, ma non ancora salvato in archiviazione persistente. Consente inoltre di mantenere in modo permanente i valori delle proprietà che non sono stati modificati.

Il metodo _LoadProperties chiama anche _LoadProperty la cui implementazione è illustrata nel codice seguente) una volta per ogni proprietà mappata. _LoadProperty ottiene il valore della proprietà come specificato nell'elemento PropertyMap nel flusso XML e lo assegna alla cache in memoria tramite una chiamata a IPropertyStoreCache::SetValueAndState. Il flag PSC_NORMAL nella chiamata a IPropertyStoreCache::SetValueAndState indica che il valore della proprietà non è stato modificato dopo l'immissione della 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;
}

Gestione dei valori PROPVARIANT-Based

Nell'implementazione di _LoadProperty viene fornito un valore di proprietà sotto forma di PROPVARIANT. Viene fornito un set di API nel software development kit (SDK) per convertire da tipi primitivi, ad esempio PWSTR o int a o da tipi PROPVARIANT . Queste API sono disponibili in Propvarutil.h.

Ad esempio, per convertire un PROPVARIANT in una stringa, è possibile usare PropVariantToString come illustrato qui.

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

Per inizializzare un PROPVARIANT da una stringa, è possibile usare InitPropVariantFromString.

InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);

Come si può vedere in uno dei file di ricetta inclusi nell'esempio, è possibile che siano presenti più parole chiave in ogni file. Per tenere conto di questo, il sistema delle proprietà supporta stringhe multivalore rappresentate come vettore di stringhe (ad esempio "VT_VECTOR | VT_LPWSTR"). Il metodo _LoadVectorProperty nell'esempio usa valori basati su vettore.

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

Se un valore non esiste nel file, non restituisce un errore. Impostare invece il valore su VT_EMPTY e restituire S_OK. VT_EMPTY indica che il valore della proprietà non esiste.

Supporto dei metadati aperti

In questo esempio viene usato un formato di file basato su XML. Lo schema può essere esteso per supportare le proprietà che non sono state considerate durante lo sviluppomet, ad esempio. Questo sistema è noto come metadati aperti. Questo esempio estende il sistema di proprietà creando un nodo nell'elemento Recipe denominato ExtendedProperties, come illustrato nell'esempio di codice seguente.

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

Per caricare proprietà estese persistenti durante l'inizializzazione, implementare il metodo _LoadExtendedProperties , come illustrato nell'esempio di codice seguente.

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

Le API di serializzazione dichiarate in Propsys.h vengono usate per serializzare e deserializzare i tipi PROPVARIANT in BLOB di dati e quindi la codifica Base64 viene usata per serializzare tali BLOB in stringhe che possono essere salvate nel codice XML. Queste stringhe vengono archiviate nell'attributo EncodedValue dell'elemento ExtendedProperties . Il metodo di utilità seguente, implementato nel file Util.cpp dell'esempio, esegue la serializzazione. Inizia con una chiamata alla funzione StgSerializePropVariant per eseguire la serializzazione binaria, come illustrato nell'esempio di codice seguente.

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

Successivamente, la funzione CryptBinaryToString, dichiarata in Wincrypt.h, esegue la conversione 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 funzione DeserializePropVariantFromString , disponibile anche in Util.cpp, inverte l'operazione, deserializzando i valori dal file XML.

Per informazioni sul supporto per i metadati aperti, vedere "Tipi di file che supportano metadati aperti" in Tipi di file.

Contenuto di Full-Text

I gestori di proprietà possono anche facilitare una ricerca full-text del contenuto del file e sono un modo semplice per fornire tale funzionalità se il formato di file non è eccessivamente complicato. Esiste un modo alternativo e più potente per fornire il testo completo del file tramite l'implementazione dell'interfaccia IFilter .

La tabella seguente riepiloga i vantaggi di ogni approccio usando IFilter o IPropertyStore.

Funzionalità Ifilter Ipropertystore
Consente di eseguire il writeback nei file? No
Fornisce una combinazione di contenuto e proprietà?
Multilingue? No
MIME/Embedded? No
Limiti di testo? Frase, paragrafo, capitolo Nessuno
Implementazione supportata per SPS/SQL Server? No
Implementazione Complex Semplice

 

Nell'esempio del gestore delle ricette, il formato del file recipe non ha requisiti complessi, quindi solo IPropertyStore è stato implementato per il supporto full-text. La ricerca full-text viene implementata per i nodi XML denominati nella matrice seguente.

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

Il sistema di proprietà contiene la System.Search.Contents proprietà (PKEY_Search_Contents), creata per fornire contenuto full-text all'indicizzatore. Il valore di questa proprietà non viene mai visualizzato direttamente nell'interfaccia utente; il testo di tutti i nodi XML denominati nella matrice precedente viene concatenato in una singola stringa. Tale stringa viene quindi fornita all'indicizzatore come contenuto full-text del file recipe tramite una chiamata a IPropertyStoreCache::SetValueAndState , come illustrato nell'esempio di codice seguente.

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

Specifica dei valori per le proprietà

Quando vengono usati per leggere i valori, i gestori di proprietà vengono in genere richiamati per uno dei motivi seguenti:

  • Per enumerare tutti i valori delle proprietà.
  • Per ottenere il valore di una proprietà specifica.

Per l'enumerazione, viene chiesto a un gestore di proprietà di enumerare le relative proprietà durante l'indicizzazione o quando la finestra di dialogo delle proprietà richiede le proprietà da visualizzare nel gruppo Other . L'indicizzazione continua costantemente come operazione in background. Ogni volta che un file viene modificato, l'indicizzatore riceve una notifica e ri indicizza il file chiedendo al gestore della proprietà di enumerare le relative proprietà. Pertanto, è fondamentale che i gestori di proprietà vengano implementati in modo efficiente e restituiscano i valori delle proprietà il più rapidamente possibile. Enumerare tutte le proprietà per le quali si dispone di valori, esattamente come per qualsiasi raccolta, ma non enumerare le proprietà che comportano calcoli a elevato utilizzo di memoria o richieste di rete che potrebbero rallentare il recupero.

Quando si scrive un gestore di proprietà, in genere è necessario considerare i due set di proprietà seguenti.

  • Proprietà primarie: proprietà supportate dal tipo di file in modo nativo. Ad esempio, un gestore di proprietà foto per i metadati EXIF (Exchangeable Image File) supporta System.Photo.FNumberin modo nativo .
  • Proprietà estese: proprietà supportate dal tipo di file come parte dei metadati aperti.

Poiché l'esempio usa la cache in memoria, l'implementazione dei metodi IPropertyStore è solo una questione di delega a tale cache, come illustrato nell'esempio di codice seguente.

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

Se si sceglie di non delegare alla cache in memoria, è necessario implementare i metodi per fornire> il comportamento previsto seguente:

Scrittura di valori indietro

Quando il gestore della proprietà scrive il valore di una proprietà usando IPropertyStore::SetValue, non scrive il valore nel file finché non viene chiamato IPropertyStore::Commit . La cache in memoria può essere utile per implementare questo schema. Nel codice di esempio l'implementazione IPropertyStore::SetValue imposta semplicemente il nuovo valore nella cache in memoria e imposta lo stato di tale proprietà su 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;
}

In qualsiasi implementazione IPropertyStore , è previsto il comportamento seguente da IPropertyStore::SetValue:

  • Se la proprietà esiste già, viene impostato il valore della proprietà .
  • Se la proprietà non esiste, viene aggiunta la nuova proprietà e il relativo valore impostato.
  • Se il valore della proprietà non può essere salvato in modo permanente con la stessa accuratezza specificata (ad esempio, troncamento a causa di limitazioni delle dimensioni nel formato di file), il valore viene impostato il più possibile e INPLACE_S_TRUNCATED viene restituito.
  • Se la proprietà non è supportata dal gestore di proprietà, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) viene restituita.
  • Se esiste un altro motivo per cui non è possibile impostare il valore della proprietà, ad esempio il file bloccato o la mancanza di diritti di modifica tramite elenchi di controllo di accesso (ACL), viene restituito STG_E_ACCESSDENIED.

Un vantaggio principale dell'uso dei flussi, come esempio, è l'affidabilità. I gestori di proprietà devono sempre considerare che non possono lasciare un file in uno stato incoerente in caso di errore irreversibile. Il danneggiamento dei file di un utente dovrebbe ovviamente essere evitato e il modo migliore per eseguire questa operazione è con un meccanismo di "copia su scrittura". Se il gestore delle proprietà usa un flusso per accedere a un file, si ottiene questo comportamento automaticamente; il sistema scrive tutte le modifiche apportate al flusso, sostituendo il file con la nuova copia solo durante l'operazione di commit.

Per ignorare questo comportamento e controllare manualmente il processo di salvataggio dei file, è possibile rifiutare esplicitamente il comportamento di salvataggio sicuro impostando il valore ManualSafeSave nella voce del Registro di sistema del gestore, come illustrato qui.

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

Quando un gestore specifica il valore ManualSafeSave, il flusso con cui viene inizializzato non è un flusso transazionato (STGM_TRANSACTED). Il gestore stesso deve implementare la funzione di salvataggio sicuro per assicurarsi che il file non sia danneggiato se l'operazione di salvataggio viene interrotta. Se il gestore implementa la scrittura sul posto, scriverà nel flusso specificato. Se il gestore non supporta questa funzionalità, deve recuperare un flusso con cui scrivere la copia aggiornata del file usando IDestinationStreamFactory::GetDestinationStream. Al termine della scrittura, il gestore deve chiamare IPropertyStore::Commit nel flusso originale per completare l'operazione e sostituire il contenuto del flusso originale con la nuova copia del file.

ManualSafeSave è anche la situazione predefinita se non si inizializza il gestore con un flusso. Senza un flusso originale per ricevere il contenuto del flusso temporaneo, è necessario usare ReplaceFile per eseguire una sostituzione atomica del file di origine.

I formati di file di grandi dimensioni che verranno usati in modo da produrre file superiori a 1 MB devono implementare il supporto per la scrittura delle proprietà sul posto; in caso contrario, il comportamento delle prestazioni non soddisfa le aspettative dei client del sistema di proprietà. In questo scenario, il tempo necessario per scrivere le proprietà non deve essere influenzato dalle dimensioni del file.

Per i file molto grandi, ad esempio un file video di 1 GB o più, è necessaria una soluzione diversa. Se nel file non è disponibile spazio sufficiente per eseguire la scrittura sul posto, il gestore potrebbe non riuscire ad aggiornare la proprietà se la quantità di spazio riservata per la scrittura delle proprietà sul posto è stata esaurita. Questo errore si verifica per evitare prestazioni scarse derivanti da 2 GB di I/O (da 1 a lettura, 1 da scrivere). A causa di questo potenziale errore, questi formati di file devono riservare spazio sufficiente per la scrittura delle proprietà sul posto.

Se il file ha spazio sufficiente nell'intestazione per scrivere metadati e se la scrittura di tali metadati non causa l'aumento o la compattazione del file, potrebbe essere sicuro scrivere sul posto. È consigliabile usare 64 KB come punto di partenza. La scrittura sul posto equivale al gestore che richiede ManualSafeSave e chiama IStream::Commit nell'implementazione di IPropertyStore::Commit e offre prestazioni molto migliori rispetto alla copia in scrittura. Se le dimensioni del file cambiano a causa di modifiche al valore della proprietà, la scrittura sul posto non deve essere tentata a causa del potenziale danneggiamento di un file in caso di terminazione anomala.

Nota

Per motivi di prestazioni, è consigliabile usare l'opzione ManualSafeSave con gestori di proprietà che usano file di dimensioni pari a 100 KB o superiori.

 

Come illustrato nell'implementazione di esempio seguente di IPropertyStore::Commit, il gestore per ManualSafeSave viene registrato per illustrare l'opzione di salvataggio sicuro manuale. Il metodo _SaveCacheToDom scrive i valori delle proprietà archiviati nella cache in memoria nell'oggetto 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);

Successivamente, chiedere se il pecified supporta 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);_

Eseguire quindi il commit del flusso originale, che scrive nuovamente i dati nel file originale in modo sicuro.

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

                            pStreamCommit->Release();
                        }

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

Esaminare quindi l'implementazione _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;  

Ottenere quindi il numero di proprietà archiviate nella cache in memoria.

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

Eseguire ora l'iterazione delle proprietà per determinare se il valore di una proprietà è stato modificato dopo il caricamento in memoria.

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

Il metodo IPropertyStoreCache::GetState ottiene lo stato della proprietà nella cache. Il flag PSC_DIRTY, impostato nell'implementazione IPropertyStore::SetValue , contrassegna una proprietà come modificata.

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

Eseguire il mapping della proprietà al nodo XML come specificato nella matrice 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;
                        }     

Se una proprietà non è presente nella mappa, si tratta di una nuova proprietà impostata da Esplora risorse. Poiché i metadati aperti sono supportati, salvare la nuova proprietà nella sezione ExtendedProperties del codice XML.

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

                    PropVariantClear(&propvar);
                }
            }
        }
    }

    return hr;    

Implementazione di IPropertyStoreCapabilities

IPropertyStoreCapabilities informa l'interfaccia utente della shell se una determinata proprietà può essere modificata nell'interfaccia utente della shell. È importante notare che ciò è correlato solo alla possibilità di modificare la proprietà nell'interfaccia utente, non se è possibile chiamare correttamente IPropertyStore::SetValue nella proprietà . Una proprietà che provoca un valore restituito di S_FALSE da IPropertyStoreCapabilities::IsPropertyWritable potrebbe comunque essere in grado di essere impostata tramite un'applicazione.

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

IsPropertyWritable restituisce S_OK per indicare che gli utenti finali devono essere autorizzati a modificare direttamente la proprietà; S_FALSE indica che non devono essere. S_FALSE può significare che le applicazioni sono responsabili della scrittura della proprietà, non degli utenti. Shell disabilita i controlli di modifica in base ai risultati delle chiamate a questo metodo. Si presuppone che un gestore che non implementi IPropertyStoreCapabilities supporti i metadati aperti tramite il supporto per la scrittura di qualsiasi proprietà.

Se si compila un gestore che gestisce solo le proprietà di sola lettura, è necessario implementare il metodo Initialize (IInitializeWithStream, IInitializeWithItem o IInitializeWithFile) in modo che restituisca STG_E_ACCESSDENIED quando viene chiamato con il flag STGM_READWRITE.

Alcune proprietà hanno l'attributo isInnate impostato su true. Le proprietà innate hanno le caratteristiche seguenti:

  • La proprietà viene in genere calcolata in qualche modo. Ad esempio, System.Image.BitDepth viene calcolato dall'immagine stessa.
  • La modifica della proprietà non avrebbe senso senza modificare il file. Ad esempio, la modifica System.Image.Dimensions non ridimensiona l'immagine, quindi non ha senso consentire all'utente di modificarla.
  • In alcuni casi, queste proprietà vengono fornite automaticamente dal sistema. Gli esempi includono System.DateModified, che viene fornito dal file system e System.SharedWith, che si basa sull'utente con cui condivide il file.

A causa di queste caratteristiche, le proprietà contrassegnate come IsInnate vengono fornite all'utente nell'interfaccia utente della shell solo come proprietà di sola lettura. Se una proprietà è contrassegnata come IsInnate, il sistema di proprietà non archivia tale proprietà nel gestore della proprietà. Pertanto, i gestori di proprietà non necessitano di codice speciale per tenere conto di queste proprietà nelle relative implementazioni. Se il valore dell'attributo IsInnate non è indicato in modo esplicito per una determinata proprietà, il valore predefinito è false.

Registrazione e distribuzione di gestori di proprietà

Dopo aver implementato il gestore di proprietà, deve essere registrato e l'estensione del nome file associata al gestore. Per altre informazioni, vedere Registrazione e distribuzione dei gestori di proprietà.

Informazioni sui gestori delle proprietà

Uso dei nomi dei tipi

Utilizzo degli elenchi di proprietà

Registrazione e distribuzione di gestori di proprietà

Procedure consigliate e domande frequenti sul gestore delle proprietà