C++ Q&A 专栏...
翻译:彭德奎
原文出处:MSDN Magazine Jan 2004(C++ Q&A)
原代码下载:CQA0401.exe (127KB)


Windows®有一组卷管理函数能实现这个目的。其中包括获取逻辑驱动器以及卷挂接点(mount
points)信息的函数,高级的NTFS的特性不在此之列。对于你的要求,你只要处理逻辑驱动器信息即可。Figure 1显示了相关函数。
有四个基本的函数:GetLogicalDrives, GetLogicalDriveStrings, GetDriveType和
GetVolumeInformation。第五个是SetVolumeLabel,如果你愿意,可以用它设置卷标。这些函数都相当简单易懂,为了使用方便起见,我把它们封装到了一个友好的MFC类中:CVolumeMaster,(参见
Figure 2),它可以让你处理CString而不是TCHAR数组。我还写了一个
例子程序:VolInfo.exe,示范了如何使用这个MFC类。你能从本文开始的超链接处下载源代码。Figure
3是VolInfo例子程序在我自己的计算机上运行时显示的详细信息。

Figure 3 详细的驱动器信息
第一个函数,GetLogicalDrives,返回一个DWORD的位掩码(bitmask)值,以告知驱动盘符。“0”表示是驱动器A,“1”表示驱动器B,依次类推。大家知道,英语字母表有26个字母,而DWORD有32位,你们中数学在行的人将快速地推算出:一个DWORD能提供足够大的空间来容纳所有,或者是一些可能的驱动器字母的组合。幸好微软(Redmond)不在西北利亚(古代斯拉夫语的字母有33个字母),CVolumeMaster有一个静态的方法FormatBitMask,它位掩码信息格式化为ASCII----VolInfo程序应用它来显示一条象下面这样的信息:
10110 10001 11000 00000 00000 00000 00
这条信息表示在我的电脑里有驱动器:A、C、D、F、J、K、和L。哟!如果你的大脑皮层以二进制形式编程编多了,迟早会有GetLogicalDriveStrings这样的函数诞生,它返回一个代表所有驱动器字母的重要字符串。每一个驱动器字母拥有D:\(尾随一个‘\’)的形式,这里
D 表示驱动器盘符,每个字符串有一空(null)终结符,结尾处有两个null。既然知道了用 TCHARs 处理很拘束,那么就写一个顺手的CVolumeMaster封装器,用
CStringArray 来保存得到的驱动器字母。毕竟这是个C++专栏。你只要写如下的代码:
CVolumeMaster vm;
CStringArray arDrives;
int n = vm.GetLogicalDriveStrings(arDrives);
现在arDrives里是驱动器字母串,n是逻辑驱动器数目。明白了吧?
有哪些驱动器你是知道了,但如何知道各个驱动器是什么类型的呢?GetDriveType就是为此而设。GetDriveType返回一个代码,如
DRIVE_FIXED 代表硬盘,或者 DRIVE_CDROM
代表CD-ROM驱动器。CVolumeMaster有一静态函数,用于将代码格式化为人可识别的字符串;VolInfo用它作为输出。详细情况请参见源代码。
最后,如果你想进一步了解某个逻辑驱动器,比如它的卷标,它使用的文件系统或者驱动器是否支持命名流(named streams)和加密,调用
GetVolumeInformation 函数即可。这种瑞士军刀式的函数可以获得卷标、文件系统名称(如, NTFS
或是FAT)、卷序列号、文件系统标识、最大组件长度。
你会问“最大组件长度是什么鬼东西?” 那是指反斜线符之间路径名称部分长度的文件系统表示。换句话说,如果路径名称是c:\mumble\bletch\oops,那么“mumble”、“bletch”和“oops”就是组件,每个组件的长度是有一个限制的。使用VolInfo,你可以发现NTFS支持组件的最大长度为255,而CD-ROM通常只为127。这就解释了为什么当你保存你全部的MP3到CD,你经常会得到一条信息,告诉你一些文件名或其别的什么东西太长,询问是否截短它。
CVolumeMaster 有一个自己的 GetVolumeInformation 版本——它使用 CString 代替LPTSTR。
CString volname,filesys;
DWORD serno, maxcomplen, flags;
vm.GetVolumeInformation("C:\", volname, serno, maxcomplen, flags, filesys);
与此同时,我坚持使用 CString
的原因并不是因为它更容易,它也很安全。在注重安全以及恶意病毒肆虐的今天,即使阿诺德.施瓦辛格也知道什么是缓冲区溢出。使用 CString
是一条较好的避免途径。
对于标志,它们 Figure 4 中定义。
WinBase.h 和 WinNT.h 展示了 GetVolumeInformation可以返回的标志。再一次说明, CVolumeMaster有一个函数可以将这些标志格式化为一种人可识别的字符串——
VolInfo 例子程序用到了这种格式化,也可以用它来调试你自己程序。

知道你是一位C#专家(同时也是一位C++专家),我有一个问题。我怎样才能修改系统菜单?在C++里,我可以使用 GetSystemMenu
函数,但在C#中,我不知道该如何完成?
Philippe Morvan

嘿,Philippe,至少在我有10年经验之前我不能称自己为C#专家,而且C#出现并不长。然而,我知道你的问题的答案:使用 GetSystemMenu。对,就如你在C++中一样。怎样做呢?自然是用 托管。
有时我感觉就像坏掉的唱片,因为如此多的C#问题,我都用相同的答案:托管。那是因为我得到的大部分问题都是 GUI 问题,并且
Windows 窗体目前只暴露基本的窗口子集。一旦你想做一些复杂的东西,你还必须返回到Win32®。幸运的是,Microsoft
.NET Framework 托管服务使得它更容易。
如果有一种方法能用 Windows 窗体获得系统菜单,那么窗体类中就应该有一个类似SystemMenu的东西。啊哈,事实上没有这样的属性。
控件一般都用 Control.ContextMenu 得到上下文菜单,窗体用 Form.Menu 获得主菜单,但没有 SystemMenu 或是其它的属性
用 Menu 来直接存取系统菜单。这就是你要使用托管的原因。我写了一个小程序,SysMenu,来示范如何使用托管。Figure 5列出了代码。Figure 6为结果。

Figure 6 修改后的系统菜单
为使用GetSystemMenu API函数,首先声明托管方式, 用 DllImpor。. 对于SysMenu, 你实际上需要两个函数:
GetSystemMenu 和 AppendMenu.
using System.Runtime.InteropServices;
public class Form1 : Form
{
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hwnd, int bRevert);
[DllImport("user32.dll")]
private static extern bool AppendMenu(IntPtr hMenu,
MenuFlags uFlags, uint uIDNewItem, String lpNewItem);
}
你应该经常使用 IntPtr 来代替 HWNDs、HMENUs和其它类型的窗口句柄。对于 LPCTSTRs,
将参数声明为String类型。托管服务会在传给 Windows 之前将System::String自动转换为 LPCTSTR类型。对于
MenuFlags, 那是你必须自己定义的枚举:
public enum MenuFlags {
MF_INSERT = 0x00000000,
MF_CHANGE = 0x00000080,
ooo // etc
}
你不一定非要用枚举,但用枚举更安全。MF_XXX 值来自 WinUser.h。最后, 你需要一个新的命令
ID。在SysMenu中,IDC_MYCOMMAND值为 100. 如果你使用的值小于0xF000, 你要保证不和SC_MINIMIZE,
SC_MAXIMIZE 或其它内建的系统命令冲突。同时也必须确保不和你自己的主菜单命令冲突。有了这些定义之后,
你便可以开始添加菜单项。所有需要做的只是在你的窗体构造函数中添加很少的代码。首先是获得系统菜单:
// Get system menu
IntPtr hSysMenu = GetSystemMenu(this.Handle, 0);
随后是加入你的命令:
// Add separator and new command
AppendMenu(hSysMenu,MenuFlags.MF_SEPARATOR,0,null);
AppendMenu(hSysMenu,MenuFlags.MF_BYCOMMAND, IDC_MYCOMMAND, "Do you like interop?");
现在当用户在窗口标题栏点击系统菜单,你的新菜单将显示,如 Figure
6所示。只是为了好玩,我给了它一个复选标记。但用户调用你的命令时会发生什么呢?目前,什么也不会发生。为处理这个命令,你必须重写窗体的虚拟
WndProc 方法:
const int WM_SYSCOMMAND = 0x0112;
protected override void WndProc(ref Message msg)
{
if (msg.Msg==WM_SYSCOMMAND) {
if (msg.WParam.ToInt32() == IDC_MYCOMMAND) {
// handle it!
return;
}
}
base.WndProc(ref msg);
}
无论你做什么, 如果消息不是你的,不要忘记调用基类的 WndProc 方法。否则你的程序将会挨一记重拳后回家。
好了,今天就到这儿。和通常一样,你可以从MSDN®
Magazine Web 站点上下载所有程序资源。
编程快乐!
使用 cppqa@microsoft.com 发送你的问题和评论给 Paul

作者简介:
Paul DiLascia 是一个自由作家,顾问和 Web/UI 方面资深的设计师。他是 Windows++: Writing Reusable
Windows Code in C++ (Addison-Wesley, 1992)一书的作者。你可以在
http://www.dilascia.com 网站和 Paul
联系上。
本文出自
January 2004 期刊,可通过当地
报摊获得,或者最好是 订阅
