VC6.0到VS2008遷移排錯

首先可以直接用Visual Studio 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) : fatal error 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定義刪除,這樣就會使用Plarform SDK中的_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 function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
e:/software/microsoft visual studio 9.0/vc/include/string.h(74) : see declaration 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) : see declaration 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 the target 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' should not 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格式的圖像文件的嘛,於是趕快用depends look了一下,頓時高喊:鬼啊~~~。原來這個depends竟然查不到導出函數的名字,後來才知道還有NONAME參數強制用順序號定位導出函數,於是就常常弄個沒有導出函數名字的DLL到處show。。。。嗯,又扯遠了。話說爲什麼舊的系統要以此指定這四個導出函數的順序號我就沒有研究了,反正現在不需要指定了,只要將@1,@2之類的刪除就行了,不過不刪好像也沒什麼問題,它們會被自動忽略。


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

    錯誤現象之一:

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

    錯誤現象之二:
f:/project/...../crpfileopavdlg.cpp(87) : error C2440: 'static_cast' : cannot convert 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 convert from '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 LRESULT OnNcHitTest(CPoint point);

不必太在意,這個不是你的錯,不過,如果你要維護一個老的界面庫(通常很多控件的subclass都會用到ON_WM_NCHITTEST),改起來還是很痛苦地,不扯了,繼續下一個。


七、statreg.cpp 和 atlimpl.cpp 的廢棄(obsolete)問題

    在編譯老的ATL嚮導生成的代碼時,會遇到下面的編譯輸出:

StdAfx.cpp
statreg.cpp is obsolete. Please remove it from your project.
atlimpl.cpp is obsolete. Please remove it from your project.

因爲老的ATL嚮導生成的代碼通常在stdafx.cpp文件中添加以下代碼:

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>

根據提示刪除#include <statreg.cpp>和#include <atlimpl.cpp>兩行代碼就行了,不過更好的辦法是這樣改:

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#if _MSC_VER <= 1200 // MFC 6.0 or earlier
#include <statreg.cpp>
#endif
#endif

#if _MSC_VER <= 1200 // MFC 6.0 or earlier
#include <atlimpl.cpp>
#endif

 


八、新的C++編譯器不再支持默認類型的變量定義


錯誤現象是:


f:/project/...../WzCheckBox.cpp(464) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int

產生這個錯誤的原因是程序中出現了這樣的代碼:

const some_const_var = 10;



static some_static_bool = FALSE;

新的C++編譯器嚴格按照C++標準,不再支持默認類型的變量定義方式,必須嚴格指定變量類型,如下使用:

const int some_const_var = 10;



static BOOL some_static_bool = FALSE;


九、for 語句的變量作用域問題

    考察下面的代碼:

for(int i = 0; i < 120; i++)
{
    if(something_happen)
    {
         break;
    }
.............
}

if(i < 120)
{
    //something happen
}

在VC6的編譯器中,這樣的代碼是沒有問題的,因爲VC6的編譯器爲了兼容舊的Microsoft C/C++編譯器,沒有嚴格按照C++標準執行,但是從VC7開始,VC的編譯器開始遵守C++標準,所以就會出現“變量i沒有定義的錯誤”。解決的方法也很簡單,按照Jim Hyslop 和 Herb Sutter的經典對話系列的第四篇中的方法,改成如下就可以了:

int i;
for(i = 0; i < 120; i++)

 

十、字符串函數的返回值問題

    strchr(_tcschr)、strpbrk(_tcspbrk ??)、strrchr(_tcsrchr)和strstr(_tcsstr)這四個函數在VC6的CRT庫中定義的返回值都是char *(TCHAR *),所以以前的代碼通常是這樣使用的:

TCHAR *cp = _tcschr( pszPath, _T('//') );
//使用*cp,可以通過cp指針修改pszPath的內容

這其實是一個“漏洞”,因爲如果pszPath是const char(TCHAR) *字符串,那麼就表示它不希望修改字符串的內容,但是調用strchr(_tcschr)函數後就可以通過cp指針修改其內容了,這豈不荒謬?所有在新版本的CRT庫中,這幾個函數的返回值都改成const char *,這就會導致上面的代碼產生編譯錯誤。建議的修改方式是改成如下方式:

const TCHAR *cp = _tcschr( pszPath, _T('//') );
//不能再通過cp指針修改pszPath的內容

但是這樣修改可能對代碼的影響比較大,比如下面的代碼:
TCHAR buf[256]; //局部緩衝區
......
TCHAR *cp = _tcschr( buf, _T('//') );
//作爲局部緩衝區(非const),希望通過cp修改buf的內容

這種情況怎麼辦呢?對了,C++還有個const_cast操作符,這時就可以排上用場了:

TCHAR *cp = const_char<TCHAR *>(_tcschr( buf, _T('//') ));

不過上面的方法要慎用,除非確定buf是非const的,否則最好老老實實地修改代碼。

 


十一、類成員函數指針做爲函數參數的“C3867”錯誤

    考察下面的代碼,CWzWindowsHook類的構造函數使用一個該類的成員函數指針,這樣構造對象時可以選擇消息過濾的handler,可以是MouseMsgFilter,也可以是KeyboardMsgFilter:

typedef BOOL (CWzWindowsHook::*FILTERPROC)(WPARAM wParam, LPARAM lParam);

// A hook used in customization sheet to filter keyboard/mouse events
class CWzWindowsHook
{
private:
    FILTERPROC m_pFilter;
    BOOL MouseMsgFilter(WPARAM wParam, LPARAM lParam);
    BOOL KeyboardMsgFilter(WPARAM wParam, LPARAM lParam);
public:
    CWzWindowsHook(FILTERPROC pFilter) : m_pFilter(pFilter)


舊的遺留代碼存在這樣的用法:

CWzWindowsHook mouseHooker(CWzWindowsHook::MouseMsgFilter);

在VC6的編譯器下編譯可能沒有問題,但是在VC9的編譯器下編譯會有如下報錯:

f:/project/...../WzWindowsHook.cpp(272) : error C3867: 'CWzWindowsHook::MouseMsgFilter': function call missing argument list; use '&CWzWindowsHook::MouseMsgFilter' to create a pointer to member

雖然C++從C繼承來了函數名即是函數地址的語法規則,但是根據C++的標準,類成員函數的指針仍然需要一個取地址符“&”。解決方法很簡單,按照提示改成如下代碼即可:

CWzWindowsHook mouseHooker(&CWzWindowsHook::MouseMsgFilter);

 

十二、wchar_t *類型與USHORT *的轉換錯誤

    VC6的編譯器不支持wchar_t數據類型,wchar_t實際上被定義成unsigned short,VC9的編譯器已經支持wchar_t爲內置數據類型,但是由一個編譯選項控制,這個選項默認是打開的,也就是將wchar_t作爲編譯器的內置數據類型。但是OLECHAR和WCHAR的定義仍然是unsigned short,在VC6的編譯環境中,兩者的指針都是USHORT *,相互賦值和做爲函數參數傳遞沒有問題,但是如果wchar_t作爲編譯器的內置數據類型,那就意味着wchar_t *與OLECHAR *或WCHAR *是兩種不同類型的指針,相互賦值就會報編譯錯誤,下面的信息就是一個典型的錯誤輸出:

f:/project/...../shellpidl.cpp(290) : error C2664: 'MultiByteToWideChar' : cannot convert parameter 5 from 'USHORT *' to 'LPWSTR'
        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

解決的方法就是使用C++的reinterpret_cast操作符或使用C-style強制轉換,當然也可以在項目屬性設置中關閉前面提到的那個選項(這個偶美試過,不知道會不會有其它問題)。

 

原文地址:http://www.handytech.cn/blog/article.asp?id=96

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