我想讓用戶雙擊程序圖標時按住 Control 鍵,以一種特殊的方式來啟動程序。 但::GetCommandLine 和__argc 均沒有任何反應,用 MFC 中的 CCommandLineInfo 似乎也是如此。有沒有一種方法可以解決這個問題呢?
有,非常簡單。你所要做的就是調用 GetKeyState。當你正在處理的當前消息被發送時, 該函數返回虛擬鍵的狀態。這個狀態可能是彈起,按下,或者套索釘。套索釘用於大寫鎖定( Caps)和轉換鎖(Shift Lock),它們可以轉換狀態。對於一般的 鍵,如控制鍵(VK_CONTROL),如果鍵被按下,則其狀態的高位標識位為 1。
許多的應用程序使用 Control+F8 作為特殊鍵來啟動恢復模式。比如,如果應用程序允許用戶 定制工作間,那麼Control+F8就可以將其恢復到初始的默認設置,只是在恢復之前一定要讓用戶進行確認。做的更好一點的話,你可以在單獨的INI文件中保存用戶的設置,這樣用戶 有機會恢復它們。不管怎樣,要想在程序啟動時檢查 Control 鍵,你可以像下面這樣寫:
if (GetKeyState(VK_CONTROL)<0)
{
// enter special mode
}
Figure 1 給出了一個基於 MFC 的示例程序代碼段,你可以通過本文頂端的鏈接進行下載,如果用戶在啟動程序的時候按下Ctrl+F8,它將顯示一個消息框,並且 發出蜂鳴聲。如果你只是想檢查 Control鍵,可以忽略對 VK_F8 鍵的測試。
我經常在 C++ 程序中使用標准模板庫(STL)的 std::string 類,但在 使用 Unicode 時碰到了問題。在使用常規 C 風格的字符串時,我可以使用 TCHAR 和 _T 宏,這樣針對 Unicode 或 ASCII 均可以進行編譯,但我 總是發現這種ASCII/Unicode的結合很難與 STL 的 string 類一起使用。你有什麼好的建議嗎?
是的,一旦知道 TCHAR 和_T 是如何工作的,那麼這個問題很簡單。基本思想是 TCHAR 要麼是char,要麼是 wchar_t,這取決於 _UNICODE 的值:
// abridged from tchar.h
#ifdef _UNICODE
typedef wchar_t TCHAR;
#define __T(x) L ## x
#else
typedef char TCHAR;
#define __T(x) x
#endif
當你在工程設置中選擇 Unicode 字符集時,編譯器會用 _UNICODE 定義進行編譯。如果你選擇MBCS(多字節字符集),則編譯器將不會帶 _UNICODE 定義 。一切取決於_UNICODE 的值。同樣,每一個使用字符指針的 Windows API 函數會有一個 A(ASCII) 和一個 W(Wide/Unicode) 版本,這些版本的 實際定義也是根據 _UNICODE 的值來決定:
#ifdef UNICODE
#define CreateFile CreateFileW
#else
#define CreateFile CreateFileA
#endif
同樣,_tprintf 和 _tscanf 對應於 printf 和 scanf。所有帶"t"的版本使用 TCHARs 取代了chars。那麼怎樣把以上的這些應用到 std::string 上呢?很簡單。STL已經有一個使用寬字符定義的wstring類 (在 xstring 頭文件中定義)。string 和 wstring 均是使用 typedef 定義的模板類,基於 basic_string, 用它可以創建任何字符類型的字符串類。以下就是 STL 定義的 string 和 wstring:
// (from include/xstring)
typedef basic_string< char,
char_traits< char >, allocator< char > >
string;
typedef basic_string< wchar_t,
char_traits< wchar_t >, allocator< wchar_t > >
wstring;
模板被潛在的字符類型(char 或 wchar_t)參數化,因此,對於 TCHAR 版本,所要做的就是使用 TCHAR 來模仿定義。
typedef basic_string< TCHAR,
char_traits< TCHAR >,
allocator< TCHAR > >
tstring;
現在便有了一個 tstring,它基於 TCHAR——也就是說,它要麼是 char,要麼是 wchar_t,這取決於 _UNICODE 的值。 以上示范並指出了 STL 是怎樣使用 basic_string 來實現基於任何類型的字符串的。定義一個新的 typedef 並不是解決此問題最有效的方法。一個更好的方法是基於 string 和wstring 來簡單 地定義 tstring,如下:
#ifdef _UNICODE
#define tstring wstring
#else
#define tstring string
#endif
這個方法之所以更好,是因為 STL 中已經定義了 string 和 wstring,那為什麼還要使用模板來定義一個新的和其中之一一樣的字符串類呢? 暫且叫它 tstring。可以用 #define 將 tstring 定義為 string 和 wstring,這樣可以避免創建另外一個模板類( 雖然當今的編譯器非常智能,如果它把該副本類丟棄,我一點也不奇怪)。[編輯更新-2004/07/30:typedef 不創建新類,只是為某個類型引入限定范圍的名稱,typedef 決不會定義一個新的類型]。不管怎樣,一旦定義了 tstring,便可以像下面這樣編碼:
tstring s = _T("Hello, world");
_tprintf(_T("s =%s\n"), s.c_str());
basic_string::c_str 方法返回一個指向潛在字符類型的常量指針;在這裡,該字符類型要麼是const char*,要麼是 const wchar_t*。
Figure 2 是一個簡單的示范程序,舉例說明了 tstring 的用法。它將“Hello,world”寫入一個文件,並報告寫了多少個字節。我對 工程進行了設置,以便用 Unicode 生成 debug 版本,用 MBCS 生成 Release 版本。你可以分別進行編譯/生成並運行程序,然後比較結果。Figure 3 顯示了例子的運行情況。
Figure 3 運行中的 tstring
順便說一下,MFC 和 ATL 現在已經聯姻,以便都使用相同的字符串實現。結合後的實現使用一個叫做 CStringT 的模板類,這在某種意義上 ,其機制類似 STL 的 basic_string,用它可以根據任何潛在的字符類型來創建 CString 類。在 MFC 包含文件 afxstr.h 中定義了三種字符 串類型,如下:
typedef ATL::CStringT< wchar_t,
StrTraitMFC< wchar_t > > CStringW;
typedef ATL::CStringT< char,
StrTraitMFC< char > > CStringA;
typedef ATL::CStringT< TCHAR,
StrTraitMFC< TCHAR > > CString;
CStringW,CStringA 和 CString 正是你所期望的:CString 的寬字符,ASCII 和 TCHAR 版本。
那麼,哪一個更好,STL 還是 CStirng?兩者都很好,你可以選擇你最喜歡的一個。但有一個問題要考慮到:就是你想要鏈接哪個庫,以及你是否已經在使用 MFC/ATL。從編碼 的角度來看,我更喜歡 CString 的兩個特性:
其一是無論使用寬字符還是char,都可以很方便地對 CString 進行初始化。
CString s1 = "foo";
CString s2 = _T("bar");
這兩個初始化都正常工作,因為 CString 自己進行了所有必要的轉換。使用 STL 字符串,你必須使用_T()對 tstring 進行初始化,因為你 無法通過一個char*初始化一個wstring,反之亦然。
其二是 CString 對 LPCTSTR 的自動轉換操作,你可以像下面這樣編碼:
CString s;
LPCTSTR lpsz = s;
另一方面,使用 STL 必須顯式調用 c_str 來完成這種轉換。這確實有點挑剔,某些人會爭辯說,這樣能更好地了解何時進行轉換。比如, 在C風格可變參數的函數中使用 CString 可能會有麻煩,像 printf:
printf("s=%s\n", s); // 錯誤
printf("s=%s\n", (LPCTSTR)s); // 必需的
沒有強制類型轉換的話,得到的是一些垃圾結果,因為 printf 希望 s 是 char*。我敢肯定很多讀者都犯過這種錯誤。防止這種災禍是 STL 設計者不提供轉換操作符的一個毋庸置疑的理由。而是堅持要你調用 c_str。一般來講,喜歡使用 STL 家伙趨向於理論和學究氣,而 Redmontonians(譯者:指微軟)的大佬們則更注重實用和散漫。嘿,不管怎樣,std::string 和 CString 之間的實用差別是微不足道的。
我正在試圖用托管擴展和互用性(interop)向 C# 和 .Net 框架暴露我的 C++ 庫。我的一個類中含有一個聯合(union)類型,但 .Net 似乎並不支持這種類型:
class MyClass
{
union
{
int i;
double d;
};
};
使用聯合旨在節約空間,因為我知道int和double是絕對不可能同時使用的。同時,我的很多代碼都引用了此聯合類型,而且我不想更改它們。請問我怎樣把這個類暴露給.Net呢?我是不是必須把聯合中的每個值分別定義為成員變量,或者使用一個成員方法?
你可以使用這些方法中的任意一個,但不一定非要這麼做。在 .Net 的互用性機制中,總有 辦法來很好地暴露C++對象——這麼說吧,幾乎總有辦法。公共語言運行時(CLR)不能理解聯合類型,但可以用某些特殊技巧的常規 __value struct 來告訴 它成員在哪裡。這個神奇的屬性就是 StructLayout 和FieldOffset。在托管 C++ 中,它看起來象這樣:
[StructLayout(LayoutKind::Explicit)]
public __value struct MyUnion {
[FieldOffset(0)] int i;
[FieldOffset(0)] double d;
};
這段代碼告訴 CLR,整數i和浮點數d均處於結構的零偏移處(也就是說是第一項),這樣便使它們交迭,其效果就是把一個結構變成了聯合。這樣便可以在 __gc 類中 使用 MyUnion,像這樣:
public __gc class CUnionClass {
public:
// 可以直接存取,因為它是 public 類型
MyUnion uval;
};
有了 CUnionClass 的定義,便可以在任何 .Net 語言中通過 uval 直接存取成員i和d。在C#中,它看起來像下面這樣:
CUnionClass obj = new CUnionClass();
obj.uval.i = 17;
obj.uval.d = 3.14159
我寫了一個名為 MCUnion 的小程序,它實現了一個托管C++庫,它包含前面所示的 CUnionClass,還有一個用於測試這個C++庫的 C# 程序 utest(參見 Figure 4和 Figure 5)。CUnionClass 示范了如何為聯合成員添加屬性,這樣你就可以通過 obj.i 和 obj.d,而不是 obj.uval.i 和 obj.uval.d 來 存取值。依照你的設計,這可能是,也可能不是你所想要的結果。如果你願意,你可以將 uval 設置為 private 或者protected 類型,這樣客戶端就必須使用屬性。這將完全隱藏 uval 的聯合 實質特性。測試程序通過聯合本身和屬性 i 和 d 兩種方式都可以存取 i 和 d。
我正在寫一個 DirectX 屏保,需要在用戶進行屏保設置之前,將從用戶 My Pictures 目錄下獲得JPG,BMP,GIF 和 TGA 文件列表 作為一種默認設置並自行加載它們。將圖像紋理設置到 DirectX 中沒有什麼問題,但我有點擔心的就是不同的用戶其 My Pictures 目錄可能會不 同。在我的機器上,這個路徑為“C:\Documents and Settings\Administrator\My Documents\My Pictures”。有沒有一個簡單的方法獲得 My Pictures 的位置呢?
是的,有一個簡單的方法。你需要的函數是 SHGetSpecialFolderPath,它可以通過一個預定義的ID,如 CSIDL_MYPICTURES 來找到 對應的專用文件夾,該函數被定義在 ShlObj.h中,其中還定義了很多其它的外殼元素。比如:
TCHAR buf[MAX_PATH];
SHGetSpecialFolderPath(NULL, // HWND
buf,
CSIDL_MYPICTURES,
NULL); // don''t create
應該總是使用 SHGetSpecialFolderPath 獲得專用文件夾的名稱(而不是直接搜尋注冊表),因為它保證可以在所有版本的 Windows 系統中工作, 包括未來的版本,即便微軟的大佬們修改存儲專用文件夾路徑的注冊表鍵值。對於 Windows 2000 和 Windows XP來說,SHGetSpecialFolderPath 在shell32.dll中。而 Windows 9x 和 Windows NT 等較舊版本不含 SHGetSpecialFolderPath,但Microsoft 通過一個專門的 DLL 提供——SHFOLDER.DLL,你可以 隨自己的應用程序免費分發這個DLL文件。
事實上,來自 Redmond 的官方文檔如是說:“鼓勵軟件供應商盡可能多地重新分發 SHFOLDER.DLL 文件,以便支持 Windows 2000 以前 各個版本。”唯一需要注意的是:如果你的應用程序是面向舊版本的 Windows 操作系統,但是在 Windows 2000 或 Windows XP上生成的, 那麼你必須顯式的鏈接 SHFOLDER.DLL;否則鏈接器將從 Shell32.dll 中得到 SHGetSpecialFolderPath。
既然這是一個 C++ 專欄,所以我寫了一個叫做 CSpecialFolder 的小類(參見 Figure 6),它從 CString 派生,並會自動調用 SHGetSpecialFolderPath。 其使用方法如下:
CSpecialFolder mypics(CSIDL_MYPICTURES);
LPCTSTR lpszPath = mypics;
這樣賦值是行得通的,因為 CSpecialFolder 從 CString 派生而來,它含有一個隱式的到 LPCTSTR 的轉換操作符。CSpecialFolder 可以 從下載包中得到,附帶有一個測試程序,它可以顯示所有在 ShlObj.h 文件中有 CSIDL_XXX 定義的專用文件夾路徑名稱。其中包含大家熟悉的文件夾,如 :Favorites(收藏夾),Fonts(字體),Programs(程序),History(歷史),AppData(應用程序數據)——以及一些 奇怪的文件夾,比如:CSIDL_BITBUCKET(回收站),CSIDL_INTERNET(我想是指 Microsoft IE圖標的路徑),還有 CSIDL_SYSTEMX86(在 RISC/Alpha For Windows 2000 上,x86 的系統目錄)。
本文配套源碼