プロパティ ハンドラーの初期化

このトピックでは、Windows プロパティ システムを操作するためのプロパティ ハンドラーを作成および登録する方法について説明します。

このトピックは次のように整理されています。

プロパティ ハンドラー

プロパティ ハンドラーは、プロパティ システムの重要な部分です。 これらはインデクサーによってインプロセスで呼び出され、プロパティ値の読み取りとインデックス付けが行われます。また、ファイル内のプロパティ値を直接読み取りおよび書き込むためのインプロセスエクスプローラー Windows によって呼び出されます。 これらのハンドラーは、パフォーマンスの低下や影響を受けるファイル内のデータの損失を防ぐために、慎重に記述およびテストする必要があります。 プロパティ ハンドラーの実装に影響を与えるインデクサー固有の考慮事項の詳細については、「 Windows Search 用のプロパティ ハンドラーの開発」を参照してください。

このトピックでは、.recipe ファイル名拡張子を持つレシピを記述するサンプル XML ベースのファイル形式について説明します。 .recipe ファイル名拡張子は、セカンダリ ストリームを使用してプロパティを格納する、より汎用的な.xml ファイル形式に依存するのではなく、独自の個別のファイル形式として登録されます。 ファイルの種類に合わせて一意のファイル名拡張子を登録することをお勧めします。

はじめに

プロパティ ハンドラーは、特定のファイル形式の IPropertyStore 抽象化を作成する COM オブジェクトです。 このファイル形式は、その仕様に準拠した方法で読み取り (解析) され、書き込まれます。 一部のプロパティ ハンドラーは、特定のファイル形式へのアクセスを抽象化する API に基づいて作業を行います。 ファイル形式のプロパティ ハンドラーを開発する前に、ファイル形式でプロパティを格納する方法と、それらのプロパティ (名前と値) がプロパティ ストアの抽象化にどのようにマップされるかを理解する必要があります。

実装を計画するときは、プロパティ ハンドラーは、Windows エクスプローラー、Windows Search インデクサー、シェル項目プログラミング モデルを使用するサードパーティ製アプリケーションなどのプロセスのコンテキストで読み込まれる低レベルのコンポーネントであることを覚えておいてください。 その結果、プロパティ ハンドラーはマネージド コードに実装できず、C++ で実装する必要があります。 ハンドラーで API またはサービスを使用して作業を行う場合は、プロパティ ハンドラーが読み込まれる環境でそれらのサービスが正しく機能することを確認する必要があります。

注意

プロパティ ハンドラーは常に特定のファイルの種類に関連付けられます。したがって、ファイル形式にカスタム プロパティ ハンドラーを必要とするプロパティが含まれている場合は、常にファイル形式ごとに一意のファイル名拡張子を登録する必要があります。

 

プロパティ ハンドラーの初期化

プロパティは、システムで使用される前に、 IInitializeWithStream の実装を呼び出すことによって初期化されます。 プロパティ ハンドラーは、その割り当てをハンドラーの実装に任せるのではなく、システムにストリームを割り当てることによって初期化する必要があります。 この初期化メソッドを使用すると、次のことが保証されます。

  • プロパティ ハンドラーは、ファイルを直接読み取ったり書き込んだりするアクセス権を持たずに、ストリームを介してコンテンツにアクセスすることなく、制限されたプロセス (重要なセキュリティ機能) で実行できます。
  • システムは、ファイル oplocks を正しく処理するために信頼でき、これは重要な信頼性の尺度です。
  • プロパティ システムは、プロパティ ハンドラーの実装に必要な追加機能なしで、自動安全な保存サービスを提供します。 ストリームの詳細については、「 戻る値の書き込み 」セクションを参照してください。
  • IInitializeWithStream を使用すると、ファイル システムの詳細から実装が抽象化されます。 これにより、ハンドラーは、ファイル転送プロトコル (FTP) フォルダーや.zipファイル名拡張子を持つ圧縮ファイルなどの代替ストレージを使用した初期化をサポートできます。

ストリームを使用した初期化ができない場合があります。 このような状況では、プロパティ ハンドラーが実装できるインターフェイスが 2 つあります。 IInitializeWithFileIInitializeWithItem です。 プロパティ ハンドラーが IInitializeWithStream を実装していない場合は、ストリームに変更があった場合にシステム インデクサーによって既定で配置される分離プロセスでの実行をオプトアウトする必要があります。 この機能をオプトアウトするには、次のレジストリ値を設定します。

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

ただし、 IInitializeWithStream を実装し、ストリームベースの初期化を行う方がはるかに優れています。 結果として、プロパティ ハンドラーの方が安全で信頼性が高くなります。 プロセス分離の無効化は、通常、レガシ プロパティ ハンドラーのみを対象としており、新しいコードでは避ける必要があります。

プロパティ ハンドラーの実装を詳しく調べるには、 IInitializeWithStream::Initialize の実装である次のコード例を参照してください。 ハンドラーは、そのドキュメントに関連付けられている IStream インスタンスへのポインターを介して XML ベースのレシピ ドキュメントを読み込むことで初期化されます。 コード例の末尾付近で使用される _spDocEle 変数は、サンプルの前半で MSXML2::IXMLDOMElementPtr として定義されています。

注意

以降のすべてのコード例は、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;
                }

Â

ドキュメント自体が読み込まれた後、次のコード例に示すように、保護された_LoadProperties メソッドを呼び出すことによって、Windows エクスプローラーに表示されるプロパティが読み込まれます。 このプロセスについては、次のセクションで詳しく説明します。

                if (_spDocEle)
                {
                    hr = _LoadProperties();
    
                    if (SUCCEEDED(hr))
                    {
                        _spStream = pStream;
                    }
                }
                else
                {
                    hr = E_FAIL;  // parse error
                }
            }
        }
        else
        {
            hr = E_UNEXPECTED;
        }
    }
    
    catch (_com_error &e)
    {
        hr = e.Error();
    }
    
    return hr;
}

ストリームが読み取り専用で、 grfMode パラメーターにSTGM_READWRITE フラグが含まれている場合、初期化は失敗し、STG_E_ACCESSDENIEDを返す必要があります。 このチェックがないと、Windows エクスプローラーでは、プロパティ値が書き込み可能でない場合でも書き込み可能として表示され、エンド ユーザー エクスペリエンスが混乱します。

プロパティ ハンドラーは、有効期間内に 1 回だけ初期化されます。 2 つ目の初期化が要求された場合、ハンドラーは を返す HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED)必要があります。

In-Memory プロパティ ストア

_LoadPropertiesの実装を見る前に、PKEY 値を使用して XML ドキュメント内のプロパティをプロパティ システム内の既存のプロパティにマップするためにサンプルで使用される PropertyMap 配列を理解しておく必要があります。

XML ファイル内のすべての要素と属性をプロパティとして公開しないでください。 代わりに、ドキュメントのorganization (この場合はレシピ) でエンド ユーザーに役立つと思われるもののみを選択します。 これは、プロパティ ハンドラーを開発する際に留意すべき重要な概念です。組織のシナリオに本当に役立つ情報と、ファイルの詳細に属し、ファイル自体を開くことで確認できる情報の違い。 プロパティは、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 },
};

IInitializeWithStream::Initialize によって呼び出される_LoadProperties メソッドの完全な実装を次に示します。

HRESULT CRecipePropertyStore::_LoadProperties()
{
    HRESULT hr = E_FAIL;    
    
    if (_spCache)
    {
        hr = <mark type="const">S_OK</mark>;
    }
    else
    {
        // Create the in-memory property store.
        hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&_spCache));
    
        if (SUCCEEDED(hr))
        {
            // Cycle through each mapped property.
            for (UINT i = 0; i < ARRAYSIZE(c_rgPropertyMap); ++i)
            {
                _LoadProperty(c_rgPropertyMap[i]);
            }
    
            _LoadExtendedProperties();
            _LoadSearchContent();
        }
    }
    return hr;
}

_LoadProperties メソッドは、シェル ヘルパー関数 PSCreateMemoryPropertyStore を呼び出して、処理されたプロパティのメモリ内プロパティ ストア (キャッシュ) を作成します。 キャッシュを使用すると、変更が追跡されます。 これにより、プロパティ値がキャッシュ内で変更されたが、永続化されたストレージにまだ保存されていないかどうかを追跡できなくなります。 また、変更されていないプロパティ値を不必要に永続化することもできなくなります。

_LoadProperties メソッドは、マップされたプロパティごとに 1 回、実装が次のコードに示されている_LoadPropertyも呼び出します。 _LoadProperty XML ストリームの PropertyMap 要素で指定されたプロパティの値を取得し、 IPropertyStoreCache::SetValueAndState を呼び出してメモリ内キャッシュに割り当てます。 IPropertyStoreCache::SetValueAndState の呼び出しのPSC_NORMAL フラグは、プロパティ値がキャッシュに入ってから変更されていないことを示します。

HRESULT CRecipePropertyStore::_LoadProperty(PropertyMap &map)
{
    HRESULT hr = S_FALSE;
    
    MSXML2::IXMLDOMNodePtr spXmlNode(_spDomDoc->selectSingleNode(map.pszXPath));
    if (spXmlNode)
    {
        PROPVARIANT propvar = { 0 };
        propvar.vt = map.vt;
        
        if (map.vt == (VT_VECTOR | VT_LPWSTR))
        {
            hr = _LoadVectorProperty(spXmlNode, &propvar, map);
        }
        else
        {
            // If there is no value, set to VT_EMPTY to indicate
            // that it is not there. Do not return failure.
            if (spXmlNode->text.length() == 0)
            {
                propvar.vt = VT_EMPTY;
                hr = <mark type="const">S_OK</mark>;
            }
            else
            {
                // SimplePropVariantFromString is a helper function.
                // particular to the sample. It is found in Util.cpp.
                hr = SimplePropVariantFromString(spXmlNode->text, &propvar);
            }
        }
    
        if (S_OK == hr)
        {
            hr = _spCache->SetValueAndState(map.key, &propvar, PSC_NORMAL);
            PropVariantClear(&propvar);
        }
    }
    return hr;
}

PROPVARIANT-Based値の処理

_LoadPropertyの実装では、プロパティ値が PROPVARIANT の形式で提供されます。 ソフトウェア開発キット (SDK) の API のセットは、 PWSTRint などのプリミティブ型から PROPVARIANT 型への変換または PROPVARIANT 型からの変換を行うために提供されます。 これらの API は Propvarutil.h にあります。

たとえば、 PROPVARIANT を文字列に変換するには、次に示すように PropVariantToString を 使用できます。

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

文字列から PROPVARIANT を初期化するには、 InitPropVariantFromString を使用します。

InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);

サンプルに含まれているレシピ ファイルでわかるように、各ファイルには複数のキーワード (keyword)があります。 これを考慮するために、プロパティ システムは文字列のベクトルとして表される複数値の文字列をサポートします (例: "VT_VECTOR |VT_LPWSTR")。 この例 の _LoadVectorProperty メソッドでは、ベクターベースの値を使用しています。

HRESULT CRecipePropertyStore::_LoadVectorProperty
                                (MSXML2::IXMLDOMNode *pNodeParent,
                                 PROPVARIANT *ppropvar,
                                 struct PropertyMap &map)
{
    HRESULT hr = S_FALSE;
    MSXML2::IXMLDOMNodeListPtr spList = pNodeParent->selectNodes(map.pszSubNodeName);
    
    if (spList)
    {
        UINT cElems = spList->length;
        ppropvar->calpwstr.cElems = cElems;
        ppropvar->calpwstr.pElems = (PWSTR*)CoTaskMemAlloc(sizeof(PWSTR)*cElems);
    
        if (ppropvar->calpwstr.pElems)
        {
            for (UINT i = 0; (SUCCEEDED(hr) && i < cElems); ++i)
            {
                hr = SHStrDup(spList->item[i]->text, 
                              &(ppropvar->calpwstr.pElems[i]));
            }
    
            if (SUCCEEDED(hr))
            {
                if (!IsEqualPropertyKey(map.keyCount, PKEY_Null))
                {
                    PROPVARIANT propvarCount = { VT_UI4 };
                    propvarCount.uintVal = cElems;
                    
                    _spCache->SetValueAndState(map.keyCount,
                                               &propvarCount, 
                                               PSC_NORMAL);
                }
            }
            else
            {
                PropVariantClear(ppropvar);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    
    return hr;
}

ファイルに値が存在しない場合は、エラーを返さないでください。 代わりに、値を VT_EMPTY に設定し、 S_OKを返します。 VT_EMPTYプロパティ値が存在しないことを示します。

オープン メタデータのサポート

この例では、XML ベースのファイル形式を使用します。 そのスキーマは、たとえば、開発時に考えられていたプロパティをサポートするように拡張できます。 このシステムは、オープン メタデータと呼ばれます。 この例では、次のコード例に示すように、ExtendedProperties という名前の Recipe 要素の下にノードを作成することで、プロパティ システムを拡張します。

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

初期化中に永続化された拡張プロパティを読み込むには、次のコード例に示すように、 _LoadExtendedProperties メソッドを実装します。

HRESULT CRecipePropertyStore::_LoadExtendedProperties()
{
    HRESULT hr = S_FALSE;
    MSXML2::IXMLDOMNodeListPtr spList = 
                  _spDomDoc->selectNodes(L"Recipe/ExtendedProperties/Property");
    
    if (spList)
    {
        UINT cElems = spList->length;
        
        for (UINT i = 0; i < cElems; ++i)
        {
            MSXML2::IXMLDOMElementPtr spElement;
            
            if (SUCCEEDED(spList->item[i]->QueryInterface(IID_PPV_ARGS(&spElement))))
            {
                PROPERTYKEY key;
                _bstr_t bstrPropName = spElement->getAttribute(L"Name").bstrVal;
    
                if (!!bstrPropName &&
                    (SUCCEEDED(PropertyKeyFromString(bstrPropName, &key))))
                {
                    PROPVARIANT propvar = { 0 };
                    _bstr_t bstrEncodedValue = 
                               spElement->getAttribute(L"EncodedValue").bstrVal;
                   
                    if (!!bstrEncodedValue)
                    {
                        // DeserializePropVariantFromString is a helper function
                        // particular to the sample. It is found in Util.cpp.
                        hr = DeserializePropVariantFromString(bstrEncodedValue, 
                                                              &propvar);
                    }

Propsys.h で宣言されたシリアル化 API は、 PROPVARIANT 型をデータの BLOB にシリアル化および逆シリアル化するために使用され、Base64 エンコードを使用して、それらの BLOB を XML に保存できる文字列にシリアル化します。 これらの文字列は、ExtendedProperties 要素の EncodedValue 属性に格納されます。 サンプルの Util.cpp ファイルに実装されている次のユーティリティ メソッドは、シリアル化を実行します。 次のコード例に示すように、 StgSerializePropVariant 関数を呼び出してバイナリ シリアル化を実行します。

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

次に、Wincrypt.h で宣言された CryptBinaryToString 関数は 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>;}

Util.cpp にも含まれる DeserializePropVariantFromString 関数は、XML ファイルから値を逆シリアル化して、操作を逆シリアル化します。

開いているメタデータのサポートについては、「ファイルの種類」の「Open Metadata をサポートする ファイルの種類」を参照してください。

Full-Textコンテンツ

プロパティ ハンドラーは、ファイルの内容のフルテキスト検索を容易にすることもできます。ファイル形式が過度に複雑でない場合は、その機能を簡単に提供できます。 IFilter インターフェイスの実装を通じてファイルのフルテキストを提供する、より強力な代替方法があります。

次の表は、 IFilter または IPropertyStore を使用する各アプローチの利点をまとめたものです。

機能 Ifilter IPropertyStore
ファイルへの書き戻しを許可しますか? いいえ はい
コンテンツとプロパティの組み合わせを提供しますか? はい はい
多 言語。 はい いいえ
MIME/Embedded? はい いいえ
テキストの境界 文、段落、章 なし
SPS/SQL Server でサポートされる実装 はい いいえ
実装 Complex シンプル

 

レシピ ハンドラーのサンプルでは、レシピ ファイル形式には複雑な要件がないため、フルテキスト サポート用に IPropertyStore のみが実装されています。 フルテキスト検索は、次の配列で という名前の XML ノードに対して実装されます。

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

プロパティ システムには、インデクサーに System.Search.Contents フルテキスト コンテンツを提供するために作成された (PKEY_Search_Contents) プロパティが含まれています。 このプロパティの値が UI に直接表示されることはありません。上記の配列で指定されたすべての XML ノードのテキストは、1 つの文字列に連結されます。 その文字列は、次のコード例に示すように 、IPropertyStoreCache::SetValueAndState の呼び出しを通じてレシピ ファイルのフルテキスト コンテンツとしてインデクサーに提供されます。

HRESULT CRecipePropertyStore::_LoadSearchContent()
{
    HRESULT hr = S_FALSE;
    _bstr_t bstrContent;
    
    for (UINT i = 0; i < ARRAYSIZE(c_rgszContentXPath); ++i)
    {
        MSXML2::IXMLDOMNodeListPtr spList = 
                                  _spDomDoc->selectNodes(c_rgszContentXPath[i]);
    
        if (spList)
        {
            UINT cElems = spList->length;
            
            for (UINT elt = 0; elt < cElems; ++elt)
            {
                bstrContent += L" ";
                bstrContent += spList->item[elt]->text;
            }
        }
    }
    
    if (bstrContent.length() > 0)
    {
        PROPVARIANT propvar = { VT_LPWSTR };
        hr = SHStrDup(bstrContent, &(propvar.pwszVal));
    
        if (SUCCEEDED(hr))
        {
            hr = _spCache->SetValueAndState(PKEY_Search_Contents, 
                                            &propvar, 
                                            PSC_NORMAL);
            PropVariantClear(&propvar);
        }
    }
    
    return hr;}

プロパティの値の指定

値の読み取りに使用する場合、プロパティ ハンドラーは通常、次のいずれかの理由で呼び出されます。

  • すべてのプロパティ値を列挙します。
  • 特定のプロパティの値を取得します。

列挙の場合、プロパティ ハンドラーは、インデックス作成中、またはプロパティ ダイアログ ボックスが [ その他 ] グループに表示するプロパティを求める場合に、そのプロパティを列挙するように求められます。 インデックス作成は、バックグラウンド操作として常に実行されます。 ファイルが変更されるたびに、インデクサーに通知され、プロパティ ハンドラーにプロパティの列挙を求めることで、ファイルのインデックスが再作成されます。 そのため、プロパティ ハンドラーを効率的に実装し、可能な限り迅速にプロパティ値を返す必要があります。 コレクションの場合と同様に、値を持つすべてのプロパティを列挙しますが、メモリ消費量の多い計算やネットワーク要求を含むプロパティを列挙しないでください。これにより、取得に時間がかかる可能性があります。

プロパティ ハンドラーを記述するときは、通常、次の 2 つのプロパティ セットを考慮する必要があります。

  • プライマリ プロパティ: ファイルの種類がネイティブにサポートするプロパティ。 たとえば、Exchangeable Image File (EXIF) メタデータの写真プロパティ ハンドラーでは、 がネイティブにサポートされています System.Photo.FNumber
  • 拡張プロパティ: 開いているメタデータの一部としてファイルの種類がサポートするプロパティ。

このサンプルではメモリ内キャッシュを使用するため、 IPropertyStore メソッドの実装は、次のコード例に示すように、そのキャッシュに委任するだけで済みます。

IFACEMETHODIMP GetCount(__out DWORD *pcProps)
{ return _spCache->GetCount(pcProps); }

IFACEMETHODIMP GetAt(DWORD iProp, __out PROPERTYKEY *pkey)
{ return _spCache->GetAt(iProp, pkey); }

IFACEMETHODIMP GetValue(REFPROPERTYKEY key, __out PROPVARIANT *pPropVar)
{ return _spCache->GetValue(key, pPropVar); }

メモリ内キャッシュに委任しない場合は、メソッドを実装して、次の予期される動作を提供> する必要があります。

  • IPropertyStore::GetCount: プロパティがない場合、このメソッドは S_OKを返します。
  • IPropertyStore::GetAt: iProp が cProps 以上の場合、このメソッドは E_INVALIDARGを返し、pkey パラメーターが指す構造体はゼロで埋められます。
  • IPropertyStore::GetCountIPropertyStore::GetAt には 、プロパティ ハンドラーの現在の状態が反映されます。 PROPERTYKEYIPropertyStore::SetValue を使用してファイルに追加または削除された場合、これら 2 つのメソッドは、次回呼び出されるときにその変更を反映する必要があります。
  • IPropertyStore::GetValue: このメソッドに存在しない値を求められた場合は、VT_EMPTYとして報告された値を含む S_OK が返されます。

値の書き戻し

プロパティ ハンドラーは、 IPropertyStore::SetValue を使用してプロパティの値を書き込む場合、 IPropertyStore::Commit が呼び出されるまで値をファイルに書き込むことはありません。 メモリ内キャッシュは、このスキームを実装する場合に役立ちます。 サンプル コードの IPropertyStore::SetValue 実装では、メモリ内キャッシュの新しい値を設定し、そのプロパティの状態を PSC_DIRTY に設定するだけです。

HRESULT CRecipePropertyStore::SetValue(REFPROPERTYKEY key, const PROPVARIANT *pPropVar)
    {
    
    HRESULT hr = E_FAIL;
    
    if (IsEqualPropertyKey(key, PKEY_Search_Contents))
    {
        // This property is read-only
        hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);  
    }
    else
    {
        hr = _spCache->SetValueAndState(key, pPropVar, PSC_DIRTY);
    }
    
    return hr;
}

IPropertyStore の実装では、IPropertyStore::SetValue から次の動作が想定されます。

  • プロパティが既に存在する場合は、プロパティの値が設定されます。
  • プロパティが存在しない場合は、新しいプロパティとその値セットが追加されます。
  • プロパティ値を指定したのと同じ精度で保持できない場合 (たとえば、ファイル形式のサイズ制限による切り捨て)、値は可能な限り設定され、INPLACE_S_TRUNCATEDが返されます。
  • プロパティがプロパティ ハンドラーでサポートされていない場合は、 HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) が返されます。
  • ファイルがロックされている、アクセス制御リスト (ACL) を使用して編集する権限がないなど、プロパティ値を設定できない別の理由がある場合は、STG_E_ACCESSDENIEDが返されます。

サンプルとしてストリームを使用する主な利点の 1 つは、信頼性です。 プロパティ ハンドラーは、致命的な障害が発生した場合にファイルを不整合な状態にしておくことはできないと常に考慮する必要があります。 ユーザーのファイルの破損は明らかに避ける必要があります。これを行う最善の方法は、"書き込み時のコピー" メカニズムを使用することです。 プロパティ ハンドラーがストリームを使用してファイルにアクセスする場合、この動作は自動的に取得されます。システムはストリームに変更を書き込み、コミット操作中にのみファイルを新しいコピーに置き換えます。

この動作をオーバーライドし、ファイルの保存プロセスを手動で制御するには、次に示すように、ハンドラーのレジストリ エントリに ManualSafeSave 値を設定することで、安全な保存動作をオプトアウトできます。

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

ハンドラーが ManualSafeSave 値を指定する場合、初期化に使用されるストリームはトランザクション ストリーム (STGM_TRANSACTED) ではありません。 ハンドラー自体は、保存操作が中断された場合にファイルが破損しないように、安全な保存関数を実装する必要があります。 ハンドラーがインプレース書き込みを実装する場合は、指定されたストリームに書き込みます。 ハンドラーがこの機能をサポートしていない場合は、 IDestinationStreamFactory::GetDestinationStream を使用してファイルの更新されたコピーを書き込むストリームを取得する必要があります。 ハンドラーの書き込みが完了したら、元のストリームで IPropertyStore::Commit を呼び出して操作を完了し、元のストリームの内容をファイルの新しいコピーに置き換える必要があります。

また、ストリームを使用してハンドラーを初期化しない場合は、ManualSafeSave も既定の状況です。 元のストリームが一時ストリームの内容を受信しない場合は、 ReplaceFile を使用してソース ファイルのアトミック置換を実行する必要があります。

1 MB を超えるファイルを生成する方法で使用される大きなファイル形式では、インプレース プロパティの書き込みのサポートを実装する必要があります。それ以外の場合、パフォーマンス動作はプロパティ システムのクライアントの期待を満たしません。 このシナリオでは、プロパティの書き込みに必要な時間がファイル サイズの影響を受けないようにする必要があります。

1 GB 以上のビデオ ファイルなど、非常に大きなファイルの場合は、別のソリューションが必要です。 ファイルにインプレース書き込みを実行するための十分な領域がない場合、インプレース プロパティの書き込みに予約されている領域の量が不足している場合、ハンドラーはプロパティの更新に失敗する可能性があります。 このエラーは、2 GB の IO (1 から読み取り、1 を書き込む) によって発生するパフォーマンス低下を回避するために発生します。 このエラーの可能性があるため、これらのファイル形式では、インプレース プロパティの書き込みに十分な領域を確保する必要があります。

ファイルのヘッダーにメタデータを書き込むのに十分な領域があり、そのメタデータの書き込みによってファイルが拡大または縮小されない場合は、インプレースで書き込んでも問題ない可能性があります。 開始点として 64 KB をお勧めします。 インプレース書き込みは、ManualSafeSave を要求し、IPropertyStore::Commit の実装で IStream::Commit を呼び出すハンドラーと同等であり、書き込み時のコピーよりもはるかに優れたパフォーマンスを持ちます。 プロパティ値の変更によってファイル サイズが変更された場合は、異常終了した場合にファイルが破損する可能性があるため、インプレース書き込みを試行しないでください。

注意

パフォーマンス上の理由から、ManualSafeSave オプションは、100 KB 以上のファイルを操作するプロパティ ハンドラーで使用することをお勧めします。

 

IPropertyStore::Commit の次の実装例に示すように、ManualSafeSave のハンドラーは、手動の安全な保存オプションを示すために登録されています。 _SaveCacheToDom メソッドは、メモリ内キャッシュに格納されているプロパティ値を XMLdocument オブジェクトに書き込みます。

HRESULT CRecipePropertyStore::Commit()
{
    HRESULT hr = E_UNEXPECTED;
    if (_pCache)
    {
        // Check grfMode to ensure writes are allowed.
        hr = STG_E_ACCESSDENIED;
        if (_grfMode & STGM_READWRITE)
        {
            // Save the internal value cache to XML DOM object.
            hr = _SaveCacheToDom();
            if (SUCCEEDED(hr))
            {
                // Reset the output stream.
                LARGE_INTEGER liZero = {};
                hr = _pStream->Seek(liZero, STREAM_SEEK_SET, NULL);

次に、pecified で IDestinationStreamFactory がサポートされているかどうかを確認します。

                        if (SUCCEEDED(hr))
                        {
                            // Write the XML out to the temprorary stream and commit it.
                            VARIANT varStream = {};
                            varStream.vt = VT_UNKNOWN;
                            varStream.punkVal = pStreamCommit;
                            hr = _pDomDoc->save(varStream);
                            if (SUCCEEDED(hr))
                            {
                                hr = pStreamCommit->Commit(STGC_DEFAULT);_

次に、元のストリームをコミットします。これにより、データが安全な方法で元のファイルに書き戻されます。

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

                            pStreamCommit->Release();
                        }

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

次に、 _SaveCacheToDom 実装を調べます。

// Saves the values in the internal cache back to the internal DOM object.
HRESULT CRecipePropertyStore::_SaveCacheToDom()
{
    // Iterate over each property in the internal value cache.
    DWORD cProps;  

次に、メモリ内キャッシュに格納されているプロパティの数を取得します。

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

次に、プロパティを反復処理して、プロパティの値がメモリに読み込まれた後に変更されたかどうかを判断します。

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

IPropertyStoreCache::GetState メソッドは、キャッシュ内のプロパティの状態を取得します。 IPropertyStore::SetValue 実装で設定された PSC_DIRTY フラグは、プロパティを変更としてマークします。

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

プロパティを、 eg_rgPropertyMap 配列で指定されている XML ノードにマップします。

if (SUCCEEDED(hr))
                {
                    // save as a native property if the key is in the property map
                    BOOL fIsNativeProperty = FALSE;
                    for (UINT i = 0; i < ARRAYSIZE(g_rgPROPERTYMAP); ++i)
                    {
                        if (IsEqualPropertyKey(key, *g_rgPROPERTYMAP[i].pkey))
                        {
                            fIsNativeProperty = TRUE;
                            hr = _SaveProperty(propvar, g_rgPROPERTYMAP[i]);
                            break;
                        }     

プロパティがマップにない場合は、Windows エクスプローラーによって設定された新しいプロパティです。 開いているメタデータはサポートされているため、XML の ExtendedProperties セクションに新しいプロパティを保存します。

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

                    PropVariantClear(&propvar);
                }
            }
        }
    }

    return hr;    

IPropertyStoreCapabilities の実装

IPropertyStoreCapabilities は、シェル UI で特定のプロパティを編集できるかどうかをシェル UI に通知します。 これは、UI でプロパティを編集する機能にのみ関連し、プロパティで IPropertyStore::SetValue を正常に呼び出すことができるかどうかには関係しないことに注意してください。 IPropertyStoreCapabilities::IsPropertyWritable からS_FALSEの戻り値を引き起こすプロパティは、アプリケーションを介して設定できる場合があります。

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

IsPropertyWritable エンド ユーザーにプロパティの直接編集を許可する必要があることを示すS_OKを返します。S_FALSEは、そうでないことを示します。 S_FALSEは、アプリケーションがユーザーではなくプロパティを記述する責任があることを意味します。 シェルは、このメソッドの呼び出しの結果に基づいて、必要に応じて編集コントロールを無効にします。 IPropertyStoreCapabilities を実装しないハンドラーは、プロパティの書き込みをサポートして開いているメタデータをサポートするものと見なされます。

読み取り専用プロパティのみを処理するハンドラーを構築する場合は、Initialize メソッド (IInitializeWithStream、IInitializeWithItem、または IInitializeWithFile) を実装して、STGM_READWRITE フラグで呼び出されたときにSTG_E_ACCESSDENIEDを返すようにする必要があります。

一部のプロパティでは、 isInnate 属性が true に設定 されています。 自然なプロパティには、次の特性があります。

  • プロパティは通常、何らかの方法で計算されます。 たとえば、 System.Image.BitDepth はイメージ自体から計算されます。
  • ファイルを変更しないと、プロパティを変更することは意味がありません。 たとえば、 を変更 System.Image.Dimensions してもイメージのサイズは変更されないため、ユーザーがイメージを変更することは意味がありません。
  • 場合によっては、これらのプロパティはシステムによって自動的に提供されます。 たとえば、 System.DateModifiedファイル システムによって提供される などです。これは System.SharedWith、ユーザーがファイルを共有しているユーザーに基づいています。

これらの特性により、 IsInnate としてマークされたプロパティは、読み取り専用プロパティとしてのみシェル UI でユーザーに提供されます。 プロパティが IsInnate としてマークされている場合、プロパティ システムはそのプロパティをプロパティ ハンドラーに格納しません。 そのため、プロパティ ハンドラーでは、実装でこれらのプロパティを考慮する特別なコードは必要ありません。 IsInnate 属性の値が特定のプロパティに対して明示的に指定されていない場合、既定値は false です

プロパティ ハンドラーの登録と配布

プロパティ ハンドラーが実装されている場合は、そのハンドラーを登録し、そのファイル名拡張子をハンドラーに関連付ける必要があります。 詳細については、「 プロパティ ハンドラーの登録と配布」を参照してください。

プロパティ ハンドラーについて

種類名の使用

プロパティ リストの使用

プロパティ ハンドラーの登録と配布

プロパティ ハンドラーのベスト プラクティスと FAQ