このアーカイブは同期化されません。 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 初期化についてやろう。