一般在XP文件夹里面,特别是图片和视频文件夹里有一个文件—Thumbs.db文件。这个文件是XP用来缓存图片和影音文件的缩略图的,有了这个文件,XP在打开保存大量图片文件的文件夹的时候,显示速度会明显比没有Thumbs.db文件的文件夹快—因为后者需要实时生成缩略图。

最近在做一个自己的图片管理程序,需要快速生成缩略图,就想到复用这个文件,这样我的程序可以无缝地继承视窗系统的资源管理器功能。因为Thumbs.db文件的文件结构和访问API没有被公开,所以在Google查了一些资料,发现Thumbs.db文件采用的是结构化存储文件(Structured Storage File)结构,这个文件在COM时代非常的流行,不知道为什么在.Net里面,微软把这个文件结构扔掉了。

结构化存储概述

结构化存储文件结构说白了就是一个保存在文件里面的文件系统,就是说在一个结构化存储文件里面,保存有“文件夹”信息,也保存有“文件”信息和其内容。例如,我们熟悉的Winrar的打包多个文件的过程,就可以使用结构化存储文件结构来保存(当然啦,我没有Winrar的源代码,不是说Winrar就是这样实现打包的啊)。

使用结构化存储文件的一个好处是,使得更新文件内容非常方便。 举个例子,比如我们日常使用的Word吧,当我们编辑一个文件的时候,如果Word采用的顺序存储结构—文件内容是按照内容的逻辑结构顺序存储在磁盘里的,即在硬盘里,第一页保存在第二页的前面。顺序存储方式的问题在于,它使得修改Word文档的时候,会变得非常麻烦。假设你的文档有几千页,当你增删第一页的内容的时候,顺序存储的方式就要求你必须移动后面几千页内容—可以想象到这个过程有多慢了。 如果我们将Word文档看作一个小的文件系统的话,那么对于文档中的每一页我们可以看成是一个“文件夹”,然后所有的文字段落可以看成是“文件夹”里面的文件。如果文档里面插入了图片的话,可以另外在“文件夹”里创建一个小的文件夹—“图片”文件夹,而在使用到这个图片的位置上加入一个快捷方式链接到每一页的内容里就可以了。下图演示了前一段描述的概念(注意-我没有看到Office的源代码,上述内容只不过是我的一个小猜想而已):

结构化存储文件的COM接口

刚才讲完了概念,在COM中,IStorage接口就相当于结构化存储文件中的 “文件夹”,而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就是创建和打开“文件”的方式,而 [Create/Open]Storage就是创建和打开“文件夹”的方式—“文件夹”里面不是可以包含其他的文件夹吗?下面是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函数的作用就是将内存中的修改保存到硬盘中。

一般来说,结构化存储文件的“文件夹”IStorage里面都会有一个IStream保存该“文件夹”的目录—即说明“文件夹”里面有哪些文件。

Thumbs.db文件的文件描述

既然我们已经知道IStorage和IStream的概念和用法了,回过头来看看Thumbs.db文件,Thumbs.db文件中有一个名称为“Catalog”的 IStream保存了整个Thumbs.db文件里面缓存的缩略图的文件名列表。

它包含两段内容,第一段内容的结构叫做CatalogHeader(当然这也是我们随便取的—因为微软并没有公开Thumbs.db的API),保存了所有缩略图的大小,是32x32的,还是64x64之类的,另外还有一个重要的变量保存了缩略图文件的个数。下面是这个数据结构的声明,因为没有对应的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是即时编译的系统,在编译的过程当中,通常情况下,JIT会根据当前系统内存和CPU的架构,为结构生成最优的内存布局以便在访问结构体的时候能够达到最快的速度—因此JIT可能会调整结构的一些成员在内存布局的顺序。 由于我们是在读取COM生成的数据,C++编译器可没有做到这一点,所以LayoutKind.Sequential告诉JIT编译器,不要随意更改结构成员在内存中的布局。而ReveredX属性的存在是因为这个结构是我们猜的结构,前两个属性没猜出来。

第二段内容就是缩略图的“文件名”信息了,除了名字以外,还保存了缩略图生成的时间—以便同名文件更新的时候可以生成新的缩略图,还有一个莫名其妙的 ItemId—估计是用来提高检索缩略图速度的,当然还有两个没猜出来的属性。下面是这个成员的结构定义:

[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文件当中,数据都是以倒序保存的,比如字符串就是倒序的, 而整形的四个字节也是倒序排列的—难道微软真的不想让第三方程序员访问Thumbs.db文件?

Thumbs.db文件的读取

既然已经知道文件结构,访问的方式就不多讲了,无非就是先用StgOpenStorage函数打开结构化存储文件,获取IStorage接口的引用,读取“Catalog”获得Thumbs.db文件的目录,接着获得每一个缩略图“文件名”对应的CatalogItem,使用CatalogItem的倒序ItemId拿到具体缩略图的IStream指针,然后通过IStream::Read的方法来读取缩略图的内容,最后显示在窗体上。唯一要注意的是,每一个缩略图IStream的前12个字节(3个整形)不是缩略图的内容,不能用的,因此在读取的时候跳过那三个字节好了。

因为.Net只提供了IStream的定义,而IStorage的定义需要我们自己生成。这个接口手工编写.Net对应的接口有点麻烦,因此建议去http://www.pinvoke.net/ 去搜索别人已经写好的定义。

代码下载:

/Files/killmyday/ThumbLib.zip

.Net读取XP文件夹中的Thumbs.db文件相关推荐

  1. python使用os.listdir或者os.walk读取多张图片的文件夹出现一个Thumbs.db文件

    这个文件导致通过对文件名处理时的意外,Thumbs.db 参考:http://www.xitongcheng.com/jiaocheng/dnrj_article_15244.html 这个文件网上查 ...

  2. matlab打开bmp,Matlab 读取文件夹中所有的bmp文件

    将srcimg文件下的bmp文件转为jpg图像,存放在dstimg文件夹下 str = 'srcimg'; dst = 'dstimg'; file=dir([str,'\*.bmp']); :len ...

  3. Python判断两个文件夹中互相不同的文件有哪些、判断一个文件夹相对于另外一个文件夹缺少了哪些文件

    Python判断两个文件夹中互相不同的文件有哪些.判断一个文件夹相对于另外一个文件夹缺少了哪些文件 目录 Python判断两个文件夹中互相不同的文件有哪些.判断一个文件夹相对于另外一个文件夹缺少了哪些 ...

  4. git在已忽略文件夹中不忽略指定文件

    注意:结尾的星号不能去,去掉就无效了 1.在已忽略文件夹中不忽略指定文件夹 /node_modules/* !/node_modules/layer/ 2.在已忽略文件夹中不忽略指定文件 /node_ ...

  5. 批量替换一个文件中的文件名,例如将文件夹中s**_abnormal.jpg文件修改为s**_abnor.jpg

    文章涉及到StringAbout::开头的函数具体实现参考:string与Cstring字符串类型转换和其他操作总结 CProcessFile::开头的函数实现参考:文件读写操作工具类CProcess ...

  6. java压缩zip文件夹错误_Java将文件或者文件夹压缩成zip(修复文件夹中存在多个文件报Stream Closed错误问题)...

    项目场景: Java将文件或者文件夹压缩成zip(修复文件夹中存在多个文件报Stream Closed错误问题) 问题描述: 最近的项目需要将多级文件夹压缩成zip,网上找了几个工具类,都会报错,所以 ...

  7. 失物招领php_我的Mac上“失物招领”文件夹中的大型iNode文件是什么?

    失物招领php If you're trying to figure out what's taking up space on your Mac, you might stumble upon so ...

  8. 使用cmd命令在桌面上新建一个文件夹,且在文件夹中新建一个.java文件,使用cmd命令实现.java文件的编译运行

    标题使用cmd命令在桌面上新建一个文件夹,且在文件夹中新建一个.java文件,使用cmd命令实现.java文件的编译运行 1.在电脑桌面使用快捷键win+r打开 2.打开cmd命令界面之后使用md d ...

  9. python批量读取文件夹中的所有excel文件-python遍历文件夹下所有excel文件

    大数据处理经常要用到一堆表格,然后需要把数据导入一个list中进行各种算法分析,简单讲一下自己的做法: 1.如何读取excel文件 网上的版本很多,在xlrd模块基础上,找到一些源码: import ...

最新文章

  1. 移动端调取摄像头上面如何给出框_飞桨实战笔记:自编写模型如何在服务器和移动端部署...
  2. c语言冒泡法加逗号,Ubuntu 10.04 LTS 无法添加计算机的问题
  3. MySQL 5.7建表时date类型提示默认值类型错误的问题处理
  4. HDU4302(map的用法)
  5. 实现ftoa与itoa
  6. 0xff00是65208
  7. 58 - II. 左旋转字符串
  8. CVE-2016-3714-ImageMagick 漏洞利用
  9. 3月28日 simulink学习(一)
  10. 聊聊rsocket load balancer的Ewma
  11. 斐波那契数列的三种解法
  12. 用vs2008编写和调试linux程序 ----VisualGDB 使用教程
  13. 如何解决No EPCS layout data --- looking for section [EPCS-XXXXXX]
  14. Thinkpad E430C关闭触摸板(Ubuntu)
  15. Java异常泄露敏感信息_浅谈“异常信息泄露(应用程序错误)”
  16. 修复win7更新服务器失败,Win7旗舰版系统Windows Update更新提示遇到未知错误的解决方法...
  17. R语言做复杂金融产品的几何布朗运动的模拟
  18. 【ME909】华为ME909 4G LTE模块在树莓派下通过minicom进行发送短信演示
  19. Python 数据可视化--Seaborn绘图总结1
  20. elasticsearch查询关键字slop

热门文章

  1. Note6:batch file programming
  2. RMAN异机恢复步骤及故障处理
  3. vCenter功能基本介绍
  4. vscode使用汇总——常用插件、常用配置、常用快捷键
  5. GSON使用笔记(1) -- 序列化时排除字段的几种方式
  6. 在python中配置MySQL数据库
  7. JavaScript学习 九、事件
  8. Android使用 LruCache 缓存图片
  9. 统计《ASP.Net特供视频教程》总长度
  10. 社会大学的作业,活动篇