EternalWindows
シェル拡張 / IEnumIDListの実装

IShellFolderを取得した側は、フォルダのアイテムを列挙したくなった段階でIShellFolder::EnumObjectsを呼び出します。 このメソッドでは、アイテムを列挙するためのIEnumIDListを返さなければならないため、 IEnumIDListを継承したクラスのオブジェクトが必要になります。

class CEnumIDList : public IEnumIDList
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched);
	STDMETHODIMP Skip(ULONG celt);
	STDMETHODIMP Reset(VOID);
	STDMETHODIMP Clone(IEnumIDList **ppenum);

	CEnumIDList(BYTE level, SHCONTF flags);
	~CEnumIDList();
	
private:
	LONG    m_cRef;
	BYTE    m_level;
	SHCONTF m_flags;
	ULONG   m_uCount;
};

見て分かるように、IEnumIDListを継承したCEnumIDListというクラスを定義しています。 そして、このクラスを型としたオブジェクトをEnumObjectsで作成することになります。

STDMETHODIMP CShellFolder::EnumObjects(HWND hwndOwner, SHCONTF grfFlags, IEnumIDList **ppenumIDList)
{
	*ppenumIDList = new CEnumIDList(m_level, grfFlags);

	return S_OK;
}

これにより、ppenumIDListにオブジェクトのアドレスが格納されたことになりますから、 呼び出し側はIEnumIDListを通じてオブジェクトにアクセスできるようになります。 コンストラクタの引数については後述します。

IEnumIDListを取得した側は、Nextを呼び出してフォルダ内のアイテムのPIDLを列挙するため、 CEnumIDList::Nextの実装はPIDLを返すようになっていなければなりません。 このためにはまず、フォルダ内にどれだけのアイテムを存在させるかを決定し、 さらにそのアイテムのデータを定義する必要があります。 今回は、1つのアイテムをITEMDATA構造体で識別することにします。

struct ITEMDATA {
	USHORT ub;
	BYTE   level;
	BYTE   iconIndex;
	SFGAOF attribute;
	WCHAR  szName[10];
};

ubは、この構造体のサイズを指定します。 levelは、このアイテムが存在することになるレベル(フォルダの階層)を指定します。 この値が0であるアイテムは最上位のフォルダで表示され、 1であるアイテムは1つの下のフォルダで表示されます。 iconIndexは、アイテムのアイコンのインデックスを指定します。 attributeは、アイテムの属性を指定します。 SFGAO_FOLDERが含まれる場合、そのアイテムはフォルダであると認識します。 szNameは、アイテムの名前を指定します。

ITEMDATA構造体の先頭にサイズを受け取るメンバが存在するのには理由があります。 それは、この構造体をPIDLとして扱いたい(キャストしたい)からです。 PIDLはITEMIDLIST構造体のポインタであり、ITEMIDLIST構造体にはSHITEMID構造体が含まれています。

typedef struct _SHITEMID {
  USHORT cb;
  BYTE abID[1];
} SHITEMID;

cbは、自身と可変長データのサイズの合計が格納されます。 構造体がPIDLとして認識されるためには、先頭にサイズを格納するメンバが存在しなければならないため、 ITEMDATA構造体の先頭にもcbを含めるようにしています。 abIDは配列の要素数が1になっていますが、これはこのメンバが可変長データの先頭であることを示す指標に過ぎません。 この可変長データはアイテムを一意に識別できれば何でも構いませんから、 当然ながら先に示したITEMDATA構造体のlevelメンバ以降のデータでも問題はありません。

次のコードは、ITEMDATA構造体を使用して各アイテムを定義しています。

const ITEMDATA g_itemData[] = {
	{sizeof(ITEMDATA), 0, 0, 0, L"アイテムA"},
	{sizeof(ITEMDATA), 0, 1, SFGAO_FOLDER, L"アイテムB"},
	{sizeof(ITEMDATA), 1, 2, 0, L"アイテムC"},
};
const int g_nItemCount = sizeof(g_itemData) / sizeof(g_itemData[0]);

1番目のアイテムはlevelが0になっているため、最初フォルダを開いたときにはこのアイテムが表示されます。 2番目のアイテムもlevelが0になっているため、最上位のフォルダで表示されることになりますが、 このアイテムは属性がSFGAO_FOLDERになっています。 よって、アイテムを開くことで1つの下のフォルダに移動することになります。 このフォルダで表示されるアイテムは、levelが1になっている3番目のアイテムになります。 g_itemDataの要素数が3つになっているため、g_nItemCountの値は3になります。

CEnumIDListのメソッドを順に見ていきます。 まずは、コンストラクタからです。

CEnumIDList::CEnumIDList(BYTE level, SHCONTF flags)
{
	m_cRef = 1;
	m_level = level;
	m_flags = flags;
	m_uCount = 0;
}

第1引数であるlevelは、列挙の対象となっているフォルダの階層を示す値が格納されています。 アイテムの列挙時には、異なる階層のアイテムを考慮しないようにしなければならないため、 この値はメンバとして保存することになります。 第2引数であるflagsは、 列挙の方法に関する定数が格納されています。 アイテムの列挙時には、フォルダのアイテムだけを列挙したいような場合も考えられるため、 この値もメンバとして保存することになります。 m_uCountは、列挙をするに当たって確認したアイテムの数を格納することになります。 列挙できたアイテムの数を格納するわけではないことに注意してください。

IEnumIDListを取得した呼び出し側は、Nextを通じてアイテムを列挙することになります。

STDMETHODIMP CEnumIDList::Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched)
{
	USHORT        uSize = sizeof(ITEMDATA);
	PITEMID_CHILD pidl;
	
	if (m_uCount >= g_nItemCount)
		return E_FAIL;

	for (; m_uCount < g_nItemCount; m_uCount++) {
		if (g_itemData[m_uCount].level != m_level)
			continue;
		
		if (g_itemData[m_uCount].attribute & SFGAO_FOLDER) {
			if (!(m_flags & SHCONTF_FOLDERS))
				continue;
		}
		else {
			if (!(m_flags & SHCONTF_NONFOLDERS))
				continue;
		}

		pidl = (PITEMID_CHILD)CoTaskMemAlloc(uSize + sizeof(USHORT));
		ZeroMemory(pidl, uSize + sizeof(USHORT));
		CopyMemory(pidl, &g_itemData[m_uCount], uSize);
		
		*rgelt = pidl;
		if (pceltFetched != NULL)
			*pceltFetched = 1;
		m_uCount++;

		return S_OK;
	}

	return E_FAIL;
}

m_uCountがg_nItemCount以上ということは全てのアイテムを確認したということであり、 これ以上アイテムを列挙できませんから制御を返すようにします。 ループ文では、g_itemDataの各要素を確認するためにm_uCountを添え字として使用しています。 まず、アイテムのレベルが現在のフォルダのレベルと一致しない場合は、 そのアイテムは列挙しないようにします。 次に、アイテムがフォルダであるかを調べ、フォルダである場合はフラグとしてSHCONTF_FOLDERSが指定されていないかを確認します。 これが指定されていない場合はフォルダを列挙する必要がないということですから、continueでループの先頭に戻ることになります。 一方、アイテムが非フォルダである場合は、フラグとしてSHCONTF_NONFOLDERSが指定されていないかを確認します。 これが指定されていない場合は非フォルダを列挙する必要がないということですから、continueでループの先頭に戻ることになります。 g_itemData[m_uCount]が列挙できるアイテムであると判定できたならば、PIDLを作成するためのメモリを確保することになります。 このとき、アイテムのサイズにsizeof(USHORT)を加えていますが、 これはPIDLの終端2バイト0になっていなければならない決まりがあるからです。 よって、ZeroMemoryでもサイズにsizeof(USHORT)を加えることによって、 終端2バイトも0で埋まるようにしています。 CopyMemoryによってg_itemData[m_uCount]がpidlにコピーされることになりますが、 このときのサイズはsizeof(USHORT)を考慮していないので、 先に埋めておいた終端2バイトは0のままとなります。 最後に、確保したPIDLを呼び出し側が受け取れるように*rgeltに格納し、 1つのアイテムを列挙したことを示すために*pceltFetchedに1を格納します。

CEnumIDListには、Nextの他にResetやSkip、Cloneなどのメソッドが含まれています。 これらのメソッドが呼ばれることはあまりないと思われますが、 念のために実装しておきます。

STDMETHODIMP CEnumIDList::Skip(ULONG celt)
{
	m_uCount += celt;

	return S_OK;
}

STDMETHODIMP CEnumIDList::Reset(VOID)
{
	m_uCount = 0;
	
	return S_OK;
}

STDMETHODIMP CEnumIDList::Clone(IEnumIDList **ppenum)
{
	*ppenum = new CEnumIDList(m_level, m_flags);
	(*ppenum)->Skip(m_uCount);

	return S_OK;
}

Skipの実装については、引数の分だけm_uCountを加算すれば問題ありません、 Resetは、アイテムを最初から列挙したくなった場合に呼ばれるため、m_uCountに0を指定します。 Cloneは、自身と同じCEnumIDListを必要とする際に呼ばれます。 このため、CEnumIDList型のオブジェクトを作成することになるのですが、 その引数は現在のオブジェクトのメンバを指定するようにします。 これにより、新しいオブジェクトが現在のオブジェクトと同じデータを持つことができます。 また、Skipを呼び出すことでm_uCountの値も現在のオブジェクトと同一にしておきます。


戻る