フルパスのITEMIDLISTから、ハッシュ値の計算をしたいと考えています。
単純にITEMIDLISTのデータ部のみを計算に使えば良いのでは?と思いましたが、どう
もITEMIDLISTを生成する手順によって、同じパスを示すITEMIDLISTであってもデータ部
の内容が異なっているようなのです。
試しに、下記のように組んで、ITEMIDLISTが生成される異なるパターンを比較しまし
た。
その結果、ILIsEqual()で同じITEMIDLISTと判定されるものであっても、生成方法によ
ってSHITEMIDのabIDの内容が異なります。
検索結果の表示部分なので、10万近いファイルが仮想リストに並ぶ前提ですので、シ
ーケンシャルに一致を見ることはしたくありません。
またフルパスのITEMIDLISTをGetDisplayNameOf()を用いて文字列にする方法も試しま
したが、ファイル10万件走査+その他の属性情報の取得が1秒程度に対して、フルパス文
字列生成部分だけで3秒程度かかってしまうので可能な限りやりたくありません。
ITEMIDLISTから生成手順にかかわらず、ハッシュ値を求める良い方法がありましたら
よろしくお願いします。
※OnSHChangeNotifyは、SHChangeNotifyRegisterで登録したファイルなどの変更通知を
受信しています。
※他の事例として、単純PID取得でも同様にILIsEqual()でOKなのにSHITEMIDのabIDが異
なります。
※環境はVisual Studio2010、Windows Vista(x64)です。
※性能の部分はごく一般的なデスクトップPCを前提としています。
//ITEMIDLISTをフルパス変換
CString GetPathFromPIDL(DWORD pidl)
{
TCHAR sPath[MAX_PATH * 2];
CString strTemp = _T(");
if(SHGetPathFromIDList((struct _ITEMIDLIST *)pidl, sPath))
strTemp = sPath;
return strTemp;
}
//ハッシュ値の計算
UINT GetHash(LPITEMIDLIST lpi)
{
UINT nHash = 0;
{
LPITEMIDLIST lpidl = lpi;
for (;;)
{//ITEMIDLISTの中身はSHITEMIDなので、データの部分だけ取り出し
て計算に用いる
short len = lpidl->mkid.cb - sizeof(lpidl-
>mkid.cb);//ITTEMIDLISTのサイズ
BYTE* bData = lpidl->mkid.abID;
for (short i = 0; i < len; i++)
{
nHash += bData[i];//とりあえず効率は無視し
て全部足す
}
lpidl = reinterpret_cast<LPITEMIDLIST>(lpidl-
>mkid.abID - sizeof(lpidl->mkid.cb) + lpidl->mkid.cb);
if (!lpidl->mkid.cb)
break;
}
TRACE(LHASH = %x\n, nHash);
}
return nHash;
}
//シェル更新検出
LRESULT CFolderCheckWnd::OnSHChangeNotify(WPARAM wParam, LPARAM lParam)
{
LONG lEvent = 0;
LPITEMIDLIST* pidls = NULL;
HANDLE hLock = ::SHChangeNotification_Lock((HANDLE)wParam, (DWORD)
lParam, &pidls, &lEvent);
if (!hLock)
return NULL;
if (lEvent & SHCNE_RENAMEITEM)
{
GetHash((LPITEMIDLIST)pidls[1]);//リネーム後のハッシュ値
//ファイルがリネームされた通知(ITEMIDLIST)をフルパス表記に変
換
CString str1 = GetPathFromPIDL((DWORD)pidls[0]);//リネーム前
のITEMIDLIST
CString str2 = GetPathFromPIDL((DWORD)pidls[1]);//リネーム後
のITEMIDLIST
//さらにITEMIDLISTに変換
LPITEMIDLIST pld = ILCreateFromPath(str2);
GetHash(pld);//テキスト変換して作ったITEMIDLISTのハッシュ値の
計算
if (ILIsEqual(pidls[1], pld))//テキスト変換して作った
ITEMIDLISTと、通知できたITEMIDLISTを比較してみるテスト
TRACE(LITEMIDLISTの比較はTRUE\n);//ITEMIDLISTのバ
イナリ比較だと異なるのに必ず一致する(もちろん両方共に文字列に変換すると一致す
る)
else
TRACE(LITEMIDLISTの比較はFALSE\n);//こっちには来な
い
ILFree(pld);
}
if (hLock)
::SHChangeNotification_Unlock(hLock);
return NULL;
}
--------------実行結果--------------
ren I:\test\test.bmp test1.bmp
処理結果
HASH = 1b1e
HASH = 2078
ITEMIDLISTの比較はTRUE
そもそもハッシュ値を何に使いたいのでしょう?
そのあたりまで遡ると、よりベターな回答が出るかもしれません。
そもそもITEMIDLISTの中身を見れば比較できるならILIsEqual関数は
必要ないような気もしますね。何らかの比較方法が必要だからそういう関数が
実装されているとすると同じになる要素を拾ってハッシュ値を取らないと
うまく行かないような気もします。
ご回答ありがとうございます。
>そもそもハッシュ値を何に使いたいのでしょう?
用途も若干触れていますが、約10万個前後のファイルの一覧を既に実装しています。
その一覧からCMapのテンプレートを用いて、ITEMIDLIST-CListCtrlのインデックスNo
を作成するためにハッシュ値を求めたいのです。
具体例を上げますと、上記のシェルの更新通知(SHChangeNotificationなどは
ITEMIDLISTを返す)を受け取って、更新対象のアイテム探してリストを更新したいわけ
です。
(シェル通知の「ファイルの追加」は末尾に足しているのでソート順も不定になり2分
木なども使えない制限があります)
>何らかの比較方法が必要だからそういう関数が
>実装されているとすると同じになる要素を拾ってハッシュ値を取らないと
>うまく行かないような気もします。
全くその通りだと思います。
せめて要素の構造でも分かれば追跡のヒントになるのですが・・・という所で悩んで
おります。
SHITEMIDが定義の内容毎に割り振られるのではなくて、
定義内容を保持するインスタンスに対して割り振られると考えると
インスタンスのIDが違っていても内容は同じと言うケースは有りそうです。
結局、内容が同じ事を確認する為には定義内容を保持しているインスタンスの
中身(つまり定義内容その物)を確認しないといけないとすると話は
繋がりそうな気がします。
そうなると結局はフルパスのデータを取り出すしかないのかもしれません。
もしやるとすれば、フルパスのデータを取り出してそこからハッシュ値を
計算しておくくらいでしょうか。
データ収集時に効率よくハッシュ値を格納して置くようにして
それを使うくらいしかないかもしれませんね。
PATIOさんのご意見も一理あるように思えますが、D&Dなどデータ連携する際
にも普通に使われるものが、生成インスタンス依存という設計にされるであろうか?と
いう気がします。
また、シェル通知、ILCreateFromPath()で生成ごとにハッシュ値を取ると、それぞれ
何度繰り返し生成しても、異なるプロセスで生成しても、全く同じハッシュ値になるこ
とから、オブジェクトインスタンスであるということも考えにくいです。
むしろ上の事例では、実体の有無を見る見ないという違いがあるところから、ファイ
ルないしオブジェクトの属性でも持っているような気がしてきました。
とりあえず、属性情報の観点でもう一度調べて、ダメなようなら諦めて文字列変換し
ます。
ありがとうございました。
> SHITEMIDが定義の内容毎に割り振られるのではなくて、
> 定義内容を保持するインスタンスに対して割り振られる
とはどういうことでしょうか?
ポインタのように、指す実体は同じでも、インスタンスの値は毎回異なり得るというこ
と?
だとすれば、おそらくそうではないと思われます。
というのは、ILSaveStream とか ILLoadFromStreamEx といった関数があるからです。
abID の中身をダンプしてみるとわかりますが、ほとんど同じながら、一部に違うところ
があり、結果としてハッシュ値が異なるわけです。
この違うところというのは、ILIsEqual での比較には影響を与えない部分なのでしょ
う。
ちょっと気になる記述があったのですが…
http://msdn.microsoft.com/en-us/library/bb775062.aspx
を見ると、ITEMIDLIST にはファイルサイズや日付、属性なども入っているように見える
んですよね。
ただ、構造が明らかでないので、どこがそれなのかが不明ですけど。
さて、それはそれとして、ハッシュ値というのは本質的に重複しうるものですが、CMap
のキーとして使うには不安がありませんかね。
> またフルパスのITEMIDLISTをGetDisplayNameOf()を用いて文字列にする方法も
> 試しましたが、ファイル10万件走査+その他の属性情報の取得が1秒程度に対して、
> フルパス文字列生成部分だけで3秒程度かかってしまうので
> 可能な限りやりたくありません。
というのは、10万件に対して3秒ですかね。まさか1ファイルあたり3秒ってことはないで
すよね。
であれば、ファイルパスの文字列をキーにした CMap にしてしまえばよいのでは?
>ちょっと気になる記述があったのですが…
> http://msdn.microsoft.com/en-us/library/bb775062.aspx
>を見ると、ITEMIDLIST にはファイルサイズや日付、属性なども入っているように見え
るんですよね。
IL系の比較関数と比べてCompareIDs()が極端に早く、単純な文字列比較のAPIに匹敵す
る速度であることは認識していました。
もしかしたら、オブジェクトの実体を見に行っているのではなくデータのみを見てい
るとすれば納得がいきます。
>さて、それはそれとして、ハッシュ値というのは本質的に重複しうるものですが、
CMap
>のキーとして使うには不安がありませんかね。
もちろんキーはITEMIDLISTで、ハッシュマップのデータ構造で使用するハッシュ値を
求めるという事です。
具体的には、CMapのキーとしてITEMIDLISTをキーとする記述はテンプレートに存在し
ないので、ハッシュ関数部分を自作する必要があるというだけの事です。
>というのは、10万件に対して3秒ですかね。まさか1ファイルあたり3秒ってことはない
ですよね。
もちろん10万件で3秒です。
10万件走査+情報の取得の合計が約4秒、そのうちの3秒が文字列変換でボトルネック
になっているので、何とかならないものやらというのが発端です。
とりあえず、走査の対象となるディレクトリ郡のフルパスをあらかじめ求めておい
て、速度の速い相対名称の変換結果とくっつけることで希望する性能に近づけそうな事
が確認できました。
aetosさんのおっしゃるとおり別の解法になってしまいましたが、相談に乗っていただ
いた中で思いついたことです。
ありがとうございました。
> SHITEMIDが定義の内容毎に割り振られるのではなくて、
> 定義内容を保持するインスタンスに対して割り振られる
とはどういうことでしょうか?
ポインタのように、指す実体は同じでも、インスタンスの値は毎回異なり得るというこ
と?
と言うか、同じ情報を保持しているインスタンスが複数あるイメージでした。
外から弄れる情報用にクローンを作っているイメージです。
> abID の中身をダンプしてみるとわかりますが、ほとんど同じながら、一部に違うところ
> があり、結果としてハッシュ値が異なるわけです。
> この違うところというのは、ILIsEqual での比較には影響を与えない部分なのでしょ
う。
と言う事は最初の想像の方があたっていたと言う事ですかねぇ。
なにしろ裏づけまで取っているわけではなくて現象からのみの推測なので
外している可能性は結構あると思います。
すみません。
あうあう。
最初の部分は、引用を付け忘れてました。