Initialisieren von Eigenschaftshandlern

In diesem Thema wird erläutert, wie Sie Eigenschaftenhandler für die Arbeit mit dem Windows-Eigenschaftensystem erstellen und registrieren.

Dieses Thema ist wie folgt organisiert:

Eigenschaftenhandler

Eigenschaftenhandler sind ein wichtiger Bestandteil des Eigenschaftensystems. Sie werden prozessintern vom Indexer aufgerufen, um Eigenschaftswerte zu lesen und zu indizieren, und werden auch von Windows Explorer prozessintern aufgerufen, um Eigenschaftswerte direkt in den Dateien zu lesen und zu schreiben. Diese Handler müssen sorgfältig geschrieben und getestet werden, um leistungseinbußen oder Datenverluste in den betroffenen Dateien zu verhindern. Weitere Informationen zu indexerspezifischen Überlegungen, die sich auf die Implementierung von Eigenschaftenhandlern auswirken, finden Sie unter Developing Property Handlers for Windows Search.For more information on indexer-specific considerations that affect property handler implementation, see Developing Property Handlers for Windows Search.

In diesem Thema wird ein XML-basiertes Beispieldateiformat erläutert, das ein Rezept mit der Dateinamenerweiterung .recipe beschreibt. Die Dateinamenerweiterung .recipe wird als eigenes Dateiformat registriert, anstatt sich auf das generischere .xml Dateiformat zu verlassen, dessen Handler einen sekundären Datenstrom zum Speichern von Eigenschaften verwendet. Es wird empfohlen, eindeutige Dateinamenerweiterungen für Ihre Dateitypen zu registrieren.

Vorbereitungen

Eigenschaftenhandler sind COM-Objekte, die die IPropertyStore-Abstraktion für ein bestimmtes Dateiformat erstellen. Sie lesen (analysieren) und schreiben dieses Dateiformat auf eine Weise, die der Spezifikation entspricht. Einige Eigenschaftshandler erledigen ihre Arbeit basierend auf APIs, die den Zugriff auf ein bestimmtes Dateiformat abstrahieren. Bevor Sie einen Eigenschaftenhandler für Ihr Dateiformat entwickeln, müssen Sie verstehen, wie Ihr Dateiformat Eigenschaften speichert und wie diese Eigenschaften (Namen und Werte) der Eigenschaftenspeicherabstraktion zugeordnet werden.

Denken Sie bei der Planung Ihrer Implementierung daran, dass Es sich bei Eigenschaftenhandlern um Komponenten auf niedriger Ebene handelt, die im Kontext von Prozessen wie Windows Explorer, dem Windows Search-Indexer und Drittanbieteranwendungen geladen werden, die das Programmiermodell für Shellelemente verwenden. Daher können Eigenschaftshandler nicht in verwaltetem Code implementiert werden und sollten in C++ implementiert werden. Wenn Ihr Handler APIs oder Dienste verwendet, um seine Arbeit zu erledigen, müssen Sie sicherstellen, dass diese Dienste in den Umgebungen, in denen der Eigenschaftenhandler geladen wird, ordnungsgemäß funktionieren können.

Hinweis

Eigenschaftenhandler sind immer bestimmten Dateitypen zugeordnet. Wenn Ihr Dateiformat Eigenschaften enthält, die einen benutzerdefinierten Eigenschaftenhandler erfordern, sollten Sie daher immer eine eindeutige Dateinamenerweiterung für jedes Dateiformat registrieren.

 

Initialisieren von Eigenschaftshandlern

Bevor eine Eigenschaft vom System verwendet wird, wird sie initialisiert, indem eine Implementierung von IInitializeWithStream aufgerufen wird. Der Eigenschaftshandler sollte initialisiert werden, indem das System ihm einen Stream zuweisen muss, anstatt diese Zuweisung der Handlerimplementierung zu überlassen. Diese Initialisierungsmethode stellt Folgendes sicher:

  • Der Eigenschaftenhandler kann in einem eingeschränkten Prozess (ein wichtiges Sicherheitsfeature) ausgeführt werden, ohne über Zugriffsrechte zum direkten Lesen oder Schreiben von Dateien zu verfügen, anstatt über den Stream auf deren Inhalte zuzugreifen.
  • Das System kann vertrauenswürdig sein, um die Datei-Oplocks ordnungsgemäß zu behandeln, was ein wichtiges Zuverlässigkeitsmaß ist.
  • Das Eigenschaftensystem stellt einen automatischen sicheren Speicherdienst ohne zusätzliche Funktionen bereit, die für die Implementierung des Eigenschaftenhandlers erforderlich sind. Weitere Informationen zu Streams finden Sie im Abschnitt Zurückschreiben von Werten .
  • Die Verwendung von IInitializeWithStream abstrahiert Ihre Implementierung von Dateisystemdetails. Dadurch kann der Handler die Initialisierung über alternative Speicher wie einen FTP-Ordner (File Transfer Protocol) oder eine komprimierte Datei mit einer .zip Dateinamenerweiterung unterstützen.

Es gibt Fälle, in denen die Initialisierung mit Streams nicht möglich ist. In diesen Situationen gibt es zwei weitere Schnittstellen, die Eigenschaftenhandler implementieren können: IInitializeWithFile und IInitializeWithItem. Wenn ein Eigenschaftshandler IInitializeWithStream nicht implementiert, muss er die Ausführung in dem isolierten Prozess deaktivieren, in den der Systemindexer ihn standardmäßig platzieren würde, wenn eine Änderung am Stream vorliegt. Um dieses Feature zu deaktivieren, legen Sie den folgenden Registrierungswert fest.

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

Es ist jedoch viel besser, IInitializeWithStream zu implementieren und eine streambasierte Initialisierung durchzuführen. Ihr Eigenschaftenhandler ist dadurch sicherer und zuverlässiger. Die Deaktivierung der Prozessisolation ist in der Regel nur für Legacy-Eigenschaftenhandler vorgesehen und sollte durch neuen Code dringend vermieden werden.

Um die Implementierung eines Eigenschaftenhandlers im Detail zu untersuchen, sehen Sie sich das folgende Codebeispiel an, bei dem es sich um eine Implementierung von IInitializeWithStream::Initialize handelt. Der Handler wird initialisiert, indem ein XML-basiertes Rezeptdokument über einen Zeiger auf die zugeordnete IStream-instance dieses Dokuments geladen wird. Die _spDocEle Variable, die am Ende des Codebeispiels verwendet wird, wird weiter oben im Beispiel als MSXML2::IXMLDOMElementPtr definiert.

Hinweis

Die folgenden und alle nachfolgenden Codebeispiele stammen aus dem Rezepthandlerbeispiel, das im Windows Software Development Kit (SDK) enthalten ist. .

 

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

Â

Nachdem das Dokument selbst geladen wurde, werden die in Windows Explorer anzuzeigenden Eigenschaften geladen, indem die geschützte _LoadProperties-Methode aufgerufen wird, wie im folgenden Codebeispiel veranschaulicht. Dieser Prozess wird im nächsten Abschnitt ausführlich untersucht.

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

Wenn der Stream schreibgeschützt ist, der grfMode-Parameter jedoch das flag STGM_READWRITE enthält, sollte die Initialisierung fehlschlagen und STG_E_ACCESSDENIED zurückgeben. Ohne diese Überprüfung zeigt Windows Explorer die Eigenschaftswerte als schreibbar an, obwohl sie nicht sind, was zu einer verwirrenden Endbenutzererfahrung führt.

Der Eigenschaftenhandler wird nur einmal in seiner Lebensdauer initialisiert. Wenn eine zweite Initialisierung angefordert wird, sollte der Handler zurückgeben HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED).

In-Memory Eigenschaftenspeicher

Bevor Sie sich die Implementierung von _LoadProperties ansehen, sollten Sie das PropertyMap-Array kennen, das im Beispiel verwendet wird, um Eigenschaften im XML-Dokument vorhandenen Eigenschaften im Eigenschaftensystem über ihre PKEY-Werte zuzuordnen.

Sie sollten nicht jedes Element und Attribut in der XML-Datei als Eigenschaft verfügbar machen. Wählen Sie stattdessen nur diejenigen aus, von denen Sie glauben, dass sie für Endbenutzer in der organization ihrer Dokumente nützlich sind (in diesem Fall Rezepte). Dies ist ein wichtiges Konzept, das Sie bei der Entwicklung Ihrer Eigenschaftenhandler berücksichtigen sollten: der Unterschied zwischen Informationen, die für Organisationsszenarien wirklich nützlich sind, und Informationen, die zu den Details Ihrer Datei gehören und durch Öffnen der Datei selbst sichtbar werden können. Eigenschaften sind nicht als vollständige Duplizierung einer XML-Datei vorgesehen.

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

Hier sehen Sie die vollständige Implementierung der _LoadProperties-Methode , die von IInitializeWithStream::Initialize aufgerufen wird.

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

Die _LoadProperties-Methode ruft die Shell-Hilfsfunktion PSCreateMemoryPropertyStore auf, um einen In-Memory-Eigenschaftenspeicher (Cache) für die behandelten Eigenschaften zu erstellen. Mithilfe eines Caches werden Änderungen für Sie nachverfolgt. Dadurch können Sie nicht nachverfolgen, ob ein Eigenschaftswert im Cache geändert, aber noch nicht im persistenten Speicher gespeichert wurde. Außerdem werden Sie von unnötigen Beibehalten von Eigenschaftswerten befreit, die sich nicht geändert haben.

Die _LoadProperties-Methode ruft auch einmal für jede zugeordnete Eigenschaft _LoadProperty auf, dessen Implementierung im folgenden Code veranschaulicht wird. _LoadProperty ruft den Wert der Eigenschaft ab, wie im PropertyMap-Element im XML-Stream angegeben, und weist ihn dem In-Memory-Cache über einen Aufruf von IPropertyStoreCache::SetValueAndState zu. Das PSC_NORMAL-Flag im Aufruf von IPropertyStoreCache::SetValueAndState gibt an, dass der Eigenschaftswert seit dem Zeitpunkt, zu dem er in den Cache gelangt ist, nicht geändert wurde.

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

Umgang mit PROPVARIANT-Based Werten

Bei der Implementierung von _LoadProperty wird ein Eigenschaftswert in Form eines PROPVARIANT bereitgestellt. Eine Reihe von APIs im Software Development Kit (SDK) wird bereitgestellt, um von primitiven Typen wie PWSTR oder in oder von PROPVARIANT-Typen zu konvertieren. Diese APIs befinden sich in Propvarutil.h.

Um beispielsweise eine PROPVARIANT-Datei in eine Zeichenfolge zu konvertieren, können Sie PropVariantToString verwenden, wie hier dargestellt.

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

Um propvariant aus einer Zeichenfolge zu initialisieren, können Sie InitPropVariantFromString verwenden.

InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);

Wie Sie in jeder der im Beispiel enthaltenen Rezeptdateien sehen können, kann es in jeder Datei mehrere Schlüsselwort (keyword) geben. Um dies zu berücksichtigen, unterstützt das Eigenschaftensystem mehrwertige Zeichenfolgen, die als Vektor von Zeichenfolgen dargestellt werden (für instance "VT_VECTOR | VT_LPWSTR"). Die _LoadVectorProperty-Methode im Beispiel verwendet vektorbasierte Werte.

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

Wenn in der Datei kein Wert vorhanden ist, geben Sie keinen Fehler zurück. Legen Sie stattdessen den Wert auf VT_EMPTY fest, und geben Sie S_OK zurück. VT_EMPTY gibt an, dass der Eigenschaftswert nicht vorhanden ist.

Unterstützung von offenen Metadaten

In diesem Beispiel wird ein XML-basiertes Dateiformat verwendet. Das Schema kann erweitert werden, um z. B. Eigenschaften zu unterstützen, die während der Entwicklung nicht gedacht wurden. Dieses System wird als offene Metadaten bezeichnet. In diesem Beispiel wird das Eigenschaftensystem erweitert, indem ein Knoten unter dem Recipe-Element namens ExtendedProperties erstellt wird, wie im folgenden Codebeispiel veranschaulicht.

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

Um persistente erweiterte Eigenschaften während der Initialisierung zu laden, implementieren Sie die _LoadExtendedProperties-Methode , wie im folgenden Codebeispiel veranschaulicht.

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

Serialisierungs-APIs, die in Propsys.h deklariert sind, werden verwendet, um PROPVARIANT-Typen in Datenblobs zu serialisieren und zu deserialisieren. Anschließend wird die Base64-Codierung verwendet, um diese Blobs in Zeichenfolgen zu serialisieren, die im XML-Code gespeichert werden können. Diese Zeichenfolgen werden im EncodedValue-Attribut des ExtendedProperties-Elements gespeichert. Die folgende Hilfsprogrammmethode, die in der Datei Util.cpp des Beispiels implementiert ist, führt die Serialisierung aus. Es beginnt mit einem Aufruf der StgSerializePropVariant-Funktion , um die binäre Serialisierung auszuführen, wie im folgenden Codebeispiel veranschaulicht.

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

Als Nächstes führt die in Wincrypt.h deklarierte CryptBinaryToString-Funktiondie Base64-Konvertierung aus.

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

Die Funktion DeserializePropVariantFromString , die sich ebenfalls in Util.cpp befindet, kehrt den Vorgang um und deserialisiert Werte aus der XML-Datei.

Informationen zur Unterstützung für geöffnete Metadaten finden Sie unter Dateitypen, die offene Metadaten unterstützen.

Full-Text Inhalt

Eigenschaftenhandler können auch eine Volltextsuche des Dateiinhalts erleichtern, und sie sind eine einfache Möglichkeit, diese Funktionalität bereitzustellen, wenn das Dateiformat nicht zu kompliziert ist. Es gibt eine alternative, leistungsfähigere Möglichkeit, den vollständigen Text der Datei über die IFilter-Schnittstellenimplementierung bereitzustellen.

In der folgenden Tabelle sind die Vorteile der einzelnen Ansätze mit IFilter oder IPropertyStore zusammengefasst.

Funktion Ifilter Ipropertystore
Ermöglicht das Zurückschreiben in Dateien? Nein Ja
Bietet eine Mischung aus Inhalt und Eigenschaften? Ja Ja
Mehrsprachige? Ja Nein
MIME/Eingebettet? Ja Nein
Textgrenzen? Satz, Absatz, Kapitel Keine
Wird die Implementierung für SPS/SQL Server unterstützt? Ja Nein
Implementierung Complex Einfach

 

Im Rezepthandlerbeispiel hat das Format der Rezeptdatei keine komplexen Anforderungen, sodass nur IPropertyStore für die Volltextunterstützung implementiert wurde. Die Volltextsuche wird für die XML-Knoten implementiert, die im folgenden Array benannt sind.

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

Das Eigenschaftensystem enthält die System.Search.Contents Eigenschaft (PKEY_Search_Contents), die erstellt wurde, um Volltextinhalte für den Indexer bereitzustellen. Der Wert dieser Eigenschaft wird nie direkt auf der Benutzeroberfläche angezeigt. Der Text aller XML-Knoten, die im obigen Array benannt sind, wird in einer einzelnen Zeichenfolge verkettet. Diese Zeichenfolge wird dann dem Indexer als Volltextinhalt der Rezeptdatei durch einen Aufruf von IPropertyStoreCache::SetValueAndState bereitgestellt, wie im folgenden Codebeispiel veranschaulicht.

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

Bereitstellen von Werten für Eigenschaften

Wenn sie zum Lesen von Werten verwendet werden, werden Eigenschaftenhandler in der Regel aus einem der folgenden Gründe aufgerufen:

  • Zum Aufzählen aller Eigenschaftswerte.
  • So rufen Sie den Wert einer bestimmten Eigenschaft ab.

Bei der Enumeration wird ein Eigenschaftenhandler aufgefordert, seine Eigenschaften entweder während der Indizierung aufzulisten oder wenn das Eigenschaftendialogfeld nach Eigenschaften fragt, die in der Gruppe Andere angezeigt werden sollen. Die Indizierung wird ständig als Hintergrundvorgang ausgeführt. Wenn sich eine Datei ändert, wird der Indexer benachrichtigt, und er indiziert die Datei neu, indem er den Eigenschaftenhandler auffordert, seine Eigenschaften aufzulisten. Daher ist es wichtig, dass Eigenschaftenhandler effizient implementiert werden und Eigenschaftswerte so schnell wie möglich zurückgeben. Enumerieren Sie alle Eigenschaften, für die Sie Werte haben, genau wie bei jeder Sammlung, aber listen Sie keine Eigenschaften auf, die speicherintensive Berechnungen oder Netzwerkanforderungen umfassen, die das Abrufen verlangsamen könnten.

Beim Schreiben eines Eigenschaftenhandlers müssen Sie in der Regel die folgenden beiden Sätze von Eigenschaften berücksichtigen.

  • Primäre Eigenschaften: Eigenschaften, die ihr Dateityp nativ unterstützt. Beispielsweise unterstützt System.Photo.FNumberein Fotoeigenschaftenhandler für EXIF-Metadaten (Exchangeable Image File) nativ .
  • Erweiterte Eigenschaften: Eigenschaften, die ihr Dateityp als Teil von geöffneten Metadaten unterstützt.

Da im Beispiel ein In-Memory-Cache verwendet wird, ist die Implementierung von IPropertyStore-Methoden lediglich eine Frage der Delegierung an diesen Cache, wie im folgenden Codebeispiel veranschaulicht.

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

Wenn Sie sich dafür entscheiden, nicht an den In-Memory-Cache zu delegieren, müssen Sie Ihre Methoden implementieren, um das folgende erwartete Verhalten bereitzustellen> :

  • IPropertyStore::GetCount: Wenn keine Eigenschaften vorhanden sind, gibt diese Methode S_OK zurück.
  • IPropertyStore::GetAt: Wenn iProp größer oder gleich cProps ist, gibt diese Methode E_INVALIDARG zurück, und die Struktur, auf die der pkey-Parameter verweist, wird mit Nullen gefüllt.
  • IPropertyStore::GetCount und IPropertyStore::GetAt spiegeln den aktuellen Zustand des Eigenschaftenhandlers wider. Wenn ein PROPERTYKEY der Datei über IPropertyStore::SetValue hinzugefügt oder daraus entfernt wird, müssen diese beiden Methoden beim nächsten Aufruf diese Änderung widerspiegeln.
  • IPropertyStore::GetValue: Wenn diese Methode nach einem Wert gefragt wird, der nicht vorhanden ist, gibt sie S_OK mit dem als VT_EMPTY gemeldeten Wert zurück.

Zurückschreiben von Werten

Wenn der Eigenschaftenhandler den Wert einer Eigenschaft mithilfe von IPropertyStore::SetValue schreibt, schreibt er den Wert erst in die Datei, wenn IPropertyStore::Commit aufgerufen wird. Der In-Memory-Cache kann bei der Implementierung dieses Schemas hilfreich sein. Im Beispielcode legt die IPropertyStore::SetValue-Implementierung einfach den neuen Wert im In-Memory-Cache und den Zustand dieser Eigenschaft auf PSC_DIRTY fest.

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 jeder IPropertyStore-Implementierung wird das folgende Verhalten von IPropertyStore::SetValue erwartet:

  • Wenn die Eigenschaft bereits vorhanden ist, wird der Wert der -Eigenschaft festgelegt.
  • Wenn die Eigenschaft nicht vorhanden ist, wird die neue Eigenschaft hinzugefügt und ihr Wert festgelegt.
  • Wenn der Eigenschaftswert nicht mit derselben Genauigkeit wie angegeben beibehalten werden kann (bei instance aufgrund von Größenbeschränkungen im Dateiformat abgeschnitten), wird der Wert so weit wie möglich festgelegt und INPLACE_S_TRUNCATED zurückgegeben.
  • Wenn die Eigenschaft vom Eigenschaftenhandler nicht unterstützt wird, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) wird zurückgegeben.
  • Wenn es einen anderen Grund gibt, aus dem der Eigenschaftswert nicht festgelegt werden kann, z. B. die Datei, die gesperrt ist, oder fehlende Rechte zum Bearbeiten über Zugriffssteuerungslisten (Access Control Lists, ACLs), wird STG_E_ACCESSDENIED zurückgegeben.

Ein großer Vorteil der Verwendung von Streams als Beispiel ist die Zuverlässigkeit. Eigenschaftenhandler müssen immer berücksichtigen, dass sie eine Datei im Falle eines schwerwiegenden Fehlers nicht in einem inkonsistenten Zustand belassen können. Die Beschädigung der Dateien eines Benutzers sollte offensichtlich vermieden werden, und der beste Weg, dies zu tun, ist ein "Copy-on-Write"-Mechanismus. Wenn Ihr Eigenschaftenhandler einen Stream für den Zugriff auf eine Datei verwendet, erhalten Sie dieses Verhalten automatisch. Das System schreibt alle Änderungen in den Stream und ersetzt die Datei nur während des Commitvorgangs durch die neue Kopie.

Um dieses Verhalten zu überschreiben und den Dateispeichervorgang manuell zu steuern, können Sie das sichere Speicherverhalten deaktivieren, indem Sie den Wert ManualSafeSave im Registrierungseintrag Ihres Handlers festlegen, wie hier dargestellt.

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

Wenn ein Handler den ManualSafeSave-Wert angibt, ist der Datenstrom, mit dem er initialisiert wird, kein transaktionierter Datenstrom (STGM_TRANSACTED). Der Handler selbst muss die Funktion zum sicheren Speichern implementieren, um sicherzustellen, dass die Datei nicht beschädigt wird, wenn der Speichervorgang unterbrochen wird. Wenn der Handler das direkte Schreiben implementiert, schreibt er in den angegebenen Stream. Wenn der Handler dieses Feature nicht unterstützt, muss er einen Stream abrufen, mit dem die aktualisierte Kopie der Datei mithilfe von IDestinationStreamFactory::GetDestinationStream geschrieben werden soll. Nachdem der Handler mit dem Schreiben fertig ist, sollte er IPropertyStore::Commit für den ursprünglichen Stream aufrufen, um den Vorgang abzuschließen, und den Inhalt des ursprünglichen Datenstroms durch die neue Kopie der Datei ersetzen.

ManualSafeSave ist auch die Standardsituation, wenn Sie Den Handler nicht mit einem Stream initialisieren. Ohne einen ursprünglichen Datenstrom, der den Inhalt des temporären Datenstroms empfängt, müssen Sie ReplaceFile verwenden, um eine atomische Ersetzung der Quelldatei durchzuführen.

Große Dateiformate, die so verwendet werden, dass Dateien mit mehr als 1 MB erzeugt werden, sollten Unterstützung für das direkte Schreiben von Eigenschaften implementieren. Andernfalls entspricht das Leistungsverhalten nicht den Erwartungen der Clients des Eigenschaftensystems. In diesem Szenario sollte die zum Schreiben von Eigenschaften erforderliche Zeit von der Dateigröße nicht beeinflusst werden.

Für sehr große Dateien, z. B. eine Videodatei mit 1 GB oder mehr, ist eine andere Lösung erforderlich. Wenn in der Datei nicht genügend Speicherplatz für direkte Schreibvorgänge vorhanden ist, schlägt der Handler die Aktualisierung der Eigenschaft möglicherweise fehl, wenn der für das direkte Schreiben von Eigenschaften reservierte Speicherplatz erschöpft ist. Dieser Fehler tritt auf, um eine schlechte Leistung zu vermeiden, die sich aus 2 GB E/A ergibt (1 zum Lesen, 1 zum Schreiben). Aufgrund dieses potenziellen Fehlers sollten diese Dateiformate genügend Speicherplatz für das direkte Schreiben von Eigenschaften reservieren.

Wenn die Datei über genügend Speicherplatz zum Schreiben von Metadaten verfügt und das Schreiben dieser Metadaten nicht dazu führt, dass die Datei vergrößert oder verkleinert wird, ist es möglicherweise sicher, dass sie an Ort und Stelle geschrieben wird. Wir empfehlen 64 KB als Ausgangspunkt. Das direkte Schreiben entspricht dem Handler, der nach ManualSafeSave und aufrufen von IStream::Commit in der Implementierung von IPropertyStore::Commit fragt, und hat eine viel bessere Leistung als copy-on-write. Wenn sich die Dateigröße aufgrund von Eigenschaftswertänderungen ändert, sollte das direkte Schreiben nicht versucht werden, da eine beschädigte Datei im Falle einer ungewöhnlichen Beendigung möglicherweise beschädigt ist.

Hinweis

Aus Leistungsgründen wird empfohlen, die Option ManualSafeSave mit Eigenschaftenhandlern zu verwenden, die mit Dateien arbeiten, die mindestens 100 KB umfassen.

 

Wie in der folgenden Beispielimplementierung von IPropertyStore::Commit gezeigt, wird der Handler für ManualSafeSave registriert, um die Option zum manuellen sicheren Speichern zu veranschaulichen. Die _SaveCacheToDom-Methode schreibt die im In-Memory-Cache gespeicherten Eigenschaftswerte in das XMLdocument-Objekt.

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

Fragen Sie als Nächstes, ob die angegebene IDestinationStreamFactory unterstützt.

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

Als Nächstes committen Sie den ursprünglichen Datenstrom, der die Daten auf sichere Weise zurück in die ursprüngliche Datei schreibt.

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

                            pStreamCommit->Release();
                        }

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

Überprüfen Sie dann die implementierung der _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;  

Rufen Sie als Nächstes die Anzahl der eigenschaften ab, die im In-Memory-Cache gespeichert sind.

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

Durchlaufen Sie nun die Eigenschaften, um zu bestimmen, ob der Wert einer Eigenschaft geändert wurde, seit sie in den Arbeitsspeicher geladen wurde.

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

Die IPropertyStoreCache::GetState-Methode ruft den Status der Eigenschaft im Cache ab. Das PSC_DIRTY-Flag, das in der IPropertyStore::SetValue-Implementierung festgelegt wurde, markiert eine Eigenschaft als geändert.

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

Ordnen Sie die -Eigenschaft dem XML-Knoten zu, wie im eg_rgPropertyMap-Array angegeben.

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

Wenn sich eine Eigenschaft nicht in der Zuordnung befindet, handelt es sich um eine neue Eigenschaft, die von Windows Explorer festgelegt wurde. Da offene Metadaten unterstützt werden, speichern Sie die neue Eigenschaft im Abschnitt ExtendedProperties des XML.

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

                    PropVariantClear(&propvar);
                }
            }
        }
    }

    return hr;    

Implementieren von IPropertyStoreCapabilities

IPropertyStoreCapabilities informiert die Shell-Benutzeroberfläche darüber, ob eine bestimmte Eigenschaft auf der Shell-Benutzeroberfläche bearbeitet werden kann. Es ist wichtig zu beachten, dass sich dies nur auf die Möglichkeit bezieht, die Eigenschaft auf der Benutzeroberfläche zu bearbeiten, und nicht, ob Sie IPropertyStore::SetValue für die -Eigenschaft erfolgreich aufrufen können. Eine Eigenschaft, die einen Rückgabewert von S_FALSE von IPropertyStoreCapabilities::IsPropertyWritable provoziert, kann möglicherweise noch über eine Anwendung festgelegt werden.

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

IsPropertyWritable gibt S_OK zurück, um anzugeben, dass Endbenutzer die Eigenschaft direkt bearbeiten dürfen. S_FALSE gibt an, dass sie es nicht sollten. S_FALSE kann bedeuten, dass Anwendungen für das Schreiben der Eigenschaft und nicht für Benutzer verantwortlich sind. Die Shell deaktiviert die Bearbeitungssteuerelemente entsprechend den Ergebnissen der Aufrufe dieser Methode. Es wird davon ausgegangen, dass ein Handler, der IPropertyStoreCapabilities nicht implementiert, offene Metadaten durch Unterstützung für das Schreiben einer beliebigen Eigenschaft unterstützt.

Wenn Sie einen Handler erstellen, der nur schreibgeschützte Eigenschaften behandelt, sollten Sie Ihre Initialize-Methode (IInitializeWithStream, IInitializeWithItem oder IInitializeWithFile) so implementieren, dass sie STG_E_ACCESSDENIED zurückgibt, wenn sie mit dem flag STGM_READWRITE aufgerufen wird.

Bei einigen Eigenschaften ist das attribut isInnate auf true festgelegt. Angeborene Eigenschaften weisen die folgenden Merkmale auf:

  • Die Eigenschaft wird normalerweise in irgendeiner Weise berechnet. Für instance System.Image.BitDepth wird aus dem Bild selbst berechnet.
  • Das Ändern der Eigenschaft wäre nicht sinnvoll, ohne die Datei zu ändern. Für instance würde das Ändern System.Image.Dimensions der Größe des Bilds nicht geändert, sodass es nicht sinnvoll ist, dem Benutzer zu erlauben, es zu ändern.
  • In einigen Fällen werden diese Eigenschaften automatisch vom System bereitgestellt. Beispiele sind System.DateModified, die vom Dateisystem bereitgestellt werden, und System.SharedWith, die darauf basieren, für wen der Benutzer die Datei freigibt.

Aufgrund dieser Merkmale werden als IsInnate gekennzeichnete Eigenschaften dem Benutzer auf der Shell-Benutzeroberfläche nur als schreibgeschützte Eigenschaften bereitgestellt. Wenn eine Eigenschaft als IsInnate gekennzeichnet ist, speichert das Eigenschaftensystem diese Eigenschaft nicht im Eigenschaftenhandler. Daher benötigen Eigenschaftshandler keinen speziellen Code, um diese Eigenschaften in ihren Implementierungen zu berücksichtigen. Wenn der Wert des IsInnate-Attributs für eine bestimmte Eigenschaft nicht explizit angegeben wird, ist der Standardwert false.

Registrieren und Verteilen von Eigenschaftenhandlern

Wenn der Eigenschaftenhandler implementiert ist, muss er registriert werden und seine Dateinamenerweiterung dem Handler zugeordnet sein. Weitere Informationen finden Sie unter Registrieren und Verteilen von Eigenschaftenhandlern.

Grundlegendes zu Eigenschaftenhandlern

Verwenden von Kindnamen

Verwenden von Eigenschaftenlisten

Registrieren und Verteilen von Eigenschaftenhandlern

Bewährte Methoden und häufig gestellte Fragen zu Eigenschaftenhandlern