Windows アイコンオーバーレイの上限を超えたアイコンを表示する方法

[Tablacus Explorer](https://tablacus.github.io/explorer.html)とアドオンの「アイコンオーバーレイ」で実現したアイコンオーバーレイの上限を超えたアイコンを表示する方法を紹介します。

#大雑把に言うと
レジストリからアイコンオーバーレイハンドラーを取得してカスタムドローでアイコンの上にアイコンオーバーレイを表示させています。

#一般的なアイコンオーバーレイの表示方法
1. [IShellIconOverlay::GetOverlayIndex](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761271(v=vs.85).aspx)でオーバーレイインデックスを取得
2. [ImageList_Draw](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761533(v=vs.85).aspx) で fStyleに [
INDEXTOOVERLAYMASK](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761408(v=vs.85).aspx) を付けてアイコンを表示

この[イメージリスト](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761391(v=vs.85).aspx) の [ImageList_SetOverlayImage](https://msdn.microsoft.com/en-us/library/windows/desktop/bb775227(v=vs.85).aspx) で設定できるアイコンオーバーレイの数がVersion 4.70以前が4個、Version 4.71以降が15個となっていてここがアイコンオーバーレイの限界になっています。
最初からショートカットやファイル共有のアイコンオーバーレイもこの15個の中に入りますし、現在では一つのシェル拡張でも10近いアイコンオーバーレイが登録されるのであっという間に上限を超えます。

#解説
##1. レジストリからアイコンオーバーレイハンドラー(IShellIconOverlayIdentifier) を取得

[ticonoverlay.cpp](https://github.com/tablacus/TablacusExplorerAddons/blob/master/iconoverlay_dll/ticonoverlay/ticonoverlay.cpp) 700行付近

アイコンオーバーレイハンドラーのレジストリ(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers) を[RegOpenKeyEx](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724897(v=vs.85).aspx) と [RegEnumKeyEx](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724862(v=vs.85).aspx) で列挙する

[RegQueryValueEx](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724911(v=vs.85).aspx)  で個々のアイコンオーバーレイハンドラーのクラスIDの文字列を取得

[CLSIDFromString](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680589(v=vs.85).aspx) で文字列からクラスIDに変換

[CoCreateInstance](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx) でクラスIDを使用してアイコンオーバーレイハンドラー([IShellIconOverlayIdentifier](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761265(v=vs.85).aspx)) を取得

[IShellIconOverlayIdentifier::GetOverlayInfo](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761261(v=vs.85).aspx) でアイコンが取得できることを確認してリストに積む

```cpp:ticonoverlay.cpp
		//Init
		case 0x60010000:
			if (nArg >= 0) {
				DWORD dwIndex = GetIntFromVariant(&pDispParams->rgvarg[nArg]);
				for (int i = g_pIconOverlayHandlers.size(); i-- > 0;) {
					SafeRelease(&g_pIconOverlayHandlers[i]);
				}
				g_pIconOverlayHandlers.clear();
				TCHAR pszName[MAX_PATH * 2], pszClsid[MAX_PATH];
				DWORD dwNameSize = MAX_PATH;
				DWORD dwSize = sizeof(pszClsid);
				FILETIME ftLastWriteTime;
				HKEY hKey, hKey1;
				LPWSTR pszKey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers";
				lstrcpy(pszName, pszKey);
				lstrcat(pszName, L"\\");
				int nName = lstrlen(pszName); 
				if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszKey, 0, KEY_ENUMERATE_SUB_KEYS, &hKey) == ERROR_SUCCESS) {
					LONG lRet;
					while ((lRet = RegEnumKeyEx(hKey, dwIndex++, &pszName[nName], &dwNameSize, NULL, NULL, NULL, &ftLastWriteTime)) != ERROR_NO_MORE_ITEMS) {
						if (lRet == ERROR_SUCCESS) {
							if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszName, 0, KEY_READ, &hKey1) == ERROR_SUCCESS) {
								if (RegQueryValueEx(hKey1, NULL, NULL, NULL, (LPBYTE)&pszClsid, &dwSize) == ERROR_SUCCESS) {
									CLSID clsid;
									if SUCCEEDED(CLSIDFromString(pszClsid, &clsid)) {
										IShellIconOverlayIdentifier *psio;
										if SUCCEEDED(CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psio))) {
											TCHAR pszIconFile[MAX_PATH * 2];
											int iIndex;
											DWORD dwFlags;
											if (psio->GetOverlayInfo(pszIconFile, sizeof(pszIconFile), &iIndex, &dwFlags) == S_OK) {
												g_pIconOverlayHandlers.push_back(psio);
											}
										}
									}
								}
								RegCloseKey(hKey1);
							}
							dwNameSize = MAX_PATH;
						}
					}
					RegCloseKey(hKey);
				}					
			}
			return S_OK;
```

##2. アイコンオーバーレイハンドラーからアイコンを取得して、画像でリストに保存

[script.js](https://github.com/tablacus/TablacusExplorerAddons/blob/master/iconoverlay/script.js) 25行付近、[ticonoverlay.cpp](https://github.com/tablacus/TablacusExplorerAddons/blob/master/iconoverlay_dll/ticonoverlay/ticonoverlay.cpp) 743行-772行

[IShellIconOverlayIdentifier::GetOverlayInfo](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761261(v=vs.85).aspx) で個々のアイコン情報を取得して画像でリストに保存

```js:script.js
		for (var i = 0, o = {}; Addons.IconOverlay.DLL.GetOverlayInfo(i, o) == S_OK; i++) {
			Addons.IconOverlay.Icon[i] = [MakeImgData("icon:" + o.IconFile + (o.dwFlags == 1 ? "" : "," + o.Index), 0, 16), o.dwFlags == 1 ? api.CreateObject("WICBitmap").FromFile(o.IconFile) : MakeImgData("icon:" + o.IconFile + "," + o.Index, 0, 32)];
}
```

```cpp:ticonoverlay.cpp
		//GetOverlayInfo
		case 0x60010001:
			HRESULT hr;
			hr = E_FAIL;
			if (nArg >= 1) {
				DWORD dwIndex = GetIntFromVariant(&pDispParams->rgvarg[nArg]);
				IUnknown *punk;
				if (FindUnknown(&pDispParams->rgvarg[nArg - 1], &punk)) {
					TCHAR pszIconFile[MAX_PATH * 2];
					int iIndex;
					DWORD dwFlags;
					if (dwIndex < g_pIconOverlayHandlers.size()) {
						hr = g_pIconOverlayHandlers[dwIndex]->GetOverlayInfo(pszIconFile, sizeof(pszIconFile), &iIndex, &dwFlags);
						if SUCCEEDED(hr) {
							VARIANT v;
							teSetSZ(&v, pszIconFile);
							tePutProperty(punk, L"IconFile", &v);
							VariantClear(&v);
							teSetLong(&v, iIndex);
							tePutProperty(punk, L"Index", &v);
							VariantClear(&v);
							teSetLong(&v, dwFlags);
							tePutProperty(punk, L"dwFlags", &v);
							VariantClear(&v);
						}
					}
				}
			}
			teSetLong(pVarResult, hr);
			return S_OK;
```

##3. [カスタムドロー](http://eternalwindows.jp/control/controlbase/controlbase08.html)でアイコンオーバーレイを表示

[script.js](https://github.com/tablacus/TablacusExplorerAddons/blob/master/iconoverlay/script.js) 33行付近、[ticonoverlay.cpp](https://github.com/tablacus/TablacusExplorerAddons/blob/master/iconoverlay_dll/ticonoverlay/ticonoverlay.cpp) 773行-788行

カスタムドローの[CDDS_ITEMPOSTPAINT](https://msdn.microsoft.com/ja-jp/library/windows/desktop/bb775483(v=vs.85).aspx)(既定の描画後)

積まれたリストのアイコンオーバーレイハンドラーを使い [IShellIconOverlayIdentifier::IsMemberOf](https://msdn.microsoft.com/en-us/library/windows/desktop/bb761267(v=vs.85).aspx) で表示項目がアイコン表示するかチェックし、表示するのであれば　2. で用意したアイコン画像を表示する

```js:script.js
			AddEvent("ItemPostPaint2", function (Ctrl, pid, nmcd, vcd)
			{
				var hList = Ctrl.hwndList;
				if (hList && pid && Addons.IconOverlay.DLL) {
					var db = Addons.IconOverlay.FV[Ctrl.Id];
					if (!db) {
						db = {};
						Addons.IconOverlay.FV[Ctrl.Id] = db;
					}
					var i = db[pid.Path];
					if (isNaN(i)) {
						i = Addons.IconOverlay.DLL.GetOverlayIconIndex(pid.Path, api.GetAttributesOf(pid, SFGAO_STORAGECAPMASK));
						db[pid.Path] = i;
					}
					var icon = Addons.IconOverlay.Icon[i];
					if (icon) {
						var image = icon[Ctrl.IconSize < 32 ? 0 : 1];
						var rc = api.Memory("RECT");
						rc.Left = LVIR_ICON;
						api.SendMessage(hList, LVM_GETITEMRECT, nmcd.dwItemSpec, rc);
						var w = Ctrl.IconSize * screen.logicalYDPI / 96;
						image = GetThumbnail(image, Ctrl.IconSize < 32 ? w : (w * 5 / 16) + 16);
						var x = api.SendMessage(hList, LVM_GETVIEW, 0, 0) == 2 ? rc.Right - w : rc.Left + (rc.Right - rc.Left - w) / 2;
						var y = rc.Bottom - image.GetHeight() - 2;
						if (image) {
							image.DrawEx(nmcd.hdc, x, y, 0, 0, CLR_NONE, CLR_NONE, ILD_NORMAL);
						}
					}
				}
});
```

```cpp:ticonoverlay.cpp
		//GetOverlayIconIndex
		case 0x60010002:
			int iResult;
			iResult = -1;
			if (nArg >= 1) {
				BSTR bsPath = GetLPWSTRFromVariant(&pDispParams->rgvarg[nArg]);
				DWORD dwAttrib = GetIntFromVariant(&pDispParams->rgvarg[nArg - 1]);
				for (UINT i = 0; i < g_pIconOverlayHandlers.size(); i++) {
					if (g_pIconOverlayHandlers[i]->IsMemberOf(bsPath, dwAttrib) == S_OK) {
						iResult = i;
						break;
					}
				}
			}
			teSetLong(pVarResult, iResult);
			return S_OK;
```

#参考
[EternalWindows / シェル拡張 / アイコン オーバーレイ](http://eternalwindows.jp/shell/shellex/shellex03.html)