今回は、どこが元ネタだったかはちょっと失念しましたが、掲示板で見かけた Thumbs.db のお話。
エクスプローラなどで「縮小」で表示した場合、Thumbs.db という隠しシステムファイルが作成され、二回目以降はここからサムネイルのキャッシュを抽出することで表示速度を上げています。これを我々も利用することはできないか。
実のところ、私自身はエクスプローラを(ファイラとしては)ほとんど使わないし、ましてや縮小表示など、という人なのであんまり意味は無いんですが。
そういう質問に対し、どなたかが IShellImageStore インターフェイスを使えばどうか、と提案なさっていたのが目に留まったので、早速調べてみました。なかなか情報が少なかったんですが。
MSDN には IShellImageStore の解説 と サンプルコード が挙げられています。と言うか参考になりそうな記述はほぼこれだけです。あとは最新版の Windows SDK のヘッダファイルでインターフェイスの構造や GUID を調べるぐらい。
そんなに複雑なものでもないしコーディング自体は割りとあっさり終わりました。画像も取得できます。まずそれを提示しましょう。
// .NET Framework 2.0 以降限定コードです // 例外処理やらなにやらを考慮してません using System; using System.Drawing; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; public class ThumbnailPicker : IDisposable { private IShellImageStore store; private int lockValue; public ThumbnailPicker(string directory) { this.store = new IShellImageStore(); IPersistFile file = (IPersistFile)this.store; file.Load(directory, 0); this.store.Open(0, out this.lockValue); } public Image GetThumbnail(string fileName) { long time; if (this.store.IsEntryInStore(fileName, out time) == 0) { IntPtr hbmp; this.store.GetEntry(fileName, 0, out hbmp); Image retval = Image.FromHbitmap(hbmp); DeleteObject(hbmp); return retval; } return null; } public void Dispose() { this.store.Close(ref this.lockValue); } [DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr obj); [ComImport, Guid("1EBDCF80-A200-11d0-A3A4-00C04FD706EC")] private class ShellThumbnailDiskCache { } [ComImport, Guid("48C8118C-B924-11d1-98D5-00C04FB687DA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), CoClass(typeof(ShellThumbnailDiskCache))] private interface IShellImageStore { [PreserveSig] int Open(int mode, out int lockValue); void Create(); void ReleaseLock(); [PreserveSig] int Close([In] ref int lockValue); void Commit(); void IsLocked(); void GetMode(); void GetCapabilities(); void AddEntry(); [PreserveSig] int GetEntry( [In, MarshalAs(UnmanagedType.LPWStr)] string name, int mode, out IntPtr image); void DeleteEntry( [In, MarshalAs(UnmanagedType.LPWStr)] string name); [PreserveSig] int IsEntryInStore( [In, MarshalAs(UnmanagedType.LPWStr)] string name, out long timeStamp); } }
解説するほども無いコードですので、使用法だけ。コンストラクタでディレクトリのパスを指定します。このディレクトリには Thumbs.db が含まれている必要があります(ファイル名決め撃ちです)。あとは、サムネイルを取り出したい画像のファイル名(拡張子つき、ディレクトリ名なし)を指定して GetThumbnail メソッドで取得するだけです。それから、Dispose を呼び出さない限りアプリが終了するまで Thumbs.db は書き込みに対するロックが掛かりっぱなしになります。あと、Windows 2000 以降限定だそうです(XP SP2 でしか試してません。Vista でどうなるかも謎。そもそも Vista って Thumbs.db 作られるんですか?)。
で、致命的な問題点が。
オンラインの MSDN には、IShellImageStore のメンバとして Open、Commit、GetEntry、IsEntryInStore、Close、の五つが記載されています。しかし、該当ヘッダファイルである shlobj.h を見ると IShellImageStore にはその他にも大量にメンバが定義されています。Create、ReleaseLock、IsLocked、GetMode、GetCapabilities、AddEntry、DeleteEntry、Enum、がそうです。私のコードにも宣言だけは出てますね(Enum は IsEntryInStore の後ろなんで宣言すらしてませんが)。サムネイルを参照するだけなので Create だの AddEntry だのは正直どうでもいいんですが、Enum が何故書かれていないんでしょうか。まあ画像のキャッシュを表示するだけなら、既にディレクトリに存在していないファイルのキャッシュがあっても使う訳ないんで不要っちゃ不要ですが、プログラマから見た場合これは是非とも欲しい機能でしょう。
shlobj.h には Enum の引数である IEnumShellImageStore インターフェイスも当然定義されてあります。このまま流用すればいいでしょう。ちなみに、この IEnumShellImageStore を使って列挙できる情報は、ファイル名とタイムスタンプです。
問題点はここです。Enum は成功して IEnumShellImageStore は取得できるんですが、何故かこのインターフェイスに対して Skip メソッドと Next メソッドが失敗を返しやがります。Next メソッドだけなら「なんか宣言間違ったか?」ですが Skip メソッドが失敗するとなるとこれはもう実装してないって事なんでしょうねきっと。いや、私のコードがまずい可能性も大いにありますけど。
と暗澹たる気持ちに浸っていたら、さらに追い討ちをかけるがごとくこんなページ発見。
PeteDavis.NET - ThumbDBLib A C# library for reading thumbs.db files
あ、これで解決ですね。
ありがたいことにコードも公開してくれているのでざっと調べてみましたが、IStorage を使って Thumbs.db の Catalog をストリームとして開き、その後頑張って解析してるみたいです。やはり最後に物を言うのは腕力?


不具合が発生するようです
彼のHPのコメント欄に、その指摘と
修正コードがのっているようです
実のところここに書き散らかしているコードのほとんど(この記事を含む)は私自身は使ってないのでアレなんですが(w;