1、如何用 VolInfo 獲取邏輯驅動器?
2、如何用C#編程修改系統菜單?
我試圖寫一個能列出系統上的驅動器(C:,D:等)的MFC程序,我也需要知道該驅動器是一個硬盤或是光驅。有這樣的一個類來獲得此信息嗎?
沒有提供這種信息的MFC類,但Microsoft® 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 站點上下載所有程序資源。
本文配套源碼