VC++中應當注意對VC字符集的設定?

Tags: 問題, 字符集,

VC真是一個非常笨,非常不友好的工具,還是這樣說,VC(MFC)和現在流行的。net framework java比起來就想石器時代跟工業時代相比一樣!
接觸MFC也有幾年了,為了它有過加班、有過熬夜、甚至通宵,程式碼沒有十萬行也應該有幾萬行了。但是MFC就是這麼牛,它牛得不但令新手忘而卻步,而且常常令有經驗的軟體工程師也栽跟斗。最近由於一個小小的環境設定設定問題花了很多時間,這跟用慣了VC6突然轉到VC2005有關,但關鍵還是VC實在太笨了,它讓我在一週內連續兩中招次!

步驟/方法

第一次中招是這樣的,很簡單:
我不知道VC.net2005預設工程預設設定是採用“Unicode字符集”(Unicode Character Set)的,以前用VC6工程的時候預設是“多字符集”(Multi-Byte Character Set)的。以前也沒有用過VC.net2005啊,我一直認為。net是用來在framework上面程式設計的,在MFC上程式設計沒有必要開啟龐大的。net2005,把機器弄得像牛拉車一樣。
我聲明瞭一個CString,按計劃給它賦值,就像下面:
CString s;
s.Format(“count = %d”,count);
按經驗這肯定不會有錯誤的,但是不好意思,編譯錯誤,因為這是我的環境採用的Unicode字符集的,而我給CString的Format函式是“多字符集”(Multi-Byte)所以編譯不通過,要知道在這種設定下使用MessageBox(“ddd”);編譯是不會通過的,因為系統呼叫的是MessageBoxW,即Unicode寬字符集的那個函式。
還好我根據編譯器的提示把s.Format(“count = %d”,count);改成s.Format(_T(“count = %d”,count);就搞定了,_T代表一個巨集,巨集的意思就是把字串轉成寬字元表示。同樣的,MessageBox(“ddd”);可以為MessageBox(_T(“ddd”));
但是還有個問題就是,所有窗體顯示的東西都是寬字元的,例如a在記憶體裡就是a\0兩個位元組,前面一個位元組a後面是\0,當從窗體取下資料(例如使用者輸入)要跟其他平臺互動時,例如網路傳輸到遠端機器。如果那邊使用的不是Unicode字符集,就會出問題,為了使介面和後臺傳輸一致,只好使用把寬字元轉換成多字符集表示:
CString strWideChar;
strWideChar.Format(_T(“這是寬位元組哦”));
char buf[20];
memset(buf,0,20);
WideCharToMultiByte( //轉換Unicode到Ansi
CP_ACP,
WC_COMPOSITECHECK WC_DEFAULTCHAR,
strWideChar,
strWideChar.GetLength(),
(char *)buf, //轉換到緩衝區中
20, //最多個位元組
0,
0
);
同樣的,你接收到的字串想要在介面正常顯示,還必須把它轉換成寬位元組表示:
char chBytes[8];
memcpy(chBytes,”aaaaaaa\0”,8);
WCHAR wch[9];
n = MultiByteToWideChar( //轉換Unicode到Ansi
CP_ACP,
0,
chBytes,
8,
wch, //轉換到緩衝區中
8 //最多個位元組
);
wch[n] = '\0';
這樣每次從介面取資料和把資料顯示到介面上都要先做處理,但是也可以把編譯環境設定成“多字符集”(Multi-Byte Character Set),就可以避免這樣轉換來轉換去(可惜我發現的時候程式碼已經差不多寫完了)。就是在“Project->Configuration Properties->General->Character Set,選擇”Use Unicode Character Set“就是使用Uncode字符集,選擇” Use Multi-Byte Character Set“就是多位元組字符集。

VC++中應當注意對VC字符集的設定

第二次中招,god,花了我好長時間才找到問題:
我在CodeProject上找了一個很厚道的老外寫的一個繼承了CDialog窗體類CResizableDialog的原始碼,這個類的作用是使MFC的窗體放大縮小時,窗體上的控制元件可以定位(Auchor),不要小看這個小小的每天都要用到的功能,用MFC實現真的很麻煩。很佩服那個老外寫了那麼多程式碼(當然跟他們的條件有關,資本主義國家的工人隨便找個工作就可以衣食無憂,病了政府照顧,我們做“挨踢”的活得像民工一樣,當然沒有那個閒情去寫那麼好的程式碼免費給別人使用,這是題外話)。
我拿了那個現成的工程,直接在我的工程裡引用他的工程。Everything works perfect.直到我把專案釋出成Release的,雙擊執行後沒有任何反應,Very weird!後來我用MessageBox列印訊息,發現執行到DoModal函式裡面就沒有出來,程式直接退出了!使用try,catch都得不到錯誤!因為我的窗體是繼承老外寫的窗體類來的,原先繼承CDialog是好好的,問題肯定在他的工程裡面,可是他給的示例程式沒有任何問題啊。MFC出錯的時候是很要命的,它不會給你任何提示,它就是不幹了!

我又拿一個前的測試程式,讓它從CResizableDialog繼承,也沒有任何問題。
簡直頭大了、無語了,不知道哪裡出現了問題,Release又不能像Debug那樣除錯,打了一堆MessageBox後還是不知道問題出現在哪裡。憑著經驗,可以知道程式中可能出現了記憶體的越界訪問什麼的致命錯誤,才會導致程式“一聲不吭”地退出,但是究竟哪裡出了問題呢?
就在束手無策的時候,我發現呼叫CResizableDialog的成員函式EnableSaveRestore會引發連結錯誤:“未定義的外部符號”,不引用它不會出錯,測試程式引用它沒有任何錯誤。通常這個錯誤造成是因為引用函式在。h檔案裡聲明瞭,但是在。cpp裡面沒有定義,或者。cpp檔案裡的定義和。h上的引數對不上。但是此時不可能是這個錯誤,因為測試程式沒有錯誤啊。直覺告訴我這是解決“Release後程序直接退出的關鍵”,說不定這個函式呼叫的問題解決了Release的問題也解決了。
MFC真是很強大,它強大得不但“像迷宮一樣,裡面有怪獸,進去一不小心就永遠出不來”,而且它讓你當遇到怪獸的時候總是給你一點點星光,只要你不放棄,奇蹟就會出現,你就會練成絕世神功。這跟武俠小說是相通的,主人公每次到了生死關頭就會出現奇蹟,成為天下無敵的高手。看看我怎麼找到解決方法的,Very tricky。

VC++中應當注意對VC字符集的設定

既然呼叫EnableSaveRestore出現了不該出現的錯誤,那麼就從這個函式開始找。這個函式是這樣的:
.h檔案宣告
void EnableSaveRestore(LPCTSTR pszSection, BOOL bRectOnly = FALSE);
.cpp檔案定義
void CResizableDialog::EnableSaveRestore(LPCTSTR pszSection, BOOL bRectOnly/* = FALSE */)
{
m_sSection = pszSection;
m_bEnableSaveRestore = TRUE;
m_bRectOnly = bRectOnly;
// restore immediately
LoadWindowRect(pszSection, bRectOnly);
}

上面的程式碼沒有任何錯誤,既然沒有錯誤,就要用使用以下方法來找:
1.重新為CResizableDialog寫一個函式,它沒有引數的,呼叫它,發現沒有錯誤,看來引數有問題。
2.既然沒有引數的函式沒有錯誤,就把出問題的函式引數去掉吧,竟然也沒有錯誤!那問題就肯定是出在引數上。
3.去掉其中一個引數,測試發現是LPCTSTR pszSection的問題,而不是BOOL bRectOnly的問題。
4.既然這樣,那就換一種表示吧,把LPCTSTR pszSection換成WCHAR* pszSection,執行它,竟然不出錯了!翻開MFC巨集定義,就會發現其實LPCTSTR和WCHAR*是一樣的,MFC真是freak!
5.但是這個函式功能還是不正常,斷點進入那個函式裡面發現傳進去的字串只有一個字元了,這種情況就是寬字元當成短字元時,第二個位元組的\0當成了字串的截止字元了,也就是說,這個函式裡採用的是短字元(多字符集Multi Byte)處理的。
6.我的工程採用的是寬字符集(Unicode Char)的,檢查設定,原來那個老外是用VC6編的,預設是使用多字符集(Multi Byte)的,VC真是笨啊,兩個Project在一個Solution裡面完全不同的設定竟然沒有任何提示,簡直把我弄死了!
7.把引用工程也改成使用Unicode字符集,並且把函式EnableSaveRestore WCHAR* pszSection恢復原樣,搞定!果然不出我所料,Release也沒有問題了!我用以前的那個測試程式來使剛好以前把它設成Multi Byte,所以也沒有錯誤,Damn!
僅僅是一個設定啊,如果VC出錯提示稍微好的,至少字符集不匹配不要說成“未定義的外部符號”也好用一點啊,難怪現在用VC的人越來越少了!
注:通常說的VC不是指使用。net framework的VC,那個很簡單,記憶體都不用管,通常是指非託管的VC。

VC++中應當注意對VC字符集的設定

相關問題答案