2006 年 11 月 19 日 20 時 58 分

HardLinkIconID クラス


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


昨日作ったアイコンを使って、
ハードリンク用のアイコンを表すクラスを作ってみる。

クラス名は「HardLinkIconID」とする。
IUnknown の基準実装は、Object クラスから継承しよう。
まずは、クラスの定義を作成する。

========== HardLinkIconID.hpp ==========

#ifndef hardlinkiconid_hpp_included
#define hardlinkiconid_hpp_included

#include <shlobj.h>
#include "Object.hpp"

class HardLinkIconID : public Object, public IShellIconOverlayIdentifier {

// インタフェース
public:

    // QueryInterface のオーバーライド

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(
            /* [in] */ REFIID riid,
            /* [out] */ void** ppvObject);

    // 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 // !hardlinkiconid_hpp_included

========== end of HardLinkIconID.hpp ==========

Object が IUnknown を実装しているので、
IUnknown のメソッドの実装は不要だが、
Object は IShellIconOverlayIdentifier を知らないので、
QueryInterface だけはオーバーライドしておく。

コンストラクタやデストラクタは省略すると自動生成される。
今回は初期化や後処理を使わないので、省略しておこう。

また、複製禁止のためのメソッド定義だが、
これは親クラスである Object で明示されているので、
子クラスである HardLinkIconID でも複製はできなくなる。

もちろん、これらを明文化しても構わない。

では、実体の定義をしよう。

========== HardLinkIconID.cpp ==========

// ビルド環境用のヘッダ
#include "global.hpp"

// クラスを定義したヘッダ
#include "HardLinkIconID.hpp"

// リソース ID のヘッダ
#include "resource.h"

// HardLinkIconID の実装

HRESULT HardLinkIconID::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;
}

========== (HardLinkIconID.cpp) ==========

QueryInteface では、HardLinkIconID 固有の、
IShellIconOverlayIdentifier へのキャストも実装する。
注意点は、IID_IUnknown が要求された際に、
IUnknown にキャストしていない点である。

IUnknown は、Object と IShellIconOverlayIdentifier の
両方で実装しているため、HardLinkIconID を、
IUnknown にキャストすると曖昧さが生じる。

そこで、IShellIconOverlayIdentifier か Object を
明確に選択してキャストする必要がある。
今回は IShellIconOverlayIdentifier にキャストしている。
あらゆるインタフェースは IUnknown を継承しているので、
IShellIconOverlayIdentifier から IUnknown には、
常に暗黙のキャストが成立するからである。

========== (HardLinkIconID.cpp) ==========

HRESULT STDMETHODCALLTYPE HardLinkIconID::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;

    // ファイルのリンク数を調べるためには開く必要がある
    HANDLE file = CreateFile(pwszPath, 0,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file == INVALID_HANDLE_VALUE) return E_FAIL;

    HRESULT result = E_FAIL;

    // GetFileInformationByHandle でリンク数を得る
    BY_HANDLE_FILE_INFORMATION info;
    ZeroMemory(&info, sizeof (BY_HANDLE_FILE_INFORMATION));
    if (GetFileInformationByHandle(file, &info)) {
        // リンク数が 2 以上ならハードリンクされている
        result = (info.nNumberOfLinks >= 2) ? S_OK : S_FALSE;
    }

    CloseHandle(file);
    return result;
}

========== (HardLinkIconID.cpp) ==========

IsMemberOf はファイルがハードリンクされているか確認し、
S_OK, S_FALSE, E_FAIL のどれかを返すことになっている。

シェルエクステンションはインプロセスサーバなので、
もしアクセス違反や例外が発生すると、
これを利用するシェルを巻き込んで落ちることになる。

そのため、最小限の引数の確認はしておいた方が安全だ。
pwszPath 引数はファイル名だが、
NULL が渡された場合や、文字列を読み取れない場合は、
E_FAIL を渡しておく方が安全である。

本来は、E_POINTER や、E_INVALIDARG を返すべきであるが、
IsMemberOf が返すことができるのは、3 つだけのようだ。
(通常のメソッドは任意の HRESULT 値を返すことができる)

dwAttrib 引数には、オブジェクトの属性が渡されるのだが、
これはファイルの属性とは異なるので、
GetFileAttributes を使ってファイル属性を得て、
ディレクトリであれば対象外として抜ける。

ハードリンクされているかどうか調べるためには、
CreateFile でファイルを開き、
GetFileInformationByHandle でリンク数の情報を得る。
リンク数が 2 以上ならば、ハードリンクを持っている。

========== (HardLinkIconID.cpp) ==========

HRESULT STDMETHODCALLTYPE HardLinkIconID::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_HARDLINK;
    *pdwFlags = ISIOI_ICONFILE | ISIOI_ICONINDEX;

    return S_OK;
}

========== (HardLinkIconID.cpp) ==========

GetOverlayInfo では、アイコンのファイル名を渡す。
アイコンはリソースとして DLL に取り込まれているので、
pwszIconFile には DLL 自身のファイル名を渡す。

DLL はどこに配置されるか分からないので、
GetModuleFileName を使って動的にファイル名を取得する。
GetModuleFileName には、DLL のハンドルが必要だが、
これはどこからか取得してこなければならない。
ここでは g_moduleHandle グローバル変数を使っている。
g_moduleHandle については後日解説しよう。

pIndex には、アイコンのインデックスを指定する。
これは、DLL 内にあるアイコンの「順番」を表す数値だが、
負の値を使って ID を渡すこともできる。
リソース内にどの順でアイコンが並べられるかは、
リソースコンパイラに依存するので、ID の方が確実である。

pdwFlags には、このメソッドが、
ファイル名とインデックス両方を返すことを指定しておく。

========== (HardLinkIconID.cpp) ==========

HRESULT STDMETHODCALLTYPE HardLinkIconID::GetPriority(
        /* [out] */ int* pPriority) {

    // 引数のチェック
    if (pPriority == NULL) return E_POINTER;
    if (IsBadWritePtr(pPriority, sizeof (int))) return E_INVALIDARG;

    // 優先順位は最高(デフォルトに任せる)
    *pPriority = 0;

    return S_OK;
}

========== end of HardLinkIconID.cpp ==========

そして、GetPriority だが、
これはデフォルトの 0 を返すことにしておく。

これで、基本的な実装は OK である。

さて、現時点では g_moduleHandle が未定義なので、
当然コンパイルエラーが出る。
明日は、グローバル変数と、DLL 初期化についてやろう。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.