このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
この勢いで、フォルダリンク(フォルダショートカット)の、
FolderLinkIconID クラスも作り上げてしまおう。
やはり似たようなソースになることが想定されるので、
HardLinkIconID からコピーして作ることにする。
========== FolderLinkIconID.hpp ==========
#ifndef folderlinkiconid_hpp_included
#define folderlinkiconid_hpp_included
#include <shlobj.h>
#include "Object.hpp"
class FolderLinkIconID : public Object,
public IShellIconOverlayIdentifier {
// インタフェース
public:
// IUnknown の実装
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [out] */ void** ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
// IShellIconOverlayIdentifier
virtual HRESULT STDMETHODCALLTYPE IsMemberOf(
/* [in] */ LPCWSTR pwszPath,
/* [in] */ DWORD dwAttrib);
virtual HRESULT STDMETHODCALLTYPE GetOverlayInfo(
/* [out] */ LPWSTR pwszIconFile,
/* [in] */ int cchMax,
/* [out] */ int* pIndex,
/* [out] */ DWORD* pdwFlags);
virtual HRESULT STDMETHODCALLTYPE GetPriority(
/* [out] */ int* pPriority);
};
#endif // !folderlinkiconid_hpp_included
========== end of FolderLinkIconID.hpp ==========
え~、当たり前の話だが、
クラス名とインクルードガード以外完全に同じだ。
んで、実体。
========== FolderLinkIconID.cpp ==========
// ビルド環境用のヘッダ
#include "global.hpp"
// クラスを定義したヘッダ
#include "FolderLinkIconID.hpp"
// リソース ID のヘッダ
#include "resource.h"
// FolderLinkIconID の実装
// IUnknown
HRESULT FolderLinkIconID::QueryInterface(
/* [in] */ REFIID riid,
/* [out] */ void** ppvObject) {
// ppvObject が NULL なら例外
if (ppvObject == NULL) return E_POINTER;
// ppvObject は out なので、
// ポインタが指す元の値は無視できる
*ppvObject = NULL;
if (IsEqualIID(riid, IID_IShellIconOverlayIdentifier)) {
*ppvObject = static_cast<IShellIconOverlayIdentifier*>(this);
} else if (IsEqualIID(riid, IID_IUnknown)) {
*ppvObject = static_cast<IShellIconOverlayIdentifier*>(this);
} else {
return E_NOINTERFACE;
}
// ポインタを作成した場合カウンタを増加
static_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
ULONG STDMETHODCALLTYPE FolderLinkIconID::AddRef() {
return Object::AddRef();
}
ULONG STDMETHODCALLTYPE FolderLinkIconID::Release() {
return Object::Release();
}
========== (FolderLinkIconID.cpp) ==========
……IUnknown の実装も全く同じ。だいぶ飽きてきた。
========== (FolderLinkIconID.cpp) ==========
// IShellIconOverlayIdentifier
HRESULT STDMETHODCALLTYPE FolderLinkIconID::GetOverlayInfo(
/* [out] */ LPWSTR pwszIconFile,
/* [in] */ int cchMax,
/* [out] */ int* pIndex,
/* [out] */ DWORD* pdwFlags) {
// 引数のチェック
if (pwszIconFile == NULL || pIndex == NULL || pdwFlags == NULL) {
return E_POINTER;
}
if (IsBadWritePtr(pwszIconFile, sizeof (WCHAR) * cchMax)) {
return E_INVALIDARG;
}
if (IsBadWritePtr(pIndex, sizeof (int))) return E_INVALIDARG;
if (IsBadWritePtr(pdwFlags, sizeof (DWORD))) return E_INVALIDARG;
// 自分自身のファイル名とアイコンインデックスを返す
if (!GetModuleFileName(g_moduleHandle, pwszIconFile, cchMax)) {
return HRESULT_FROM_WIN32(GetLastError());
}
*pIndex = -IDI_FOLDERLINK;
*pdwFlags = ISIOI_ICONFILE | ISIOI_ICONINDEX;
return S_OK;
}
HRESULT STDMETHODCALLTYPE FolderLinkIconID::GetPriority(
/* [out] */ int* pPriority) {
// 引数のチェック
if (pPriority == NULL) return E_POINTER;
if (IsBadWritePtr(pPriority, sizeof (int))) return E_INVALIDARG;
// 優先順位は最高(デフォルトに任せる)
*pPriority = 0;
return S_OK;
}
HRESULT STDMETHODCALLTYPE FolderLinkIconID::IsMemberOf(
/* [in] */ LPCWSTR pwszPath,
/* [in] */ DWORD dwAttrib) {
// 引数のチェック
if (pwszPath == NULL) return E_FAIL;
if (IsBadStringPtrW(pwszPath, MAX_PATH)) return E_FAIL;
// ファイル属性を得る
DWORD fileAttr = GetFileAttributes(pwszPath);
if (fileAttr == INVALID_FILE_ATTRIBUTES) return E_FAIL;
// フォルダリンクはディレクトリであり、
// システムまたは読み取り専用属性を持つ
if (!(fileAttr & FILE_ATTRIBUTE_DIRECTORY)) return S_FALSE;
if (!(fileAttr & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
return S_FALSE;
// desktop.ini のパスを作成
static const wchar_t *CONFIG_FILE = L"desktop.ini";
// バッファを用意
WCHAR iniPath[MAX_PATH];
// ディレクトリのパス長を取得
int length = lstrlen(pwszPath);
if (length <= 0) return S_FALSE;
if (pwszPath[length - 1] == L'\\') {
// バックスラッシュで終端
// バッファの容量が足りるかどうか調べる
if (length + lstrlen(CONFIG_FILE) >= MAX_PATH)
return E_FAIL;
// パスを作成
wsprintf(iniPath, L"%s%s",
pwszPath, CONFIG_FILE);
} else {
// バックスラッシュがない
// バッファの容量が足りるかどうか調べる
if (length + 1 + lstrlen(CONFIG_FILE) >= MAX_PATH)
return E_FAIL;
// パスを作成
wsprintf(iniPath, L"%s%c%s",
pwszPath, L'\\', CONFIG_FILE);
}
// desktop.ini から CLSID を得る
WCHAR clsid[40];
// [.ShellClassInfo]
// CLSID2=XXXXXXXX
if (!GetPrivateProfileString(L".ShellClassInfo",
L"CLSID2", NULL, clsid, 40, iniPath)) {
// [.ShellClassInfo]
// CLSID=XXXXXXXX
if (!GetPrivateProfileString(L".ShellClassInfo",
L"CLSID", NULL, clsid, 40, iniPath))
return S_FALSE;
}
// フォルダリンクの CLSID かどうか
if (lstrcmpi(clsid, L"{0AFACED1-E828-11D1-9187-B532F1E9575D}"))
return S_FALSE;
return S_OK;
}
========== end of FolderLinkIconID.cpp ==========
GetOverlayInfo と GetPriority は最早説明不要だ。
IsMemberOf が核心となる。
フォルダリンクの場合は、まず対象となるフォルダに、
読み取り専用またはシステム属性がついており、
フォルダ内部に desktop.ini というファイルが必要となる。
そして、desktop.ini には「.ShellClassInfo」という
セクションが存在し、そこにキーとして、
「CLSID2」または「CLSID」が存在している必要がある。
さて、フォルダの属性を調べるのは簡単だが、
desktop.ini を調べるのはなかなか難しい。
というのは、C 言語は文字列の処理が苦手なのである。
C++ には、高度なライブラリが含まれているので、
それらを使って処理することもできるのだが、
ここは地道なコードで書いていこう。
フォルダのパスの末尾に文字列を追加するには、
文字列の長さやバッファの容量などを調べ、
注意深く文字列を連結する必要がある。
desktop.ini のファイルを自力で解析するのは骨が折れるが、
幸い、Windows には、INI ファイル用の API が存在する。
GetPrivateProfileString を使えば、
INI ファイルに簡単にアクセスすることができるのである。
Windows XP のフォルダリンクを調べてみると、
CLSID=XXXXXXXX という値だけでなく、
CLSID2=XXXXXXXX という形式の値も存在するようなので、
先に CLSID2 を調べ、失敗すれば CLSID を調べる事にする。
CLSID は文字 16 進数表現として格納されているので、
大文字小文字を区別せずに比較する必要がある。
少し長くなったが、コード自体はそれほど難しくない。