EternalWindows
シェル拡張 / メタデータ ハンドラ
サンプルはこちら

メタデータ ハンドラは、特定の拡張子を持ったファイルのプロパティを表示する場合に使用します。 プロパティとは、次の図におけるタグのようなものです。

メタデータ ハンドラのオブジェクトは、IPropertyStore、IPropertyStoreCapabilities、IInitializeWithStreamを実装する必要があります。

class CPropertyStore : public IPropertyStore, public IPropertyStoreCapabilities, public IInitializeWithStream
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP GetCount(DWORD *cProps);
	STDMETHODIMP GetAt(DWORD iProp, PROPERTYKEY *pkey);
	STDMETHODIMP GetValue(REFPROPERTYKEY key, PROPVARIANT *pv);
	STDMETHODIMP SetValue(REFPROPERTYKEY key, REFPROPVARIANT propvar);
	STDMETHODIMP Commit(VOID);
	
	STDMETHODIMP IsPropertyWritable(REFPROPERTYKEY key);

	STDMETHODIMP Initialize(IStream *pstream, DWORD grfMode);

	CPropertyStore();
	~CPropertyStore();

private:
	LONG                m_cRef;
	IStream             *m_pStream;
	IPropertyStoreCache *m_pCache;
};

class CClassFactory : public IClassFactory
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
	STDMETHODIMP LockServer(BOOL fLock);
};

extern void LockModule(BOOL bLock);
extern BOOL CreateRegistryKey(HKEY hKeyRoot, LPTSTR lpszKey, LPTSTR lpszValue, LPTSTR lpszData);

GetCountからCommitまでがIPropertyStoreのメソッドであり、 IsPropertyWritableがIPropertyStoreCapabilitiesのメソッド、 InitializeがIInitializeWithStreamのメソッドになります。 IInitializeWithStreamの代わりに、IInitializeWithFileやIInitializeWithItemを実装したいかもしれまんが、 これらのIIDはQueryInterfaceに送られてきません。

IPropertyStoreのGetValueは、ファイルからプロパティを取得しようとする際に呼ばれます。 よって、メソッドの実装はそのようなコードを記述すればよいのですが、 こうした取得処理を呼び出しの度に行うのは少し非効率といえるはずです。 できることなら、ファイルがサポートするプロパティをメモリに展開しておき、 それを返すようにしたほうがよいでしょう。 これを実現するためのインターフェースとしてIPropertyStoreCacheが存在するため、 Initializeで取得することになります。

STDMETHODIMP CPropertyStore::Initialize(IStream *pstream, DWORD grfMode)
{
	PROPVARIANT propvar;

	PSCreateMemoryPropertyStore(IID_PPV_ARGS(&m_pCache));
	
	InitPropVariantFromString(L"my tag", &propvar);
	m_pCache->SetValueAndState(PKEY_Keywords, &propvar, PSC_NORMAL);
	PropVariantClear(&propvar);
	
	m_pStream = pstream;
	m_pStream->AddRef();

	return S_OK;
}

PSCreateMemoryPropertyStoreを呼び出せば、メモリ上にプロパティストアが作成され、 それをIPropertyStoreCacheで表すことができます。 SetValueAndStateを呼び出しているのは、メモリ上のプロパティストアにプロパティを設定するためであり、 これがプロパティをメモリに展開するという意味になります。 SetValueAndStateの第1引数は、設定するプロパティの種類を表すプロパティキーであり、 g_propKeySupport[0](中身はPKEY_Keywords)はシェルで言うところの「タグ」に相当します。 第2引数はプロパティの設定するデータであり、これはPROPVARIANT構造体でなければなりません。 InitPropVariantFromStringを呼び出せば、第1引数の文字列を基にPROPVARIANT構造体を作成することができます。 上記では、文字列の値をハードコードしていますが、 実際の開発ではファイルから該当プロパティを取得するためにIStream::Readを呼び出すことになるでしょう。 IStreamはプロパティをファイルに格納する際にも必要になるため、メンバ変数に保存すると共に参照カウントを1つ上げておきます。 これは、Initializeの終了時にIStreamで表されるオブジェクトが破棄されないようにするためです。

IPropertyStoreCacheでプロパティをメモリに展開しておけば、 次に示す3つのメソッドの実装は非常に単純です。

STDMETHODIMP CPropertyStore::GetCount(DWORD *cProps)
{
	if (m_pCache == NULL)
		return E_UNEXPECTED;

	return m_pCache->GetCount(cProps);
}

STDMETHODIMP CPropertyStore::GetAt(DWORD iProp, PROPERTYKEY *pkey)
{
	if (m_pCache == NULL)
		return E_UNEXPECTED;

	return m_pCache->GetAt(iProp, pkey);
}

STDMETHODIMP CPropertyStore::GetValue(REFPROPERTYKEY key, PROPVARIANT *pv)
{
	if (m_pCache == NULL)
		return E_UNEXPECTED;

	return m_pCache->GetValue(key, pv);
}

GetCountは、ファイルがサポートするプロパティの数を取得する際に呼ばれます。 このための具体的な処理は、IPropertyStoreCache::GetCountを呼び出せば行うことができます。 GetAtは、特定のインデックスに関連するPROPERTYKEYを取得する際に呼ばれます。 これもIPropertyStoreCacheのGetAtを呼び出すだけで構いません。 GetValueは、PROPERTYKEYからプロパティの値を取得する際に呼ばれますが、 やはりこれもIPropertyStoreCacheのGetValueを呼び出すことになります。

シェルはプロパティを取得する目的でIPropertyStoreを使用するため、 プロパティを設定するSetValueを呼び出すことはありません。 よって、E_FAILを返すだけで問題ありません。 他のアプリケーションからの設定を考慮する場合は、 次のような実装になるでしょうか。

STDMETHODIMP CPropertyStore::SetValue(REFPROPERTYKEY key, REFPROPVARIANT propvar)
{
	int     i;
	HRESULT hr = E_FAIL;

	if (m_pCache == NULL)
		return E_UNEXPECTED;

	for (i = 0; i < g_nPropKeyCount; i++) {
		if (IsEqualPropertyKey(key, g_propKeySupport[i])) {
			hr = m_pCache->SetValueAndState(g_propKeySupport[i], &propvar, PSC_NORMAL);
			break;
		}
	}
	
	return hr;
}

g_nPropKeyCountにはファイルがサポートするプロパティの数が格納されており、 g_propKeySupportにはファイルがサポートするプロパティのキーが格納されています。 サポートしていないプロパティを設定するわけにはいきませんから、 keyの値がg_propKeySupport[i]と一致するかを調べ、 一致する場合はSetValueAndStateを呼び出してプロパティを設定します。 IsEqualPropertyKeyは、指定された2つのPROPERTYKEYが一致するかを調べるマクロです。

SetValueを呼び出したアプリケーションは、最終的にCommitを呼び出すことになります。 Commitでは、メモリに展開されたプロパティを実際にファイルへ書き込みますが、 この方法はファイルの形式によって大きく異なります。 よって、次のコードはイメージとして考えてください。

STDMETHODIMP CPropertyStore::Commit(VOID)
{
	int         i;
	HRESULT     hr = E_FAIL;
	PROPVARIANT propvar;
	ULONG       uSize;
	WCHAR       szBuf[256];

	if (m_pCache == NULL)
		return E_UNEXPECTED;

	for (i = 0; i < g_nPropKeyCount; i++) {
		m_pCache->GetValue(g_propKeySupport[i], &propvar);
		PropVariantToString(propvar, szBuf, sizeof(szBuf) / sizeof(WCHAR));
		
		uSize = sizeof(szBuf);
		m_pStream->Write(szBuf, lstrlenW(szBuf) * sizeof(WCHAR), &uSize);
		
		PropVariantClear(&propvar);
	}

	m_pStream->Commit(STGC_DEFAULT);

	return S_OK;
}

GetValueによってメモリに展開されたプロパティを取得し、 これをPropVariantToStringで文字列に変換します。 そして、IStram::Writeを呼び出して文字列をファイルに書き込みます。 最終的にIStram::Commitを呼び出すことによって、書き込み内容はファイルに反映されることになります。

メタデータ ハンドラは、IPropertyStoreの他にIPropertyStoreCapabilitiesを実装するべきとされています。

STDMETHODIMP CPropertyStore::IsPropertyWritable(REFPROPERTYKEY key)
{
	return IsEqualPropertyKey(key, PKEY_Search_Contents) ? S_FALSE : S_OK;
}

このメソッドがどのようなタイミングで呼ばれるのかはよく分かりませんが、 上記のような実装で問題ないようです。

メタデータ ハンドラを登録するには、次に示すレジストリキーを作成することになります。

HKEY_LOCAL_MACHINE
  SOFTWARE
    Microsoft
      Windows
        CurrentVersion
          PropertySystem
            PropertyHandlers
              <特定の拡張子>  (既定) = オブジェクトのCLSID

メタデータ ハンドラのDLLは、リストビューを詳細表示する際にシェルによってロードされ、 プロパティの取得が終了した場合は直ちにアンロードされます。 DLLが認識されるためには、登録後にシェルを再起動する必要があります。

使われなくなった技術

IPropertyStoreが存在しないWindows Vista以前のWindowsにおいて、 今回と同じような動作を行うには主に2つの方法があります。 1つはIColumnProviderを実装して詳細表示のカラムに値を設定する方法で、 この値をファイルのプロパティにすれば、プロパティが表示されているように見えることになります。 IColumnProviderのシェル拡張は、特定の拡張子ではなくフォルダに対して関連付を行うため、 特定の拡張子のファイルからプロパティを取得するアプリケーションにとっては、 あまり使いやすいインターフェースとは言えないでしょう。 IColumnProviderは、Windows Vistaからサポートされなくなりました。

2つ目はIPropertySetStorageを実装する方法で、 これはIPropertyStoreの前身に当たります。 IPropertySetStorageはWindows Vistaからでも依然として使用可能ですが、 各種拡張子の関連付けをレジストリで確認してみると、 IPropertySetStorageはほとんど使用されていないことが分かります。 これは、プロパティを取り扱う標準がIPropertyStoreに置き換わった事実を物語っているといえるでしょう。 WordやExcelなどのファイルは構造化ストレージということもあり、 IPropertySetStorageに対応しています。



戻る