VC6到VS2008的變化

直接用VisualStudio 2008的打開VC6的工作區文件和項目文件(dsw和dsp),並將其升級爲VS2008的解決方案格式和項目格式(sln和vcproj),VC9的編譯器相對於VC6有了很大的變化,一些編譯參數和鏈接參數被廢棄(比如/map:line),有一些改變了名稱,還有新增的選項,不過不用擔心,升級過程會自動對其進行轉換,最終都會得到一個正確的解決方案和VC項目文件,這個過程不會遇到太多的麻煩,問題都出在隨後的編譯過程中,下面就將我在移植的過程中遇到的問題和我的解決方法總結一下,希望對還在用VC6維護代碼的朋友有所幫助。

一、_WIN32_WINNT 與 _WIN32_IE 設置衝突

_WIN32_WINNT 與 _WIN32_IE設置不兼容會導致如下C1189致命錯誤:

StdAfx.cpp
c:\program files\microsoft sdks\windows\v6.0a\include\sdkddkver.h(217) : fatalerror C1189: #error : _WIN32_WINNT settings conflicts with _WIN32_IE setting

StdAfx.cpp通常是項目中第一個編譯的文件,這個錯誤將導致編譯無法繼續進行。產生這個錯誤的原因是原因是_WIN32_WINNT的版本定義太老,老的VC代碼對_WIN32_WINNT的典型設置是:

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif

0x0400相對於VS2008所帶的Plarform SDK(在文件sdkddkver.h中)中_WIN32_IE的定義來說太老了,導致不兼容,可以將其改成0x0501或更高的版本避免這個問題,如下所示:

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif

也可以將這三行_WIN32_WINNT定義刪除,這樣就會使用PlarformSDK中的_WIN32_WINNT定義,自然就不存在不兼容問題了。不過出於對老版本VC的兼容考慮(畢竟以後可能還要使用VC6編譯代碼),最好這樣修改:

#if _MSC_VER <= 1200 // MFC 6.0 or earlier
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#endif

二、afximpl.h文件中的語法錯誤

MFC出現的時候STL還沒有成爲C++的標準,所以MFC使用一套自己的模版庫,比如CArray、CList、CMap等等,這些類型聲明都在afximpl.h文件中。原來在VC6編譯器適用的模版語法可能不適用VC9,特別是當以下四個環境變量設置不兼容時,就會出現這個編譯錯誤,大致情況如下:

e:\software\microsoft visual studio 9.0\vc\atlmfc\src\mfc\afximpl.h(625) :error C2059: syntax error : '<L_TYPE_raw>'
e:\software\microsoft visual studio 9.0\vc\atlmfc\src\mfc\afximpl.h(625) :error C2238: unexpected token(s) preceding ';'
e:\software\microsoft visual studio 9.0\vc\atlmfc\src\mfc\afximpl.h(629) :error C2059: syntax error : '<L_TYPE_raw>'
e:\software\microsoft visual studio 9.0\vc\atlmfc\src\mfc\afximpl.h(629) :error C2238: unexpected token(s) preceding ';'

合理調整stdafx.h中WINVER、_WIN32_WINNT、_WIN32_WINDOWS和_WIN32_IE的設置可以避免這個問題,將三個與Windows版本有關的環境變量設置爲0x0501或更高版本,將IE版本的環境變量設置爲0x0500以後的版本就可以解決這個問題。當然,考慮到與舊的VC6代碼兼容,可以採用上一個問題中提到的最後一個解決辦法,用_MSC_VER進行隔離。

三、舊的CRT庫和新的安全CRT庫引起的C4996告警

解決了環境變量設置不匹配導致的問題後,編譯過程就真正開始了,不過首先映入眼簾的應該是成堆的C4996編譯告警,對每個使用了含字符串參數的CRT庫函數都會有C4996編譯告警,一個典型的輸出如下所示:

f:\project\.....\commonfunc.cpp(280) : warning C4996: 'strcpy': This functionor variable may be unsafe. Consider using strcpy_s instead. To disabledeprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
e:\software\microsoft visual studio 9.0\vc\include\string.h(74) : seedeclaration of 'strcpy'

MSDN online 是這樣解釋的:爲了顯著增加CRT庫的安全性,許多CRT函數都有了一個更安全的新版本,新版本和舊版本的區別就是新版本函數名多了一個_s後綴。只要一個CRT函數有新的安全版本,編譯器就會產生一個C4996告警,不過,出現這個告警的目的並不是說舊版本的CRT函數將淡出CRT庫,告警出現只是爲了提醒程序員這個函數有更安全的版本存在。一種安全的或者是被鼓勵的做法是用安全版本的函數替換現有的CRT函數,不過對於一個有相當代碼量的項目,替換工作量也是巨大的,這可不是用名稱查找、替換就能簡單解決的問題,因爲許多安全版本的CRT函數參數個數也發生了變化。也可以用預處理指令消除這個告警:
#pragma warning( disable : 4996 )
或者定義 _CRT_SECURE_NO_WARNINGS 壓制這個告警(在stdafx.h中define或在項目屬性中設置預處理符號,PreProcessor Definitions)。

除了C語言的CRT函數外,POSIX 兼容函數也存在這個告警,解決方法是用POSIX標準名稱替換(比如access換成_access)或者是定義 _CRT_NONSTDC_NO_WARNINGS 壓制這個告警(方法同上)。

四、“CWinApp::Enable3dControls”引起的C4996告警

這個是編譯使用了老的嚮導生成的MFC代碼時遇到的問題,一個典型的告警信息輸出如下所示:

CrpFileCrack.cpp
f:\project\.....\crpfilecrack.cpp(52) : warning C4996: 'CWinApp::Enable3dControls':CWinApp::Enable3dControls is no longer needed. You should remove this call.
e:\software\microsoft visual studio 9.0\vc\atlmfc\include\afxwin.h(4818) : seedeclaration of 'CWinApp::Enable3dControls'

通常向導生成的代碼是:

#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif

這兩個函數的調用是舊的MFC版本對新版本的操作系統特性的支持,在新的(那個時候是新的)Windows 95平臺上要這樣調用一下才能使用新的Windows 3D樣式的控件,否則就是老的Win 3.2樣子的控件。想當初喜歡OWL就是因爲感覺它的控件比較“酷”,比如那個帶底紋的對話框,菱形的checkbox,還有帶圖標的“OK”按鈕,看到MFC作出來的灰灰的界面就覺得土,不過後來就知道MFC做界面也是很漂亮的,比如我做的。。。。,再打住。對於新的MFC版本來說已經不需要再調用這兩個函數了,參考前面的方法,用_MSC_VER對其隔離就行了:

#if _MSC_VER <= 1200 // MFC 6.0 or earlier
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
#endif

五、.def文件引起的連接告警

對於普通的DLL項目中使用的.def文件通常會引起LNK4017鏈接告警,如下所示:

.\ComFunc.def(4) : warning LNK4017: DESCRIPTION statement not supported for thetarget platform; ignored
Creating library .\..\Debug/ComFunc.lib and object .\..\Debug/ComFunc.exp

一個典型的.def文件通常有以下內容:

LIBRARY "XorCryptor"
DESCRIPTION 'XorCryptor Windows Dynamic Link Library'

EXPORTS
; Explicit exports can go here
..................
消除這個連接告警的方法就是從.def文件中刪除DESCRIPTION描述信息,不過這個告警也不是什麼大問題,不刪也可以。另一個可能產生的連接告警是LNK4222,通常出現在ocx控件和com組件的項目中,一個典型輸出是:

Linking...
.\PlusInModule.def : warning LNK4222: exported symbol 'DllCanUnloadNow' shouldnot be assigned an ordinal
.\PlusInModule.def : warning LNK4222: exported symbol 'DllGetClassObject'should not be assigned an ordinal
.\PlusInModule.def : warning LNK4222: exported symbol 'DllRegisterServer'should not be assigned an ordinal
.\PlusInModule.def : warning LNK4222: exported symbol 'DllUnregisterServer'should not be assigned an ordinal

出現這個告警的原因是舊的項目的.def文件通常這樣定義ocx和com必需的四個導出函數:
EXPORTS
DllCanUnloadNow @1 PRIVATE
DllGetClassObject @2 PRIVATE
DllRegisterServer @3 PRIVATE
DllUnregisterServer @4 PRIVATE

其中爲這四個重要的導出函數指定了四個順序號。Windows平臺上通常用兩種方式定位DLL文件中的導出函數,一種是根據導出函數名稱,一種是根據順序號,上學時曾經寫過一個顯示圖片的程序,能處理大多數當時流行的圖像格式文件,唯獨jpeg格式的搞不定,有一次看到一個圖像處理軟件中包含了一個LoadJpeg.dll,很顯然這個DLL是處理jpeg格式的圖像文件的嘛,於是趕快用dependslook了一下,頓時高喊:鬼啊~~~。原來這個depends竟然查不到導出函數的名字,後來才知道還有NONAME參數強制用順序號定位導出函數,於是就常常弄個沒有導出函數名字的DLL到處show。。。。嗯,又扯遠了。話說爲什麼舊的系統要以此指定這四個導出函數的順序號我就沒有研究了,反正現在不需要指定了,只要將@1,@2之類的刪除就行了,不過不刪好像也沒什麼問題,它們會被自動忽略。

六、使用MFC的消息映射宏引起的編譯錯誤

錯誤現象之一:

f:\project\.....\plusmaindlg.cpp(220) : error C2440: 'static_cast' : cannotconvert from 'void (__thiscall CPlusMainDlg::* )(int,BOOL)' to 'LRESULT (__thiscallCWnd::* )(WPARAM,LPARAM)'
None of the functions with this name in scope match the target type

錯誤現象之二:
f:\project\.....\crpfileopavdlg.cpp(87) : error C2440: 'static_cast' : cannotconvert from 'LRESULT (__thiscall CCrpFileOpavDlg::* )(LPCTSTR,int)' to'LRESULT (__thiscall CWnd::* )(WPARAM,LPARAM)'
None of the functions with this name in scope match the target type

以上兩個編譯錯誤產生是因爲新舊版本的MFC 中對ON_MESSAGE消息映射宏定義不同引起的,先看看老版本的MFC的ON_MESSAGE消息宏定義:

#define ON_MESSAGE(message, memberFxn) \
{ message, 0, 0, 0, AfxSig_lwl, \
(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM,LPARAM))&memberFxn },

再看看新版本的ON_MESSAGE定義:

#define ON_MESSAGE(message, memberFxn) \
{ message, 0, 0, 0, AfxSig_lwl, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
(memberFxn)) },

注意,函數類型沒有變化,都是:
LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM);
類型的函數指針(CWnd以及派生類的類成員函數指針),區別之處是新的ON_MESSAGE宏使用C++的 static_cast 操作符代替了C類型的強制轉換。產生這兩個錯誤其實是因爲用戶沒有按照ON_MESSAGE宏的約定聲明和定義消息響應函數造成的,比如,對於某些不需要處理返回值的消息響應函數,用戶通常這樣聲明和定義消息響應函數:

在頭文件中聲明:
afx_msg void OnFileProcess(WPARAM wParam,LPARAM lParam);

在源文件中實現:
void CCrpFileOpavDlg::OnFileProcess(WPARAM wParam, LPARAM lParam)
{
.......
}

或者更過分一些,直接指定爲實際參數類型:

在頭文件中聲明:
afx_msg void OnFileProcess(LPCTSTR lpszMessage, int nPercent);

在源文件中實現:
void CCrpFileOpavDlg::OnFileProcess(LPCTSTR lpszMessage, int nPercent)
{
.......
}

舊版本的ON_MESSAGE使用了C類型的強制轉換,宏解開後的代碼後不會產生錯誤信息,但是改成對類型檢查很嚴格的static_cast 操作符時就出問題了,因爲通不過static_cast操作符的檢查。解決方法就是修改代碼,同時吸取教訓,普遍使用的方法並不一定就能約定俗成,一切還是要按照規矩來。

錯誤現象之三:

f:\project\.....\WzButton.cpp(74) : error C2440: 'static_cast' : cannot convertfrom 'UINT (__thiscall CWzButton::* )(CPoint)' to 'LRESULT (__thiscall CWnd::*)(CPoint)'
Cast from base to derived requires dynamic_cast or static_cast

出現這個錯誤的原因可是“人力不可抗拒”之原因造成的,因爲舊版本的ON_WM_NCHITTEST 宏使用了
UINT (__thiscall CWzButton::* )(CPoint);
類型的類成員函數指針,其定義如下:

#define ON_WM_NCHITTEST() \
{ WM_NCHITTEST, 0, 0, 0, AfxSig_wp, \
(AFX_PMSG)(AFX_PMSGW)(UINT (AFX_MSG_CALL CWnd::*)(CPoint))&OnNcHitTest },

但是新版本變成了:

#define ON_WM_NCHITTEST() \
{ WM_NCHITTEST, 0, 0, 0, AfxSig_l_p, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(CPoint) > (&ThisClass ::OnNcHitTest)) },

注意返回值類型由UINT改成了LRESULT,再加上static_cast的嚴格檢查,所以就出錯了。修改的方法就是將你的OnNcHitTest函數由:

afx_msg UINT OnNcHitTest(CPoint point);

改成:

afx_msg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章