Windows中的文件组织采用分层结构。计算机上可以安装多个物理驱动器,每个物理磁盘又可以分成多个主分区和扩展分区,一个主分区就是一个逻辑驱动器,而扩展分区又可以创建多个逻辑驱动器。所以电脑上可以有C盘、D盘等多个逻辑驱动器。

Windows 2000新的磁盘结构的基本单位是“卷(Volume)”,与分区相比,卷可以跨越多个物理磁盘。对于每个卷(逻辑驱动器),可以给它取一个标号,叫做“卷标(volume label)”,卷标做为一个目录项被存放在驱动器的根目录中。根目录是驱动器的顶层目录,它下面可以包含多个文件和下层子目录。

8.2.1 驱动器操作

1.卷标操作
设置驱动器卷标的函数是SetVolumeLabel,用法如下。

BOOL SetVolumeLabel(  
   LPCTSTR lpRootPathName, // 目标逻辑驱动器根目录名称。如果要设置C盘卷标,应指定为“C:\”  
   LPCTSTR lpVolumeName // 要设置的卷标名称。如果为NULL,函数删除卷标 
);

如果lpRootPathName参数设为NULL,则说明要设置当前目录所在驱动器的卷标。

获取驱动器的卷标可以使用GetVolumeInformation函数,它还可以返回逻辑驱动器的序列号,文件系统类型等信息,用法如下。

BOOL GetVolumeInformation(  
   LPCTSTR lpRootPathName, // 目标逻辑驱动器根目录名称。如果要检测C盘信息,应指定为“C:\”  
   LPTSTR lpVolumeNameBuffer, // 用来取得驱动器卷标的缓冲区,长度有下面的nVolumeNameSize参数指定  
   DWORD nVolumeNameSize,  
   LPDWORD lpVolumeSerialNumber, // 用来取得驱动器序列号的双字变量地址  
   LPDWORD lpMaximumComponentLength, // 函数在这里返回文件系统所支持的文件名的最大长度  
   LPDWORD lpFileSystemFlags, // 函数在这里返回指定驱动器的属性信息 
   LPTSTR lpFileSystemNameBuffer, // 函数在这里返回文件系统名称,如“FAT”或者“NTFS”,  // 长度有下面的nFileSystemNameSize参数指定  
   DWORD nFileSystemNameSize 
);

逻辑驱动器的序列号是在格式化驱动器时,操作系统随机分配的一个GUID(Globally Unique Identifier,全局惟一的标识),用于标识卷。

2.检测逻辑驱动器
要检测当前系统中有哪些可用的逻辑驱动器可以使用GetLogicalDrives函数,原型如下。

DWORD GetLogicalDrives(void);

此函数没有参数,它的返回值是一个位掩码,用来描述当前可用的逻辑驱动器。第0位(最低位)代表驱动器A,第1位代表驱动器B,第2位代表驱动器C,依此类推。如果某一位的值为1,则说明此位对应的驱动器可用,反之则不可用。

如果认为对GetLogicalDrives函数的返回值进行位测试比较麻烦,可以使用另一个函数GetLogicalDriveStrings,这个函数以字符串的形式返回系统内可用驱动器列表,原型如下。

DWORD GetLogicalDriveStrings(  
   DWORD nBufferLength,  // 下面的lpBuffer参数所指缓冲区的长度  
   LPTSTR lpBuffer  // 函数在这里以字符串形式返回所有可用的驱动器, 
);  // 字符串格式为“A:\”,0,“B:\”,“C:\”,0,0。字符串列表以一个附加的0结束

函数执行成功,返回值是拷贝到缓冲区的字符串长度。如果用户提供的缓冲区不够用,函数返回实际需要的缓冲区长度。如果执行失败,函数返回0。

获取逻辑驱动器分布之后,有时还有必要了解某个驱动器的类型。GetDriveType函数用来决定磁盘是可移动存储设备、固定盘、光盘还是内存虚拟盘或者网络硬盘,原型如下。

UINT GetDriveType(  
   LPCTSTR lpRootPathName // 要检测的逻辑驱动器的根目录,如“C:\”等 
);

函数的返回值说明了驱动器的类型,可以是下列取值之一:

DRIVE_UNKNOWN      无法识别此驱动器(此值定义为:0) 
DRIVE_NO_ROOT_DIR  指定的根目录无效(此值定义为:1) 
DRIVE_REMOVABLE    可移动存储设备 
DRIVE_FIXED        固定盘,如硬盘中的逻辑驱动器 
DRIVE_FIXED        固定盘,如硬盘中的逻辑驱动器 
DRIVE_CDROM        光盘
DRIVE_RAMDISK      内存虚拟盘

写软件时经常需要检测逻辑驱动器的剩余空间,这可以使用GetDiskFreeSpace函数实现。

BOOL GetDiskFreeSpace(  
   LPCTSTR lpRootPathName, // 要检测的逻辑驱动器的根目录,如“C:\”等  
   LPDWORD lpSectorsPerCluster, // 用于返回每簇的扇区数  
   LPDWORD lpBytesPerSector, // 用于返回每扇区的字节数 
   LPDWORD lpNumberOfFreeClusters, // 用于返回未使用的簇的数量  
   LPDWORD lpTotalNumberOfClusters // 用于返回驱动器中簇的总数 
);

驱动器总容量的计算公式为:簇总数×每簇扇区数×每扇区的字节数;驱动器中空闲容量的计算公式为:未使用的簇×每簇扇区数×每扇区的字节数。

3.格式化驱动器
格式化逻辑驱动器的函数是SHFormatDrive。这是一个外壳函数(Windows界面操作环境称之为外壳),从shell32.dll库中导出,使用时应包含头文件shlobj.h。调用SHFormatDrive后会弹出一个格式化对话框,函数用法如下。

DWORD SHFormatDrive( 
   HWND hwnd, // 为格式化对话框指定父窗口句柄  
   UINT drive, // 要格式化的驱动器。0代表A盘,1代表B盘,依次类推  
   UINT fmtID, // 物理格式标识,仅有一个值可用:SHFMT_ID_DEFAULT  
   UINT options // 用于改变对话框的默认选项。0表示默认,SHFMT_OPT_FULL表示选中“快速格式 
); // 化”选项,SHFMT_OPT_SYSONLY表示选中“创建一个MS-DOS启动盘”选项

函数的返回值是最后一次成功格式化的磁盘标识,或者是下列取值之一:

SHFMT_ERROR     上次格式化出错,磁盘可能被格式化
SHFMT_CANCEL    格式化被取消
SHFMT_NOFORMAT  不能进行磁盘格式化

实际的格式化操作由对话框界面控制,只有用户单击“开始”按钮之后,格式化操作才开始。例如,下面的代码将弹出外壳的格式化对话框,如图8.1所示,为格式化驱动器C做准备。

SHFormatDrive(hMainWnd, 2, SHFMT_ID_DEFAULT, 0);

SHFormatDrive函数要求操作系统至少为Windows 2000。在VC++ 6.0中调用时,必须使用Windows 2000以上的SDK才能通过编译。如果没有更新SDK,则应当调用LoadLibrary和GetProcAddress之类的函数动态获取这个函数的地址。

8.2.2 目录操作

创建目录的函数是CreateDirectory,用法如下。

BOOL CreateDirectory( 
   LPCTSTR lpPathName, // 指定要创建的目录  
   LPSECURITY_ATTRIBUTES lpSecurityAttributes // 指定目录的安全属性。如果为NULL的话则为默认 
);

这个函数仅能创建一层目录,如果想创建多层目录的话,必须循环调用它。下面的自定义函数MakeSureDirectoryPathExists实现了创建多层目录的功能。

BOOL MakeSureDirectoryPathExists(LPCTSTR lpszDirPath) // lpszDirPath为要创建的目录,如“C:\dir1\dir2” 
{ 
     CString strDirPath = lpszDirPath;  
     int nPos = 0;  
     while((nPos = strDirPath.Find(’\\’, nPos+1)) != -1)  
     { 
         ::CreateDirectory(strDirPath.Left(nPos), NULL); 
     }  
     return ::CreateDirectory(strDirPath, NULL); 
}

删除目录的函数是RemoveDirectory,它只能用来删除存在的空目录。

BOOL RemoveDirectory(LPCTSTR lpPathName);