この勢いで、フォルダリンク(フォルダショートカット)の、
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 進数表現として格納されているので、
大文字小文字を区別せずに比較する必要がある。

少し長くなったが、コード自体はそれほど難しくない。