C++获取系统图标方法
原作者:James Brown
Original Author: James Brown
原文链接:http://www.catch22.net/tuts/sysimg.asp
Original Link: http://www.catch22.net/tuts/sysimg.asp
注意:本文的中文翻译工作已得到了原文作者 James Brown 的邮件授权,如果您需要转载,请联系 James 本人。
系统图像列表(有时亦被称作 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 优秀的网站上发现的,那里关于系统图像列表的信息比我这里提到的更多,另外还有更多的未公开的好东西,现在就过去看看吧!
祝你们用得开心!
如果有任何的评论和建议,请给我发邮件:james@catch22.net