第四章 文件的本质
以前,所有文件和目录都有一个确定的属性集:时间,日期,尺寸,以及表示‘只读的’,‘隐藏的,‘存档的’,或‘系统的’状态标志。然而,Windos95(及后来的WindowsNT4.0)出现使这些概念产生了改变,其中最重要的‘文件’变得更加广泛。现在,文件可以是任何Shell部件对象—不一定必须是文件系统的部件。
文件的精确定义是,任何作为Shell命名空间部件的对象称之为文件对象。注意,在定义中所说的‘命名空间’,它不是C++的关键字。‘Shell 命名空间’所指的是实际组成Shell的所有命名项的集合。它们都被显示在探测器的树观察中。
并不是所有文件都是文件系统中的一个实体,比如‘打印机’和‘我的计算机’。一个包含子文件对象的文件对象成为文件夹对象,文件和目录是最普通的文件对象。
所有这些变化作为微软完全面向对象的操作系统实现的第一步,已经融入到Windows9x和WindowsNT中。
一个文件对象可以有多少属性呢?回答是,它是一个集合,它完全包含MS-DOS下一个文件的所有属性以及几个由Windows95 和Windows NT外壳的图形本质要求的属性。Shell API提供了一个复合函数和相当丰富的功能来探索给定文件对象的特征,它可以是一个普通文件,一个目录,甚或一个系统文件夹,一个象打印机或拨号连接那样的系统对象。这个函数就是SHGetFileInfo()。
在这一章中,主要目标是研究这个函数的原形。对于特定的文件对象,重点是:
怎样获取类型名
怎样获取探测器图标的Handle
怎样获取可执行文件的目标平台
怎样读出属性,以确定在探测器下对文件对象有哪些事情是可以做的,哪些事情是不能做的。
对SHGetFileInfo()函数所获得信息的多样性,你会感到奇怪。曾记得,有一个读者询问我,怎样确定一个给定的.EXE文件是16位的还是32位的(不用映射EXE的头结构)。我的答案是SHGetFileInfo()。几天以后,他回来再次问我,怎样获取驱动器的图标,我再次告诉他,使用SHGetFileInfo()。这最终的结果告诉我们仔细研究SHGetFileInfo()函数是理解Shell文件对象的最好方法。
SHGetFileInfo()函数的功能
同以往一样,我们从函数的原形开始,它在shellapi.h中定义。这个函数有五个变量,定义如下:
DWORD SHGetFileInfo( LPCTSTR pszPath,
DWORD dwFileAttributes,
SHFILEINFO FAR* psfi,
UINT cbFileInfo,
UINT uFlags);
基本上讲,SHGetFileInfo()函数提供关于文件系统对象的信息。如前面解释的,这个对象可以是文件,文件夹,目录或驱动器根。DWORD的返回是指可能有相当多的返回状态,这与uFlags变量的设置有关。简单地说,使用这个函数,你可以期望:
确定可执行文件的目标平台(Win32,Win16,MS-DOS)
获取各种有特色的文件图标(小的,大的,有关联重叠的,选中的,打开的)
读出其它显示属性,如文件类型(显示在探测器类型列上的简短描述)和显示名(出现在名字列上)
读出任何其它属性,可以是文件特有的,如,是否可以拷贝,移动,删除或重命名,是否它可以形成一个快捷方式,它是否有子文件夹,是否是共享的,是拖拽目标,或有附加的属性页,等等。
SHGetFileInfo()函数的工作原理
为了正确地理解函数具有的功能,使用所有可能的方法强制调用这个函数是十分必要的。首先,让我们查看一下他所要求的变量:
变量名 |
描述 |
pszPath |
一个包含要取得信息的文件相对或绝对路径的缓冲。它可以处理长或短文件名。 |
dwFileAttributes |
资料上说,这个参数仅用于uFlags中包含SHGFI_USEFILEATTRIBUTES标志的情况。如此,它应该是文件属性的组合:存档,只读,目录,系统等。 |
Psfi |
指向一个接收数据的SHFILEINFO结构的指针。 |
cbFileInfo |
简单地给出上项结构的尺寸。 |
uFlags |
函数的核心变量,通过所有可能的标志,你就能驾驭函数的行为和实际地得到信息。 |
SHFILEINFO结构定义如下:
typedef struct _SHFILEINFO
{
HICON hIcon;
int iIcon;
DWORD dwAttributes;
char szDisplayName[MAX_PATH];
char szTypeName[80];
} SHFILEINFO;
此外,这个结构总是用于返回数据到调用程序,并且从不需要初始化。唯一可以包含信息来影响函数行为的是dwAttributes成员,在后面将进一步给出解释。显然,驾驭SHGetFileInfo()函数各种行为的所有兴趣都集中在对uFlags变量值的设置上。绝大多数情况下,信息经由psfi缓冲返回,但也有些情况,回应可以有效地包含在函数的DWORD返回之中。
指定输入文件
恢复文件信息的函数首先要求操作文件的名字,pszPath参数就是用于这个目的。然而,有一些观念是需要澄清的。其中之一是,它可以是路径名(正象所期望的),或是一个PIDL,这在第二章中讨论过了。
如果想要传递一个PIDL,而不是普通的路径名,你就应该设置SHGFI_PIDL标志到uFlags变量中。反之也是如此:如果设置了SHGFI_PIDL,则pszPath就必须指向一个ITEMIDLIST结构(即一个PIDL)。当然,pszPath也可以是文件夹名或驱动器名,此时,你需要把一个 ‘/’留在路径名的最后。即,你应该指定‘c:/’而不是‘c:’以避免错误地恢复了驱动器信息。
在SHGetFileInfo()中使用通配符
资料中并没有给出关于在SHGetFileInfo()函数中使用通配符的的任何解释,因此,你可能认为通配符不能识别。然而,我发现,如果传递一个带有通配符的串,然后提供至少一个文件匹配这个模式,函数照样正确工作。下图显示了一个简单程序的输出结果,这个程序将在后面详细讨论:
这个程序让我们选择一个文件名或路径名,然后恢复它的信息。它返回一个图标,显示名,类型名和所有其它属性的列表。另,你也可以询问程序以确定可执行文件的类型。Exe文件类型复选框抑制所有其它的选择。‘返回码’标签显示函数的返回码(或它的文字描述),通过选中‘接受任何文件名’复选框,可以强迫函数接受任何东西作为输入文件。
上面显示,程序使用e:/mssdk/doc/misc/*.txt路径名,你可以看到程序的响应:图标和类型名是正确的(在我的PC上,文本文件的描述是‘Text Document’)。奇怪,尽管指定了通配符,我们还是获得了非空的文件属性和显示名。图标和类型名可以从文件的扩展名中获得,但是,同样的情况对显示名和属性是不行的—显然那些信息是相对于一个特殊文件的。
为了检测所发生的事情,我们再使用不同的路径调用函数:e:/mssdk/doc/misc/g*.*。象所看到的,在扩展名中和文件名中都有通配符。对话框的结果如下所示:
正如显示所见,我们获得了与前面相同的信息文件,这明显说明,如果传递通配符,SHGetFileInfo()函数取第一个匹配这个串的文件,并对它进行操作。如果没有匹配这个模式的文件,这个函数什么也不做,直接返回0。另一个我们需要检测的情况是传递一个由‘*.*’结尾的路径名。如图所示,函数返回相关文件夹的信息:
还要继续检测吗?先暂停一会,让我们来仔细地查看一下函数的输出。在‘显示(Display)’字段,你可以看到一个点(.),就象在老DOS下目录列表一样。这个结果印证了我前面所说的:SHGetFileInfo()函数操作在名字匹配于这个模式的第一个文件对象上。事实上,如果你试着使用*.*来枚举一个文件夹的内容,作为匹配的串,所获得的第一个项是点(.)。如果还不信,选择测试下面的代码:
WIN32_FIND_DATA wff;
FindFirstFile("*.*", &wff);
Msg(wff.cFileName);
总的来说,即使这个特征没有写进资料,你也可以在函数中使用通配符如果:
指定一个至少匹配一个文件的模式串
知道函数停止在第一个找到的文件上
这可能是SHGetFileInfo()函数的内在代码的某个地方保持了一个WIN32_FIND_DATA结构所致。它由底层文件信息所填写,因此,用在这里就一点也不奇怪了。附带地,这个结构还涉及到另一个Shell函数SHGetDataFromIDList(),他也返回文件对象的信息,这将在后面章节中进行表述。
文件的显示名
查看上面的截图,并在你自己的机器上运行这个程序,你就会注意到它返回的显示名稍微有些不同。在我的机器上,显示名是由文件名加扩展名组成,但是在你的机器上可能只看到文件名。这依赖于探测器的‘观察|文件夹选项’对话框的设置,在此,你可以选择‘隐藏已知类型的文件扩展名’。
这里,‘已知文件类型’是指一个注册的文件类型。我们在第十四章中讨论怎样注册文件类型。现在,知道它就是一个Shell知道怎样处理的文档类型就足够了。如果你双击一个已知类型的文件,偶然地这个资料将由知道怎样处理它的程序打开。要编程取得这个设置,你需要使用SHGetSettings()函数。我们将在下一章讲述。
示例程序
前面,我们已经看到了这个用于测试的示例程序。它是一个基于对话框的应用程序。这次我给它取的工程(project)名为FileInfo。示例的操作部分围绕SHGetFileInfo()函数展开,作为一个通用查询执行器,它查询给定文件或文件夹的状态和属性。下面是程序界面:
正如所见,用户界面由一个编辑框和相关的按钮组成,使你能选择一个文件。不幸地是,这种风格不能选择目录,如果你想传递文件夹名,就必须手动键入。复选检查框允许选择想要加到调用中的标志,如果选中了EXE类型框,所有其它复选框都被禁止。这是因为SHGetFileInfo()函数要求单独指定可执行类型标志。文件图标绘制在一个静态控件上,属性被解析并转换为描述串。
大多数代码都在OnOK()方法中,当用户单击‘Go’按钮后执行这段代码。要成功地编译这段代码,记住包含 #include "resource.h"语句,并保存对话框控件的IDs,<shlobj.h>中声明了SHGetFileInfo()的原形:
void OnOK(HWND hDlg)
{
TCHAR szFile[MAX_PATH] = {0};
TCHAR szBuf[1024] = {0};
// 取得文件名
GetDlgItemText(hDlg, IDC_FILENAME, szFile, MAX_PATH);
/////////////////////////////////////////
// 收集标志
//
DWORD dwFileAttributes = 0;
UINT uFlags = 0;
if(IsDlgButtonChecked(hDlg, IDC_FILEICON))
uFlags |= SHGFI_ICON;
if(IsDlgButtonChecked(hDlg, IDC_DISPLAYNAME))
uFlags |= SHGFI_DISPLAYNAME;
if(IsDlgButtonChecked(hDlg, IDC_TYPENAME))
uFlags |= SHGFI_TYPENAME;
if(IsDlgButtonChecked(hDlg, IDC_OTHER))
uFlags |= SHGFI_ATTRIBUTES;
if(IsDlgButtonChecked(hDlg, IDC_WILDCARD))
uFlags |= SHGFI_USEFILEATTRIBUTES;
if(IsDlgButtonChecked(hDlg, IDC_EXETYPE))
uFlags = SHGFI_EXETYPE;
/////////////////////////////////////////
// 调用函数
//
SHFILEINFO sfi;
ZeroMemory(&sfi, sizeof(SHFILEINFO));
DWORD dwRC = SHGetFileInfo(
szFile, dwFileAttributes, &sfi, sizeof(SHFILEINFO), uFlags);
/////////////////////////////////////////
// 处理界面显示
//
wsprintf(szBuf, "%d", dwRC);
SetDlgItemText(hDlg, IDC_RETCODE, szBuf);
wsprintf(szBuf, "Icon Index: %d", sfi.iIcon);
SetDlgItemText(hDlg, IDC_ICONINDEX, szBuf);
SetDlgItemText(hDlg, IDC_DISPLAY, sfi.szDisplayName);
SetDlgItemText(hDlg, IDC_TYPE, sfi.szTypeName);
/////////////////////////////////////////
// Parse 解析和显示属性
//
DWORD dwAttrib = sfi.dwAttributes;
lstrcpy(szBuf, "");
if(dwAttrib != 0)
{
if(dwAttrib & SFGAO_CANCOPY)
lstrcat(szBuf, "Copy, ");
if(dwAttrib & SFGAO_CANMOVE)
lstrcat(szBuf, "Move, ");
if(dwAttrib & SFGAO_CANDELETE)
lstrcat(szBuf, "Delete, ");
if(dwAttrib & SFGAO_CANRENAME)
lstrcat(szBuf, "Rename, ");
if(dwAttrib & SFGAO_CANLINK)