系统图像列表----Shell_GetImageLists函数

来源:互联网 发布:cacti监控linux服务器 编辑:程序博客网 时间:2024/05/29 16:12

分享一下,原文:http://blog.titilima.com/show-212-1.html

介绍

系统图像列表(有时亦被称作 Shell 图标缓存)是一个由 Windows Shell 维护的图标资源,资源管理器和其它应用程序使用这个列表来显示系统对象、程序和文件类型的图标。

这个列表其实就是一个简单的 HIMAGELIST(可以用图像列表 API 存取的标准图像列表),一些应用程序可能会发现显示系统提供的图标更好一些,而不是自己内部存储这些图标的副本。所以,本教程的目的就是解说如何存取系统的图像列表,并在你的应用程序里使用它。

简单的方法

如果你先前从未花时间浏览过 MSDN 的话,你可能就会调用 SHGetFileInfo API(由 shell32.dll 导出,Win9x/WinNT4/2000/XP 都可使用)。这个函数看起来是这样的:

DWORD_PTR SHGetFileInfo(
    LPCTSTR     pszPath,              
    DWORD       dwFileAttributes,
    SHFILEINFO *psfi,
    UINT        cbFileInfo,
    UINT        uFlags
);

此函数可被用来获得指定文件的信息,uFlags 参数则指定了要获取的具体哪些信息。对于这个参数来说,有两个非常有趣的取值: SHGFI_ICON 和 SHGFI_SYSICONINDEX 。当指定了这两个值之后,SHGetFileInfo 会返回系统图像列表的句柄,并将相应的图标类型索引保存到 SHFILEINFO 结构中。

下面的调用示范了可能的使用方法:

SHFILEINFO shfi;
HIMAGELIST hSysImgList;
...
hSysImgList = (HIMAGELIST)SHGetFileInfo(
       "C:", 
       0, 
       &shfi, 
       sizeof(SHFILEINFO), 
       SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ICON);

平台差异

在 Windows 9x 和 Windows NT 系列的操作系统之间,存在一个很微小但又十分重要的区别。在 Windows 9x 下,SHGetFileInfo 返回系统图像列表的句柄,这个句柄是被所有进程共享的。这个图像列表与资源管理器所使用的以及 Shell 用来显示系统图标的图像列表是同一个图像列表,这就意味着如果一个进程对这个图像列表进行了任何的误操作,那么所有的进程(包括资源管理器)都会受到影响。

在 Windows NT (以及 2000/XP )之下,就会略有不同。由于系统架构的不同,亦为保持一个稳定的操作环境,SHGetFileInfo 会为每个请求系统图像列表的进程返回一份单独的拷贝。详而述之,SHGetFileInfo 并不会像 Windows 9x 那样返回一个完整的系统图像列表——只有在 SHGetFileInfo 调用中所请求的图标会出现在返回的图像列表中。对于 99% 的情况来说并没有什么关系,但是由于我们要制作一个浏览整个图像列表的应用程序,这就向我们提出了一个问题。

在所有的 Windows 版本中定位系统图像列表

闲话休叙,在 shell32.dll 中有一个名为 Shell_GetImageLists 的未公开 API ,——你可能猜到了,它会返回系统图像列表的句柄(注意函数名的复数形式,有大图标和小图标的版本)。这个 shell32.dll 中未公开的 API 适用于所有的 Windows 版本。

BOOL WINAPI Shell_GetImageLists(HIMAGELIST *lphimlLarge, HIMAGELIST *lphimlSmall);

我们需要的另一个 API 是 FileIconInit, 它只适用于 Windows NT/2000/XP 。这不是问题,因为我们只需要在 Windows NT 下调用它。

BOOL WINAPI FileIconInit(BOOL bFullInit);

这些 API 并未用函数名导出,而只有序号:

; shell32 未公开的函数
Shell_GetImageLists @71
FileIconInit        @660

获得了这一信息后,我们现在可以编写一个函数来返回系统图像列表的句柄了。第一步是定义两个 typedef ,以使我们的工作更容易些。

typedef BOOL (WINAPI * SH_GIL_PROC)(HIMAGELIST *phLarge, HIMAGELIST *phSmall);
typedef BOOL (WINAPI * FII_PROC)   (BOOL fFullInit);

SH_GIL_PROC 是 Shell_GetIconLists_Procecedure 简写,同理 FII_PROC 是 FileIconInit_Procedure 的简写。现在我们可以定位这些 shell32.dll 未公开的的函数了,以便调用。

HMODULE      hShell32;
SH_GIL_PROC  Shell_GetImageLists;
FII_PROC     FileIconInit;    

// 装载 shell32.dll ,如果它尚未装载的话
hShell32 = LoadLibrary("shell32.dll");

if(hShell32 == 0)
    return FALSE;

//
// 从 shell32.dll 中获取未公开的 API
//
Shell_GetImageLists  = (SH_GIL_PROC) GetProcAddress(hShell32, (LPCSTR)71);
FileIconInit         = (FII_PROC)    GetProcAddress(hShell32, (LPCSTR)660);

请注意我们是如何把序号参数传给 GetProcAddress 的。OK ,现在我们已经获得了所需的函数指针,可以进行处理了。

// 初始化本进程的图像列表——在 Win95/98 下函数是不可用的
if(FileIconInit != 0)
    FileIconInit(TRUE);

// 获得大图标和小图标的系统图像列表句柄
Shell_GetImageLists(phLarge, phSmall);

// 不要卸载 shell32.dll !

上面代码段最后的注释指出“不要卸载 shell32.dll ”,这是非常重要的。因为只要 shell32.dll 被卸载,系统图像列表就会被销毁,这样一来我们是前的所有工作都会付之东流。

永远不要修改或删除系统图像列表!

在我们往下进行之前,你应该了解另外一条重要的信息。你永远不要去尝试在系统图像列表中添加、删除图标,也不要删除系统图像列表。这一点在 SHGetFileInfo 的文档中已经解释得很清楚了,而且这也是个很好的意见。但是,仍然有一个很简单的方法会将系统图像列表误删。

现在来说这个问题。在默认情况下,当一个列表视图(ListView)控件被销毁后,它会自动调用 ImageList_Destroy API 来删除与之关联的图像列表。也就是说,在默认的情况下你甚至要求你的程序删除系统图像列表!——是的,当时就是这样。所幸,列表视图控件有一个 LVS_SHAREIMAGELISTS 样式可以防止它删除它的图像列表。(在这种情况下,请确认你设置了这个样式!)

十分诡异,树型视图(TreeView)控件却不会引起这一问题—— MSDN 指出树型视图不会销毁与之关联的图像列表,所以只有列表视图控件需要特别注意。
显然,如果你的程序运行在 Windows NT/2000/XP 下,那么误删系统图像列表并不会有很严重的后果,因为你只是删除了自己进程中图像列表的拷贝。但是,在 Windows 95/98/ME 下,删除这个图像列表会产生灾难性的后果。不信就试试!

结论

获取完整的系统图像列表本是一件非常棘手的事,尤其是如果我们不得不从缓存图标的文件中提取图标的话。所幸,这一对未公开的 API 函数可以帮我们做这些事情。当然,我不能把发现这些的功劳记在自己身上——我是在 James Holderness 优秀的网站上发现的,那里关于系统图像列表的信息比我这里提到的更多,另外还有更多的未公开的好东西,现在就过去看看吧!