.Net读取XP文件夹中的Thumbs.db文件
一般在XP文件夾里面,特別是圖片和視頻文件夾里有一個文件—Thumbs.db文件。這個文件是XP用來緩存圖片和影音文件的縮略圖的,有了這個文件,XP在打開保存大量圖片文件的文件夾的時候,顯示速度會明顯比沒有Thumbs.db文件的文件夾快—因為后者需要實時生成縮略圖。
最近在做一個自己的圖片管理程序,需要快速生成縮略圖,就想到復(fù)用這個文件,這樣我的程序可以無縫地繼承視窗系統(tǒng)的資源管理器功能。因為Thumbs.db文件的文件結(jié)構(gòu)和訪問API沒有被公開,所以在Google查了一些資料,發(fā)現(xiàn)Thumbs.db文件采用的是結(jié)構(gòu)化存儲文件(Structured Storage File)結(jié)構(gòu),這個文件在COM時代非常的流行,不知道為什么在.Net里面,微軟把這個文件結(jié)構(gòu)扔掉了。
結(jié)構(gòu)化存儲概述
結(jié)構(gòu)化存儲文件結(jié)構(gòu)說白了就是一個保存在文件里面的文件系統(tǒng),就是說在一個結(jié)構(gòu)化存儲文件里面,保存有“文件夾”信息,也保存有“文件”信息和其內(nèi)容。例如,我們熟悉的Winrar的打包多個文件的過程,就可以使用結(jié)構(gòu)化存儲文件結(jié)構(gòu)來保存(當(dāng)然啦,我沒有Winrar的源代碼,不是說Winrar就是這樣實現(xiàn)打包的啊)。
使用結(jié)構(gòu)化存儲文件的一個好處是,使得更新文件內(nèi)容非常方便。 舉個例子,比如我們?nèi)粘J褂玫?/span>Word吧,當(dāng)我們編輯一個文件的時候,如果Word采用的順序存儲結(jié)構(gòu)—文件內(nèi)容是按照內(nèi)容的邏輯結(jié)構(gòu)順序存儲在磁盤里的,即在硬盤里,第一頁保存在第二頁的前面。順序存儲方式的問題在于,它使得修改Word文檔的時候,會變得非常麻煩。假設(shè)你的文檔有幾千頁,當(dāng)你增刪第一頁的內(nèi)容的時候,順序存儲的方式就要求你必須移動后面幾千頁內(nèi)容—可以想象到這個過程有多慢了。 如果我們將Word文檔看作一個小的文件系統(tǒng)的話,那么對于文檔中的每一頁我們可以看成是一個“文件夾”,然后所有的文字段落可以看成是“文件夾”里面的文件。如果文檔里面插入了圖片的話,可以另外在“文件夾”里創(chuàng)建一個小的文件夾—“圖片”文件夾,而在使用到這個圖片的位置上加入一個快捷方式鏈接到每一頁的內(nèi)容里就可以了。下圖演示了前一段描述的概念(注意-我沒有看到Office的源代碼,上述內(nèi)容只不過是我的一個小猜想而已):?
結(jié)構(gòu)化存儲文件的COM接口
剛才講完了概念,在COM中,IStorage接口就相當(dāng)于結(jié)構(gòu)化存儲文件中的 “文件夾”,而IStream接口就是“文件”啦。下面就是IStorage的接口:
| MIDL_INTERFACE("0000000b-0000-0000-C000-000000000046") IStorage : public IUnknown { public: ??? virtual HRESULT STDMETHODCALLTYPE CreateStream( ??????? /* [string][in] */ __RPC__in const OLECHAR *pwcsName, ??????? /* [in] */ DWORD grfMode, ??????? /* [in] */ DWORD reserved1, ??????? /* [in] */ DWORD reserved2, ??????? /* [out] */ __RPC__deref_out_opt IStream **ppstm) = 0; ??? ??? virtual /* [local] */ HRESULT STDMETHODCALLTYPE OpenStream( ??????? /* [string][in] */ const OLECHAR *pwcsName, ??????? /* [unique][in] */ void *reserved1, ??????? /* [in] */ DWORD grfMode, ??????? /* [in] */ DWORD reserved2, ??????? /* [out] */ IStream **ppstm) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE CreateStorage( ??????? /* [string][in] */ __RPC__in const OLECHAR *pwcsName, ??????? /* [in] */ DWORD grfMode, ??????? /* [in] */ DWORD reserved1, ??????? /* [in] */ DWORD reserved2, ??????? /* [out] */ __RPC__deref_out_opt IStorage **ppstg) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE OpenStorage( ??????? /* [string][unique][in] */ __RPC__in_opt const OLECHAR *pwcsName, ??????? /* [unique][in] */ __RPC__in_opt IStorage *pstgPriority, ??????? /* [in] */ DWORD grfMode, ??????? /* [unique][in] */ __RPC__deref_opt_in_opt SNB snbExclude, ??????? /* [in] */ DWORD reserved, ??????? /* [out] */ __RPC__deref_out_opt IStorage **ppstg) = 0; ??? ??? virtual /* [local] */ HRESULT STDMETHODCALLTYPE CopyTo( ??????? /* [in] */ DWORD ciidExclude, ??????? /* [size_is][unique][in] */ const IID *rgiidExclude, ??????? /* [unique][in] */ SNB snbExclude, ??????? /* [unique][in] */ IStorage *pstgDest) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE MoveElementTo( ??????? /* [string][in] */ __RPC__in const OLECHAR *pwcsName, ??????? /* [unique][in] */ __RPC__in_opt IStorage *pstgDest, ??????? /* [string][in] */ __RPC__in const OLECHAR *pwcsNewName, ??????? /* [in] */ DWORD grfFlags) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE Commit( ??????? /* [in] */ DWORD grfCommitFlags) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE Revert( void) = 0; ??? ??? virtual /* [local] */ HRESULT STDMETHODCALLTYPE EnumElements( ??????? /* [in] */ DWORD reserved1, ??????? /* [size_is][unique][in] */ void *reserved2, ??????? /* [in] */ DWORD reserved3, ??????? /* [out] */ IEnumSTATSTG **ppenum) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE DestroyElement( ??????? /* [string][in] */ __RPC__in const OLECHAR *pwcsName) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE RenameElement( ??????? /* [string][in] */ __RPC__in const OLECHAR *pwcsOldName, ??????? /* [string][in] */ __RPC__in const OLECHAR *pwcsNewName) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE SetElementTimes( ??????? /* [string][unique][in] */ __RPC__in_opt const OLECHAR *pwcsName, ??????? /* [unique][in] */ __RPC__in_opt const FILETIME *pctime, ??????? /* [unique][in] */ __RPC__in_opt const FILETIME *patime, ??????? /* [unique][in] */ __RPC__in_opt const FILETIME *pmtime) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE SetClass( ??????? /* [in] */ __RPC__in REFCLSID clsid) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE SetStateBits( ??????? /* [in] */ DWORD grfStateBits, ??????? /* [in] */ DWORD grfMask) = 0; ??? ??? virtual HRESULT STDMETHODCALLTYPE Stat( ??????? /* [out] */ __RPC__out STATSTG *pstatstg, ??????? /* [in] */ DWORD grfStatFlag) = 0; }; |
?
注意上面的定義里面,[Create/Open]Stream就是創(chuàng)建和打開“文件”的方式,而 [Create/Open]Storage就是創(chuàng)建和打開“文件夾”的方式—“文件夾”里面不是可以包含其他的文件夾嗎?下面是IStream接口的定義:
| ??? MIDL_INTERFACE("0000000c-0000-0000-C000-000000000046") ??? IStream : public ISequentialStream ??? { ??? public: ??????? virtual /* [local] */ HRESULT STDMETHODCALLTYPE Seek( ??????????? /* [in] */ LARGE_INTEGER dlibMove, ??????????? /* [in] */ DWORD dwOrigin, ??????????? /* [out] */ ULARGE_INTEGER *plibNewPosition) = 0; ??????? ?????? ?virtual HRESULT STDMETHODCALLTYPE SetSize( ??????????? /* [in] */ ULARGE_INTEGER libNewSize) = 0; ??????? ??????? virtual /* [local] */ HRESULT STDMETHODCALLTYPE CopyTo( ??????????? /* [unique][in] */ IStream *pstm, ??????????? /* [in] */ ULARGE_INTEGER cb, ??????????? /* [out] */ ULARGE_INTEGER *pcbRead, ??????????? /* [out] */ ULARGE_INTEGER *pcbWritten) = 0; ??????? ??????? virtual HRESULT STDMETHODCALLTYPE Commit( ??????????? /* [in] */ DWORD grfCommitFlags) = 0; ??????? ??????? virtual HRESULT STDMETHODCALLTYPE Revert( void) = 0; ??????? ??????? virtual HRESULT STDMETHODCALLTYPE LockRegion( ??????????? /* [in] */ ULARGE_INTEGER libOffset, ??????????? /* [in] */ ULARGE_INTEGER cb, ??????????? /* [in] */ DWORD dwLockType) = 0; ??????? ??????? virtual HRESULT STDMETHODCALLTYPE UnlockRegion( ??????????? /* [in] */ ULARGE_INTEGER libOffset, ??????????? /* [in] */ ULARGE_INTEGER cb, ??????????? /* [in] */ DWORD dwLockType) = 0; ??????? ??????? virtual HRESULT STDMETHODCALLTYPE Stat( ??????????? /* [out] */ __RPC__out STATSTG *pstatstg, ??????????? /* [in] */ DWORD grfStatFlag) = 0; ??????? ??????? virtual HRESULT STDMETHODCALLTYPE Clone( ??????????? /* [out] */ __RPC__deref_out_opt IStream **ppstm) = 0;?? ??? }; |
?
?IStream的用法跟.Net里面的System.IO.Stream的用法類似,其中IStream::Commit函數(shù)的作用就是將內(nèi)存中的修改保存到硬盤中。
一般來說,結(jié)構(gòu)化存儲文件的“文件夾”IStorage里面都會有一個IStream保存該“文件夾”的目錄—即說明“文件夾”里面有哪些文件。
Thumbs.db文件的文件描述
既然我們已經(jīng)知道IStorage和IStream的概念和用法了,回過頭來看看Thumbs.db文件,Thumbs.db文件中有一個名稱為“Catalog”的 IStream保存了整個Thumbs.db文件里面緩存的縮略圖的文件名列表。
它包含兩段內(nèi)容,第一段內(nèi)容的結(jié)構(gòu)叫做CatalogHeader(當(dāng)然這也是我們隨便取的—因為微軟并沒有公開Thumbs.db的API),保存了所有縮略圖的大小,是32x32的,還是64x64之類的,另外還有一個重要的變量保存了縮略圖文件的個數(shù)。下面是這個數(shù)據(jù)結(jié)構(gòu)的聲明,因為沒有對應(yīng)的COM API,所以我們直接在C#中聲明了。
| ??? [Interop.StructLayout(Interop.LayoutKind.Sequential)] ??? public struct CatalogHeader ??? { ??????? public short Reserved1; ? ??????? public short Reserved2; ? ??????? public int ThumbCount; ? ??????? public int ThumbWidth; ? ??????? public int ThumbHeight; ??? } |
?
注意聲明上面的StructLayout屬性,由于.Net是即時編譯的系統(tǒng),在編譯的過程當(dāng)中,通常情況下,JIT會根據(jù)當(dāng)前系統(tǒng)內(nèi)存和CPU的架構(gòu),為結(jié)構(gòu)生成最優(yōu)的內(nèi)存布局以便在訪問結(jié)構(gòu)體的時候能夠達到最快的速度—因此JIT可能會調(diào)整結(jié)構(gòu)的一些成員在內(nèi)存布局的順序。 由于我們是在讀取COM生成的數(shù)據(jù),C++編譯器可沒有做到這一點,所以LayoutKind.Sequential告訴JIT編譯器,不要隨意更改結(jié)構(gòu)成員在內(nèi)存中的布局。而ReveredX屬性的存在是因為這個結(jié)構(gòu)是我們猜的結(jié)構(gòu),前兩個屬性沒猜出來。
第二段內(nèi)容就是縮略圖的“文件名”信息了,除了名字以外,還保存了縮略圖生成的時間—以便同名文件更新的時候可以生成新的縮略圖,還有一個莫名其妙的 ItemId—估計是用來提高檢索縮略圖速度的,當(dāng)然還有兩個沒猜出來的屬性。下面是這個成員的結(jié)構(gòu)定義:
| ??? [Interop.StructLayout(Interop.LayoutKind.Sequential)] ??? public struct CatalogItem ??? { ??????? public int Reserved1; ? ??????? private int m_ItemId; ??????? public int ItemId ??????? { ????? ??????get { return m_ItemId; } ??????????? set ??????????? { ??????????????? m_ItemId = value; ??????????????? BuildItemIdString(m_ItemId); ??????????? } ??????? } ? ??????? public DateTime Modified; ? ??????? public string FileName; ? ??????? public short Reserved2; ? ??????? // 自己添加的新域 ??????? public string ItemIdString ??????? { ??????????? get; ??????????? private set; ??????? } ? ??????? private void BuildItemIdString(int itemId) ??????? { ??????????? var temp = itemId.ToString(); ??????????? var buffer = new char[temp.Length]; ??????????? for (int i = 0; i < temp.Length; ++i) ??????????????? buffer[i] = temp[temp.Length - i - 1]; ? ??????????? ItemIdString = new string(buffer); ??????? } ??? } |
?
不知道是什么原因,在Thumbs.db文件當(dāng)中,數(shù)據(jù)都是以倒序保存的,比如字符串就是倒序的, 而整形的四個字節(jié)也是倒序排列的—難道微軟真的不想讓第三方程序員訪問Thumbs.db文件?
Thumbs.db文件的讀取
既然已經(jīng)知道文件結(jié)構(gòu),訪問的方式就不多講了,無非就是先用StgOpenStorage函數(shù)打開結(jié)構(gòu)化存儲文件,獲取IStorage接口的引用,讀取“Catalog”獲得Thumbs.db文件的目錄,接著獲得每一個縮略圖“文件名”對應(yīng)的CatalogItem,使用CatalogItem的倒序ItemId拿到具體縮略圖的IStream指針,然后通過IStream::Read的方法來讀取縮略圖的內(nèi)容,最后顯示在窗體上。唯一要注意的是,每一個縮略圖IStream的前12個字節(jié)(3個整形)不是縮略圖的內(nèi)容,不能用的,因此在讀取的時候跳過那三個字節(jié)好了。
因為.Net只提供了IStream的定義,而IStorage的定義需要我們自己生成。這個接口手工編寫.Net對應(yīng)的接口有點麻煩,因此建議去http://www.pinvoke.net/ 去搜索別人已經(jīng)寫好的定義。
代碼下載:
/Files/killmyday/ThumbLib.zip
總結(jié)
以上是生活随笔為你收集整理的.Net读取XP文件夹中的Thumbs.db文件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 京东2019年全年业绩财报 净营收达到
- 下一篇: ip地址管理与子网的划分二