從VC6到VC9移植代碼問題總結

當年在學校學習C編程的時候用的是TC2.0,後來學C++用TC3.0,有一天突然發現Borland C++ 3.1的IDE比較好用,於是改用BC31,然後是BC4,但是到了BC5(還有BC5.02)的時候就玩不下去了,因爲我那臺只有16M內存的P100實在是跑不動這個龐然大物,在OWL和MFC之間“痛苦地”抉擇了一部電影的時間後,我決定放棄添加內存的計劃,改用Visual C++試試。因爲當時內存太貴了,不過電影很好看,我還記得名字是《西域雄獅》,講的是黃飛鴻到北美開寶之林分號的故事,還收了一個洋徒。。。你看這思維跳躍的,打住,說正事兒吧。看完電影后裝了一個Visual C++ 4.2,這Microsoft的東西就是爭氣,在我的破機器上跑得馬溜的快,於是就用買內存的預算買了一本《Visual C++ 4.0從入門到精通》,那個時候的出版社就是效率低,這本書已經是所有關於VC的書中最新的了,好像還看到一本《Visual C++ 1.5-2.x 使用指南》,什麼年代的事情了,還擺在書架上。哪像現在的出版社,簡直就是“與時俱進”的典範,Microsoft那邊 《Windows 7》 剛發佈了個預覽版,這邊《下一代xxx揭密》就已經上架了,唉,又扯遠了,打住。沒多久VC5就出來了,好在這兩個版本的用戶界面變化不大(MFC的版本穩定在4.2),我就在《Visual C++ 4.0從入門到精通》這本書的指導下“精通”了VC5。1998年的時候Visual C++ 6.0推出了,但是我一直堅持用VC5,1999年我畢業設計還是選擇用VC5,不爲別的,就是因爲當時盜版的VC6都沒有幫助文件,就是缺少MSDN,沒有這個還怎麼寫代碼?畢業設計完成之後我從朋友那裏弄到了傳說中的兩張MSDN光盤,於是開始用VC6,從此以後,VC6就一直存在於所有我用過的電腦中,後來VC7(7.1),VC8出來以後,VC6的地位也一直沒有動搖過,用C++開發軟件我首選VC6,我還爲VC6開發了一個文件標籤欄插件Tabbar,可以通過標籤欄在打開的代碼文件之間快速切換,除此之外還具有很多其它功能,比如自動打包壓縮項目代碼。。。唉,又跑題了,打住。

    做人要與時俱進,這不,今年VC9也隨着Visual Studio 2008發佈了,再不趕上就真的老套了。說是這麼說,不過心裏還是有些餘悸的,2006年的時候我曾經試圖將我的一個工具軟件的代碼升級到VC8,但是我低估了新的編譯器的兼容性(當年從VC5到VC6可是沒有那麼多麻煩),上來就是一大堆編譯錯誤,警告就更是牛毛,當時因爲急着爲工具軟件開發一個新功能,沒有時間解決這些問題,只好放下了,這一放就是兩年。我這個人喜歡自己做工具軟件,目的是爲了方便自己,以前用VC6,覺得在代碼源文件之間切換很麻煩,但是WndTab太佔用資源,於是就借鑑WndTab的部分代碼自己做了個標籤欄插件Tabbar,還把從CodeProject上看到的好的創意都添加進去,後來用Source Insight,覺得它沒有文件標籤欄太土,就給它做了個標籤欄外掛(TabSiPlus),就這樣幾年下來竟然有幾百兆的工具代碼,這個移植的工作量可是非同小可,想着都怕怕呀!不過也沒辦法,這兩年主要用GCC做嵌入式開發了,沒有時間維護這些工具,自己在使用過程中累計下來的BUG和新需求也有一大堆,需要進行升級了,適逢這次機會將其移植到VC9到也是個不錯的選擇。其實程序員自己給自己寫工具很有好處,比如我喜歡邊寫代碼邊聽音樂,於是我就把自己寫的MP3播放器集成到VC開發環境中,這樣就可以象操作VC的其它功能一樣選擇音樂文件,還比如。。。又扯遠了,回到正題,講講移植過程中遇到的問題。

    首先可以直接用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強制轉換,當然也可以在項目屬性設置中關閉前面提到的那個選項(這個偶美試過,不知道會不會有其它問題)。

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