Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här portningsfallstudien är utformad för att ge dig en uppfattning om hur ett typiskt portningsprojekt är, vilka typer av problem du kan stöta på och några allmänna tips och knep för att hantera portningsproblem. Det är inte tänkt att vara en slutgiltig guide till portning, eftersom upplevelsen av att portera ett projekt beror mycket på kodens detaljer.
Spy++
Spy++ är ett allmänt använt GUI-diagnostikverktyg för Windows-skrivbordet som tillhandahåller all slags information om användargränssnittselement på Windows-skrivbordet. Den visar den fullständiga hierarkin för fönster och ger åtkomst till metadata om varje fönster och kontroll. Det här användbara programmet har levererats med Visual Studio i många år. Vi hittade en gammal version av den som senast kompilerades i Visual C++ 6.0 och porterade den till Visual Studio 2015. Upplevelsen för Visual Studio 2017 eller Visual Studio 2019 bör vara nästan identisk.
Vi ansåg att det här fallet var typiskt för portning av Windows-skrivbordsprogram som använder MFC och Win32-API:et, särskilt för gamla projekt som inte har uppdaterats med varje version av Visual C++ sedan Visual C++ 6.0.
Steg 1. Konvertera projektfilen
Projektfilen, två gamla .dsw-filer från Visual C++ 6.0, konverterades enkelt utan problem som kräver ytterligare uppmärksamhet. Ett projekt är Spy++-programmet. Den andra är SpyHk, skriven i C, en stödjande DLL. Mer komplexa projekt kanske inte uppgraderas lika enkelt, som beskrivs här.
Efter uppgraderingen av de två projekten såg lösningen ut så här:
Vi har två projekt, ett med ett stort antal C++-filer och ett annat DLL som är skrivet i C.
Steg 2. Problem med rubrikfil
När du skapar ett nyligen konverterat projekt är en av de första saker som du ofta hittar att huvudfiler som ditt projekt använder inte hittas.
En av filerna som inte kunde hittas i Spy++ var verstamp.h. Från en Internetsökning har vi fastställt att detta kom från en DAO SDK, en föråldrad datateknik. Vi ville ta reda på vilka symboler som användes från huvudfilen, för att se om den filen verkligen behövdes eller om dessa symboler definierades någon annanstans, så vi kommenterade ut deklarationen av rubrikfilen och kompilerade om. Det visar sig att det bara finns en symbol som behövs, VER_FILEFLAGSMASK.
1>C:\Program Files (x86)\Windows Kits\8.1\Include\shared\common.ver(212): error RC2104: undefined keyword or key name: VER_FILEFLAGSMASK
Det enklaste sättet att hitta en symbol i de tillgängliga inkluderingsfilerna är att använda Sök i filer (Ctrl+Skift+F) och ange Visual C++ Include Directories. Vi hittade den i ntverp.h. Vi ersatte verstamp.h-inkluderingen med ntverp.h och det här felet försvann.
Steg 3. Linker OutputFile-inställning
Äldre projekt har ibland filer placerade på okonventionella platser som kan orsaka problem efter uppgraderingen. I det här fallet måste vi lägga $(SolutionDir) till sökvägen Inkludera i projektegenskaperna för att säkerställa att Visual Studio kan hitta några huvudfiler som placeras där i stället för i någon av projektmapparna.
MSBuild klagar på att egenskapen Link.OutputFile inte matchar värdena TargetPath och TargetName och utfärdar MSB8012.
warning MSB8012: TargetPath(...\spyxx\spyxxhk\.\..\Debug\SpyxxHk.dll) does not match the Linker's OutputFile property value (...\spyxx\Debug\SpyHk55.dll). This may cause your project to build incorrectly. To correct this, please make sure that $(OutDir), $(TargetName) and $(TargetExt) property values match the value specified in %(Link.OutputFile).warning MSB8012: TargetName(SpyxxHk) does not match the Linker's OutputFile property value (SpyHk55). This may cause your project to build incorrectly. To correct this, please make sure that $(OutDir), $(TargetName) and $(TargetExt) property values match the value specified in %(Link.OutputFile).
Link.OutputFile är build-utdata (till exempel EXE, DLL) och skapas normalt från $(TargetDir)$(TargetName)$(TargetExt), vilket ger sökvägen, filnamnet och tillägget. Det här är ett vanligt fel när du migrerar projekt från det gamla Visual C++-byggverktyget (vcbuild.exe) till det nya byggverktyget (MSBuild.exe). Eftersom kompileringsverktyget ändrades i Visual Studio 2010 kan du stöta på det här problemet när du migrerar ett projekt före 2010 till en version från 2010 eller senare. Det grundläggande problemet är att projektmigreringsguiden inte uppdaterar värdet Link.OutputFile eftersom det inte alltid går att avgöra vad dess värde ska baseras på de andra projektinställningarna. Därför måste du vanligtvis ange det manuellt. Mer information finns i det här inlägget på Visual C++-bloggen.
I det här fallet har egenskapen Link.OutputFile i det konverterade projektet angetts till .\Debug\Spyxx.exe and .\Release\Spyxx.exe för Spy++-projektet, beroende på konfigurationen. Det bästa valet är att helt enkelt ersätta dessa hårdkodade värden med $(TargetDir)$(TargetName)$(TargetExt) för Alla konfigurationer. Om det inte fungerar kan du anpassa därifrån eller ändra egenskaperna i avsnittet Allmänt där dessa värden anges (egenskaperna är Utdatakatalog, Målnamn och Måltillägg. Kom ihåg att om den egenskap som du visar använder makron kan du välja Redigera i listrutan för att visa en dialogruta som visar den sista strängen med makroersättningarna gjorda. Du kan visa alla tillgängliga makron och deras aktuella värden genom att välja knappen Makron .
Steg 4. Uppdatera Windows-målversionen
Nästa fel anger att WINVER-versionen inte längre stöds i MFC. WINVER för Windows XP är 0x0501.
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxv_w32.h(40): fatal error C1189: #error: MFC does not support WINVER less than 0x0501. Please change the definition of WINVER in your project properties or precompiled header.
Windows XP stöds inte längre av Microsoft, så även om det är tillåtet att rikta in sig på det i Visual Studio bör du fasa ut stödet för det i dina program och uppmuntra användarna att använda nya versioner av Windows.
Om du vill bli av med felet definierar du WINVER genom att uppdatera inställningen Projektegenskaper till den lägsta versionen av Windows som du för närvarande vill rikta in dig på. Hitta en tabell med värden för olika Windows-versioner här.
Filen stdafx.h innehöll några av dessa makrodefinitioner.
#define WINVER 0x0500 // these defines are set so that we get the
#define _WIN32_WINNT 0x0500 // maximum set of message/flag definitions,
#define _WIN32_IE 0x0400 // from both winuser.h and commctrl.h.
WINVER sätter vi på Windows 7. Det är lättare att läsa koden senare om du använder makrot för Windows 7 (_WIN32_WINNT_WIN7) i stället för själva värdet (0x0601).
#define WINVER _WINNT_WIN32_WIN7 // Minimum targeted Windows version is Windows 7
Steg 5. Länkfel
Med dessa ändringar bygger projektet SpyHk (DLL), men det skapar ett länkfel.
LINK : warning LNK4216: Exported entry point _DLLEntryPoint@12
Startpunkten för en DLL ska inte exporteras. Startpunkten är endast avsedd att anropas av inläsaren när DLL:en först läses in i minnet, så den bör inte finnas i exporttabellen, vilket är för andra anropare. Vi behöver bara se till att det inte har __declspec(dllexport)-direktivet kopplat till sig. I spyxxhk.c måste vi ta bort den från två platser, deklarationen och definitionen av DLLEntryPoint. Det var aldrig meningsfullt att använda det här direktivet, men tidigare versioner av länkaren och kompilatorn flaggade det inte som problem. De nyare versionerna av länkaren ger en varning.
// deleted __declspec(dllexport)
BOOL WINAPI DLLEntryPoint(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);
C DLL-projektet, SpyHK.dll, bygger nu och länkar utan fel.
Steg 6. Fler inaktuella huvudfiler
Nu börjar vi arbeta med det huvudsakliga körbara projektet Spyxx.
Det gick inte att hitta några andra inkluderingsfiler: ctl3d.h och penwin.h. Även om det kan vara bra att söka på Internet för att försöka identifiera vad som ingår i rubriken, är informationen ibland inte så användbar. Vi fick reda på att ctl3d.h var en del av Exchange Development Kit och gav stöd för en viss typ av kontroller i Windows 95, och penwin.h relaterar till Window Pen Computing, ett föråldrat API. I det här fallet kommenterar vi bara ut #include linjen och hanterar de odefinierade symbolerna som vi gjorde med verstamp.h. Allt som rör 3D-kontroller eller pennberäkning har tagits bort från projektet.
Med tanke på ett projekt med många kompileringsfel som du gradvis eliminerar är det inte realistiskt att hitta all användning av ett inaktuellt API direkt när du tar bort #include direktivet. Vi upptäckte det inte omedelbart, utan vid ett senare tillfälle insåg vi att det fanns ett fel eftersom WM_DLGBORDER var odefinierat. Det är faktiskt bara en av många odefinierade symboler som kommer från ctl3d.h. När vi har fastställt att det är relaterat till ett inaktuellt API har vi tagit bort alla referenser i kod till det.
Steg 7. Uppdatera gammal iostreams-kod
Nästa fel är vanligt med gammal C++-kod som använder iostreams.
mstream.h(40): fatal error C1083: Cannot open include file: 'iostream.h': No such file or directory
Problemet är att det gamla iostreams-biblioteket har tagits bort och ersatts. Vi måste ersätta de gamla iostreams med de nyare standarderna.
#include <iostream.h>
#include <strstrea.h>
#include <iomanip.h>
Dessa är de uppdaterade tilläggen:
#include <iostream>
#include <sstream>
#include <iomanip>
Med den här ändringen har vi problem med ostrstream, som inte längre används. Lämplig ersättning är ostringstream. Vi försöker lägga till ett typedef till ostrstream för att undvika att ändra koden för mycket, åtminstone som en start.
typedef std::basic_ostringstream<TCHAR> ostrstream;
För närvarande skapas projektet med hjälp av MBCS (Multi-byte Character Set), så char är lämplig teckendatatyp. Men för att göra det enklare att uppdatera koden till UTF-16 Unicode uppdaterar vi den till TCHAR, vilket löser sig till char eller wchar_t beroende på om egenskapen Teckenuppsättning i projektinställningarna är inställd på MBCS eller Unicode.
Några andra koddelar måste uppdateras. Vi ersatte basklassen ios med ios_base, och vi ersatte ostream med basic_ostream<T>. Vi lägger till ytterligare två typedefs och det här avsnittet kompileras.
typedef std::basic_ostream<TCHAR> ostream;
typedef ios_base ios;
Att använda dessa typedefs är bara en tillfällig lösning. För en mer permanent lösning kan vi uppdatera varje referens till det omdöpta eller inaktuella API:et.
Här är nästa fel.
error C2039: 'freeze': is not a member of 'std::basic_stringbuf<char,std::char_traits<char>,std::allocator<char>>'
Nästa problem är att basic_stringbuf det inte har någon freeze metod. Metoden freeze används för att förhindra en minnesläcka i den gamla ostream. Vi behöver det inte nu när vi använder den nya ostringstream. Vi kan ta bort anropet till freeze.
//rdbuf()->freeze(0);
Följande två fel inträffade på intilliggande rader. Den första klagar på att använda ends, vilket är det gamla iostream bibliotekets I/O-manipulator som lägger till en null-terminator i en sträng. Det andra av dessa fel förklarar att metodens utdata str inte kan tilldelas en icke-konstant pekare.
// Null terminate the string in the buffer and
// get a pointer to it.
//
*this << ends;
LPSTR psz = str();
2>mstream.cpp(167): error C2065: 'ends': undeclared identifier2>mstream.cpp(168): error C2440: 'initializing': cannot convert from 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>' to 'LPSTR'
Med det nya strömbiblioteket behövs inte ends eftersom strängen alltid avslutas med null, vilket gör att den raden kan tas bort. För det andra problemet är att nu str() inte returnerar en pekare till teckenmatrisen för en sträng; den returnerar typen std::string. Lösningen på den andra är att ändra typen till LPCSTR och använda c_str() metoden för att begära pekaren.
//*this << ends;
LPCTSTR psz = str().c_str();
Ett fel som förbryllade oss ett tag inträffade i den här koden.
MOUT << _T(" chUser:'") << chUser
<< _T("' (") << (INT)(UCHAR)chUser << _T(')');
Makrot MOUT matchar *g_pmout som är ett objekt av typen mstream. Klassen mstream härleds från standardutdatasträngsklassen, std::basic_ostream<TCHAR>. Dock, med _T runt strängliteralen, som vi lagt in som förberedelse för konvertering till Unicode, misslyckas överlagringslösningen för operatorn << med följande felmeddelande:
1>winmsgs.cpp(4612): error C2666: 'mstream::operator <<': 2 overloads have similar conversions
1> c:\source\spyxx\spyxx\mstream.h(120): note: could be 'mstream &mstream::operator <<(ios &(__cdecl *)(ios &))'
1> c:\source\spyxx\spyxx\mstream.h(118): note: or 'mstream &mstream::operator <<(ostream &(__cdecl *)(ostream &))'
1> c:\source\spyxx\spyxx\mstream.h(116): note: or 'mstream &mstream::operator <<(ostrstream &(__cdecl *)(ostrstream &))'
1> c:\source\spyxx\spyxx\mstream.h(114): note: or 'mstream &mstream::operator <<(mstream &(__cdecl *)(mstream &))'
1> c:\source\spyxx\spyxx\mstream.h(109): note: or 'mstream &mstream::operator <<(LPTSTR)'
1> c:\source\spyxx\spyxx\mstream.h(104): note: or 'mstream &mstream::operator <<(TCHAR)'
1> c:\source\spyxx\spyxx\mstream.h(102): note: or 'mstream &mstream::operator <<(DWORD)'
1> c:\source\spyxx\spyxx\mstream.h(101): note: or 'mstream &mstream::operator <<(WORD)'
1> c:\source\spyxx\spyxx\mstream.h(100): note: or 'mstream &mstream::operator <<(BYTE)'
1> c:\source\spyxx\spyxx\mstream.h(95): note: or 'mstream &mstream::operator <<(long)'
1> c:\source\spyxx\spyxx\mstream.h(90): note: or 'mstream &mstream::operator <<(unsigned int)'
1> c:\source\spyxx\spyxx\mstream.h(85): note: or 'mstream &mstream::operator <<(int)'
1> c:\source\spyxx\spyxx\mstream.h(83): note: or 'mstream &mstream::operator <<(HWND)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1132): note: or 'CDumpContext &operator <<(CDumpContext &,COleSafeArray &)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1044): note: or 'CArchive &operator <<(CArchive &,ATL::COleDateTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1042): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::COleDateTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1037): note: or 'CArchive &operator <<(CArchive &,ATL::COleDateTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1035): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::COleDateTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1030): note: or 'CArchive &operator <<(CArchive &,COleCurrency)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1028): note: or 'CDumpContext &operator <<(CDumpContext &,COleCurrency)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(955): note: or 'CArchive &operator <<(CArchive &,ATL::CComBSTR)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(951): note: or 'CArchive &operator <<(CArchive &,COleVariant)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(949): note: or 'CDumpContext &operator <<(CDumpContext &,COleVariant)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(248): note: or 'CArchive &operator <<(CArchive &,const RECT &)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(247): note: or 'CArchive &operator <<(CArchive &,POINT)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(246): note: or 'CArchive &operator <<(CArchive &,SIZE)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(242): note: or 'CDumpContext &operator <<(CDumpContext &,const RECT &)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(241): note: or 'CDumpContext &operator <<(CDumpContext &,POINT)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(240): note: or 'CDumpContext &operator <<(CDumpContext &,SIZE)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1639): note: or 'CArchive &operator <<(CArchive &,const CObject *)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1425): note: or 'CArchive &operator <<(CArchive &,ATL::CTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1423): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::CTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1418): note: or 'CArchive &operator <<(CArchive &,ATL::CTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1416): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::CTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(694): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,const char *)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(741): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(866): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,const _Elem *)'
1> with
1> [
1> _Elem=wchar_t
1> ]
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(983): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>,wchar_t[10]>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &&,const _Ty (&))'
1> with
1> [
1> _Ty=wchar_t [10]
1> ]
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(1021): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,const std::error_code &)'
1> winmsgs.cpp(4612): note: while trying to match the argument list '(CMsgStream, const wchar_t [10])'
Det finns så många operatordefinitioner << att den här typen av fel kan vara skrämmande. När vi har tittat närmare på de tillgängliga överlagringarna kan vi se att de flesta av dem är irrelevanta, och om vi tittar närmare på klassdefinitionen mstream identifierade vi följande funktion som vi anser bör anropas i det här fallet.
mstream& operator<<(LPTSTR psz)
{
return (mstream&)ostrstream::operator<<(psz);
}
Anledningen till att den inte anropas är att strängliteralen har den typ const wchar_t[10] som du kan se från den sista raden i det långa felmeddelandet, så konverteringen till en icke-const-pekare är inte automatisk. Den operatorn bör dock inte ändra indataparametern, så den lämpligare parametertypen är LPCTSTR (const char* vid kompilering som MBCS och const wchar_t* som Unicode), inte LPTSTR (char* vid kompilering som MBCS och wchar_t* som Unicode). Om du gör den ändringen åtgärdas det här felet.
Den här typen av konvertering tilläts under den äldre, mindre strikta kompilatorn, men senare anpassningsändringar kräver mer korrekt kod.
Steg 8. Kompilatorns striktare konverteringar
Vi får också många fel som följande:
error C2440: 'static_cast': cannot convert from 'UINT (__thiscall CHotLinkCtrl::* )(CPoint)' to 'LRESULT (__thiscall CWnd::* )(CPoint)'
Felet uppstår i en meddelandekarta som helt enkelt är ett makro:
BEGIN_MESSAGE_MAP(CFindToolIcon, CWnd)
// other messages omitted...
ON_WM_NCHITTEST() // Error occurs on this line.
END_MESSAGE_MAP()
När vi går till definitionen av det här makrot ser vi att det refererar till funktionen 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)) },
Problemet har att göra med matchningsfelet i pekaren till medlemsfunktionstyper. Problemet är inte konverteringen från CHotLinkCtrl som klasstyp till CWnd som klasstyp, eftersom det är en giltig konvertering från härledd till bas. Problemet är returtypen: UINT jämfört med LRESULT. LRESULT motsvarar LONG_PTR som är en 64-bitars pekare eller en 32-bitars pekare, beroende på vilken måltyp av binär det är, så UINT omvandlas inte till denna typ. Detta är inte ovanligt när du uppgraderar kod som skrivits före 2005 eftersom returtypen för många meddelandekartametoder ändrades från UINT till LRESULT i Visual Studio 2005 som en del av 64-bitars kompatibilitetsändringarna. Vi ändrar returtypen från UINT i följande kod till LRESULT:
afx_msg UINT OnNcHitTest(CPoint point);
Efter ändringen har vi följande kod:
afx_msg LRESULT OnNcHitTest(CPoint point);
Eftersom det finns ungefär tio förekomster av den här funktionen i olika klasser som härletts från CWnd är det bra att använda Gå till definition (Tangentbord: F12) och Gå till deklaration (Tangentbord: Ctrl+F12) när markören är på funktionen i redigeraren för att hitta dessa och navigera till dem från fönstret Sök symbol . Gå till Definition är vanligtvis det mer användbara av de två. Gå till Deklarationen hittar andra deklarationer än den definierande klassdeklarationen, till exempel vänklassdeklarationer eller vidarebefordra referenser.
Steg 9. MFC-ändringar
Nästa fel gäller även en ändrad deklarationstyp och inträffar även i ett makro.
error C2440: 'static_cast': cannot convert from 'void (__thiscall CFindWindowDlg::* )(BOOL,HTASK)' to 'void (__thiscall CWnd::* )(BOOL,DWORD)'
Problemet är att den andra parametern CWnd::OnActivateApp har ändrats från HTASK till DWORD. Den här ändringen inträffade i 2002 års version av Visual Studio, Visual Studio .NET.
afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);
Vi måste uppdatera deklarationerna för OnActivateApp i härledda klasser enligt följande:
afx_msg void OnActivateApp(BOOL bActive, DWORD dwThreadId);
Nu kan vi kompilera projektet. Det finns dock några varningar att gå igenom och det finns valfria delar av uppgraderingen, till exempel konvertering från MBCS till Unicode eller förbättrad säkerhet med hjälp av säkra CRT-funktioner.
Steg 10. Hantera kompilatorvarningar
För att få en fullständig lista med varningar bör du göra en Återskapa alla på lösningen i stället för en vanlig version, bara för att se till att allt som tidigare kompilerats kommer att kompileras om, eftersom du bara får varningsrapporter från den aktuella kompileringen. Den andra frågan är om du vill acceptera den aktuella varningsnivån eller använda en högre varningsnivå. När du porterar mycket kod, särskilt gammal kod, kan det vara lämpligt att använda en högre varningsnivå. Du kanske också vill börja med standardvarningsnivån och sedan öka varningsnivån för att få alla varningar. Om du använder /Wallfår du några varningar i systemhuvudfilerna, så många använder /W4 för att få flest varningar på sin kod utan att få varningar för systemhuvuden. Om du vill att varningar ska visas som fel lägger du till alternativet /WX . De här inställningarna finns i avsnittet C/C++ i dialogrutan Projektegenskaper .
En av metoderna i CSpyApp klassen ger en varning om en funktion som inte längre stöds.
void SetDialogBkColor() {CWinApp::SetDialogBkColor(::GetSysColor(COLOR_BTNFACE));}
Varningen är följande.
warning C4996: 'CWinApp::SetDialogBkColor': CWinApp::SetDialogBkColor is no longer supported. Instead, handle WM_CTLCOLORDLG in your dialog
Meddelandet WM_CTLCOLORDLG redan hanterades i Spy++-kod, så den enda ändring som krävdes var att ta bort alla referenser till SetDialogBkColor, som inte längre behövs.
Nästa varning var enkel att åtgärda genom att kommentera ut variabelnamnet. Vi fick följande varning:
warning C4456: declaration of 'lpszBuffer' hides previous local declaration
Koden som genererar detta omfattar ett makro.
DECODEPARM(CB_GETLBTEXT)
{
P2WPOUT();
P2LPOUTPTRSTR;
P2IFDATA()
{
PARM(lpszBuffer, PPACK_STRINGORD, ED2);
INDENT();
P2IFISORD(lpszBuffer)
{
P2OUTORD(lpszBuffer);
}
else
{
PARM(lpszBuffer, LPTSTR, ED2);
P2OUTS(lpszBuffer);
}
}
}
Stor användning av makron som i den här koden tenderar att göra koden svårare att underhålla. I det här fallet innehåller makrona variablernas deklarationer. Makrot PARM definieras på följande sätt:
#define PARM(var, type, src)type var = (type)src
Därför deklareras variabeln lpszBuffer två gånger i samma funktion. Det är inte så enkelt att åtgärda detta som det skulle vara om koden inte använde makron (ta helt enkelt bort den andra typdeklarationen). Som det är nu har vi det olyckliga valet att behöva bestämma om makrokoden ska skrivas om som vanlig kod (en omständlig och möjligen felbenägen uppgift) eller inaktivera varningen.
I det här fallet väljer vi att inaktivera varningen. Vi kan göra det genom att lägga till en pragma på följande sätt:
#pragma warning(disable : 4456)
När du inaktiverar en varning kanske du vill begränsa inaktiveringseffekten till bara den kod som du skapar varningen för att undvika att ignorera varningen när den kan ge användbar information. Vi lägger till kod för att återställa varningen strax efter raden som genererar den, eller ännu bättre, eftersom den här varningen inträffar i ett makro, använder nyckelordet __pragma , som fungerar i makron (#pragma fungerar inte i makron).
#define PARM(var, type, src)__pragma(warning(disable : 4456)) \
type var = (type)src \
__pragma(warning(default : 4456))
Nästa varning kräver vissa kodrevisioner. Win32-API:et GetVersion (och GetVersionEx) är inaktuellt.
warning C4996: 'GetVersion': was declared deprecated
Följande kod visar hur versionen hämtas.
// check Windows version and set m_bIsWindows9x/m_bIsWindows4x/m_bIsWindows5x flags accordingly.
DWORD dwWindowsVersion = GetVersion();
Detta följs av mycket kod som undersöker dwWindowsVersion-värdet för att avgöra om vi kör på Windows 95 och vilken version av Windows NT. Eftersom allt detta är inaktuellt tar vi bort koden och hanterar eventuella referenser till dessa variabler.
Artikeln Operativsystemversionsändringar i Windows 8.1 och Windows Server 2012 R2 förklarar situationen.
Det finns metoder i CSpyApp klassen som frågar operativsystemversionen: IsWindows9x, IsWindows4xoch IsWindows5x. En bra utgångspunkt är att anta att de versioner av Windows som vi tänker stödja (Windows 7 och senare) alla ligger nära Windows NT 5 när det gäller de tekniker som används av det här äldre programmet. Användning av dessa metoder var att hantera begränsningar i de äldre operativsystemen. Så vi ändrade dessa metoder för att returnera TRUE för IsWindows5x och FALSE för de andra.
BOOL IsWindows9x() {/*return(m_bIsWindows9x);*/ return FALSE; }
BOOL IsWindows4x() {/*return(m_bIsWindows4x);*/ return FALSE; }
BOOL IsWindows5x() {/*return(m_bIsWindows5x);*/ return TRUE; }
Det lämnade bara ett fåtal platser där de interna variablerna användes direkt. Eftersom vi har tagit bort dessa variabler får vi några fel som måste hanteras explicit.
error C2065: 'm_bIsWindows9x': undeclared identifier
void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_bIsWindows9x || hToolhelp32 != NULL);
}
Vi kan ersätta detta med ett metodanrop eller helt enkelt skicka TRUE och ta bort det gamla specialfallet för Windows 9x.
void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
pCmdUI->Enable(TRUE /*!m_bIsWindows9x || hToolhelp32 != NULL*/);
}
Den sista varningen på standardnivån (3) har att göra med ett bitfält.
treectl.cpp(1656): warning C4463: overflow; assigning 1 to bit-field that can only hold values from -1 to 0
Koden som utlöser detta är följande.
m_bStdMouse = TRUE;
Deklarationen av m_bStdMouse anger att det är ett bitfält.
class CTreeListBox : public CListBox
{
DECLARE_DYNCREATE(CTreeListBox)
CTreeListBox();
private:
int ItemFromPoint(const CPoint& point);
class CTreeCtl* m_pTree;
BOOL m_bGotMouseDown : 1;
BOOL m_bDeferedDeselection : 1;
BOOL m_bStdMouse : 1;
Den här koden skrevs innan den inbyggda booltypen stöddes i Visual C++. I sådan kod var BOOL en typedef för int. Typen int är en signed typ och bitrepresentationen av en signed int är att använda den första biten som en teckenbit, så ett bitfält av typen int kan tolkas som att representera 0 eller -1, förmodligen inte vad som var avsett.
Du skulle inte veta genom att titta på koden varför det här är bitfält. Var avsikten att hålla storleken på objektet liten eller finns det någonstans där objektets binära layout används? Vi har ändrat dessa till vanliga BOOL, eftersom vi inte såg någon anledning till att använda ett bitfält. Att använda bitfält för att hålla ett objekts storlek litet är inte garanterat att fungera. Det beror på hur kompilatorn beskriver typen.
Du kanske undrar om det skulle vara bra att använda standardtypen bool i hela. Många av de gamla kodmönstren, till exempel BOOL-typen, uppfanns för att lösa problem som senare löstes i standard C++, så att byta från BOOL till den bool inbyggda typen är bara ett exempel på en sådan ändring som du överväger att göra när du har fått koden att börja köras i den nya versionen.
När vi har hanterat alla varningar som visas på standardnivån (nivå 3) har vi ändrat till nivå 4 för att fånga några ytterligare varningar. Den första som dök upp var följande:
warning C4100: 'nTab': unreferenced formal parameter
Koden som skapade varningen var följande.
virtual void OnSelectTab(int nTab) {};
Detta verkar ofarligt nog, men eftersom vi ville ha en ren kompilering med /W4 och /WX set, kommenterade vi bara ut variabelnamnet och lämnade det för läsbarhetens skull.
virtual void OnSelectTab(int /*nTab*/) {};
Andra varningar som vi fick var användbara för allmän kodrensning. Det finns ett antal implicita konverteringar från int eller unsigned int till WORD (som är en typedef för unsigned short). Dessa innebär en eventuell dataförlust. Vi har lagt till en rollbesättning i WORD i dessa fall.
En annan varning på nivå 4 som vi fick för den här koden var:
warning C4211: nonstandard extension used: redefined extern to static
Problemet uppstår när en variabel först deklarerades externoch sedan deklarerades staticsenare . Innebörden av dessa två lagringsklassspecificerare är ömsesidigt uteslutande, men detta tillåts som ett Microsoft-tillägg. Om du vill att koden ska vara portabel för andra kompilatorer, eller om du vill kompilera den med /Za (ANSI-kompatibilitet), ändrar du deklarationerna så att de har matchande lagringsklassspecificerare.
Steg 11. Portning från MBCS till Unicode
Observera att i Windows-världen, när vi säger Unicode, menar vi vanligtvis UTF-16. Andra operativsystem som Linux använder UTF-8, men Windows gör det vanligtvis inte. MBCS-versionen av MFC är inaktuell i Visual Studio 2013 och 2015, men den är inte längre inaktuell i Visual Studio 2017. Om du använder Visual Studio 2013 eller 2015, innan du tar steget att faktiskt porta MBCS-kod till UTF-16 Unicode, kanske vi tillfälligt vill eliminera varningarna om att MBCS är inaktuellt, för att utföra annat arbete eller skjuta upp portningen till en lämplig tid. Den aktuella koden använder MBCS och för att fortsätta med det måste vi installera ANSI/MBCS-versionen av MFC. Det ganska stora MFC-biblioteket är inte en del av visual Studio Desktop-standardutvecklingen med C++ -installation, så det måste väljas från de valfria komponenterna i installationsprogrammet. Se MFC MBCS DLL-tillägg. När du har laddat ned detta och startat om Visual Studio kan du kompilera och länka till MBCS-versionen av MFC, men för att bli av med varningarna om MBCS om du använder Visual Studio 2013 eller 2015 bör du också lägga till NO_WARN_MBCS_MFC_DEPRECATION i listan över fördefinierade makron i avsnittet Förprocessor för projektegenskaper. eller i början av din stdafx.h-rubrikfil eller annan gemensam rubrikfil.
Nu har vi några länkfel.
fatal error LNK1181: cannot open input file 'mfc42d.lib'
LNK1181 beror på att en inaktuell statisk biblioteksversion av mfc ingår i länkningsindata. Detta krävs inte längre eftersom vi kan länka MFC dynamiskt, så vi behöver bara ta bort alla MFC-statiska bibliotek från indataegenskapen i avsnittet Linker i projektegenskaperna. Det här projektet använder /NODEFAULTLIB också alternativet och visar i stället alla biblioteksberoenden.
msvcrtd.lib;msvcirtd.lib;kernel32.lib;user32.lib;gdi32.lib;advapi32.lib;Debug\SpyHk55.lib;%(AdditionalDependencies)
Nu ska vi faktiskt uppdatera den gamla MBCS-koden (Multi-byte Character Set) till Unicode. Eftersom det här är ett Windows-program som är intimt kopplat till Windows-skrivbordsplattformen kommer vi att portera det till UTF-16 Unicode som Windows använder. Om du skriver plattformsoberoende kod eller porterar ett Windows-program till en annan plattform kanske du vill överväga att portera till UTF-8, som används ofta på andra operativsystem.
Vid övergång till UTF-16 Unicode måste vi bestämma om vi fortfarande vill ha möjligheten att kompilera till MBCS eller inte. Om vi vill ha möjlighet att stödja MBCS bör vi använda TCHAR-makrot som teckentyp, som matchar antingen char eller wchar_t, beroende på om _MBCS eller _UNICODE definieras under kompilering. Att växla till TCHAR- och TCHAR-versioner av olika API:er i stället för wchar_t och dess associerade API:er innebär att du kan återgå till en MBCS-version av koden genom att helt enkelt definiera _MBCS makro i stället för _UNICODE. Förutom TCHAR finns det en mängd olika TCHAR-versioner, till exempel ofta använda typedefs, makron och funktioner. Till exempel LPCTSTR i stället för LPCSTR och så vidare. Under Konfigurationsegenskaper i avsnittet Allmänt i dialogrutan projektegenskaper ändrar du egenskapen Teckenuppsättning från Använd MBCS-teckenuppsättning till Använd Unicode-teckenuppsättning. Den här inställningen påverkar vilket makro som är fördefinierat under kompilering. Det finns både ett UNICODE-makro och ett _UNICODE makro. Projektegenskapen påverkar bägge konsekvent. Windows-huvuden använder UNICODE där Visual C++-huvuden som MFC använder _UNICODE, men när en definieras definieras alltid den andra.
Det finns en bra guide till portning från MBCS till UTF-16 Unicode med TCHAR. Vi väljer den här vägen. Först ändrar vi egenskapen Teckenuppsättning till Använd Unicode-teckenuppsättning och återskapar projektet.
Vissa platser i koden använde redan TCHAR, tydligen i väntan på att så småningom stödja Unicode. Vissa var det inte. Vi sökte efter instanser av CHAR, som är en typedef för char, och ersatte de flesta av dem med TCHAR. Dessutom letade vi efter sizeof(CHAR). När vi ändrade från CHAR till TCHAR var vi vanligtvis tvungna att ändra till sizeof(TCHAR) eftersom detta ofta användes för att fastställa antalet tecken i en sträng. Om du använder fel typ här genereras inte ett kompilatorfel, så det är värt att ägna lite uppmärksamhet åt det här fallet.
Den här typen av fel är mycket vanlig när du har bytt till Unicode.
error C2664: 'int wsprintfW(LPWSTR,LPCWSTR,...)': cannot convert argument 1 from 'CHAR [16]' to 'LPWSTR'
Här är ett exempel på kod som genererar följande:
wsprintf(szTmp, "%d.%2.2d.%4.4d", rmj, rmm, rup);
Vi lägger _T runt strängliteralen för att ta bort felet.
wsprintf(szTmp, _T("%d.%2.2d.%4.4d"), rmj, rmm, rup);
Makrot _T har effekten av att kompilera en strängliteral som en char sträng eller en wchar_t sträng, beroende på inställningen för MBCS eller UNICODE. Om du vill ersätta alla strängar med _T i Visual Studio öppnar du först kryssrutan Snabb ersättning (Tangentbord: Ctrl+F) eller Ersätt i filer (tangentbord: Ctrl+Skift+H) och väljer sedan kryssrutan Använd reguljära uttryck . Ange ((\".*?\")|('.+?')) som söktext och _T($1) som ersättningstext. Om du redan har _T makrot runt vissa strängar, lägger den här proceduren till det igen, och det kan också hitta fall där du inte vill _T, till exempel när du använder #include, så det är bäst att använda Ersätt nästa i stället för Ersätt alla.
Den här funktionen , wsprintf, definieras faktiskt i Windows-huvudena, och dokumentationen för den rekommenderar att den inte används på grund av eventuell buffertöverskridning. Ingen storlek anges för bufferten szTmp , så det finns inget sätt för funktionen att kontrollera att bufferten kan innehålla alla data som ska skrivas till den. Se nästa avsnitt om portning till säker CRT, där vi åtgärdar andra liknande problem. Det slutade med att vi ersatte den med _stprintf_s.
Ett annat vanligt fel som du ser när du konverterar till Unicode är detta.
error C2440: '=': cannot convert from 'char *' to 'TCHAR *'
Koden som genererar den är följande:
pParentNode->m_szText = new char[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);
Även om _tcscpy funktionen användes, vilket är TCHAR strcpy-funktionen för att kopiera en sträng, var bufferten som allokerades en char buffert. Detta ändras enkelt till TCHAR.
pParentNode->m_szText = new TCHAR[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);
På samma sätt ändrade vi LPSTR (Långpekare till STRing) och LPCSTR (långpekare till konstant STRing) till LPTSTR (långpekare till TCHAR STRing) respektive LPCTSTR (longpekare till konstant TCHAR STRing) när det är motiverat av ett kompilatorfel. Vi valde att inte göra sådana ersättningar genom att använda global sökning och ersättning, eftersom varje situation måste undersökas individuellt. I vissa fall är versionen char eftertraktad, till exempel vid hantering av vissa Windows-meddelanden som använder Windows-strukturer med suffixet A. I Windows-API:et betyder suffixet A ASCII eller ANSI (och gäller även för MBCS), och suffixet W betyder breda tecken eller UTF-16 Unicode. Det här namngivningsmönstret används i Windows-huvudena, men vi följde det också i Spy++-koden när vi var tvungna att lägga till en Unicode-version av en funktion som redan har definierats i endast en MBCS-version.
I vissa fall var vi tvungna att ersätta en typ för att använda en version som löser rätt (till exempel WNDCLASS i stället för WNDCLASSA).
I många fall var vi tvungna att använda den generiska versionen (makro) av ett Win32-API som GetClassName (i stället GetClassNameAför ). I meddelandehanterarens switch-instruktion är vissa meddelanden MBCS- eller Unicode-specifika, i dessa fall var vi tvungna att ändra koden för att uttryckligen anropa MBCS-versionen, eftersom vi ersatte de generiskt namngivna funktionerna med A - och W-specifika funktioner och lade till ett makro för det generiska namnet som matchar rätt A - eller W-namn baserat på om UNICODE har definierats. I många delar av koden, när vi bytte till att definiera _UNICODE, väljs W-versionen nu även när A-versionen är vad som önskas.
Det finns några platser där särskilda åtgärder måste vidtas. All användning av WideCharToMultiByte eller MultiByteToWideChar kan kräva en närmare titt. Här är ett exempel där WideCharToMultiByte användes.
BOOL C3dDialogTemplate::GetFont(CString& strFace, WORD& nFontSize)
{
ASSERT(m_hTemplate != NULL);
DLGTEMPLATE* pTemplate = (DLGTEMPLATE*)GlobalLock(m_hTemplate);
if ((pTemplate->style & DS_SETFONT) == 0)
{
GlobalUnlock(m_hTemplate);
return FALSE;
}
BYTE* pb = GetFontSizeField(pTemplate);
nFontSize = *(WORD*)pb;
pb += sizeof (WORD);
WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)pb, -1,
strFace.GetBufferSetLength(LF_FACESIZE), LF_FACESIZE, NULL, NULL);
strFace.ReleaseBuffer();
GlobalUnlock(m_hTemplate);
return TRUE;
}
För att åtgärda detta var vi tvungna att förstå att anledningen till att detta gjordes var att kopiera en bred teckensträng som representerar namnet på ett teckensnitt till den interna bufferten för en CString, strFace. Detta krävde lite annorlunda kod för flerbytessträngar CString som för breda teckensträngar CString , så vi lade till en #ifdef i det här fallet.
#ifdef _MBCS
WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)pb, -1,
strFace.GetBufferSetLength(LF_FACESIZE), LF_FACESIZE, NULL, NULL);
strFace.ReleaseBuffer();
#else
wcscpy(strFace.GetBufferSetLength(LF_FACESIZE), (LPCWSTR)pb);
strFace.ReleaseBuffer();
#endif
Naturligtvis bör vi använda wcscpy istället för wcscpy_s, den säkrare versionen. I nästa avsnitt beskrivs detta.
Som en kontroll av vårt arbete bör vi återställa teckenuppsättningen till Använd flerbytesteckenuppsättning och se till att koden fortfarande kompileras med MBCS och Unicode. Naturligtvis ska ett fullständigt testpass köras i den omkompilerade appen efter alla dessa ändringar.
I vårt arbete med den här Spy++-lösningen tog det ungefär två arbetsdagar för en genomsnittlig C++-utvecklare att konvertera koden till Unicode. Det inkluderade inte omtestningstiden.
Steg 12. Portering för att använda Secure CRT
Nästa steg är att porta koden för att använda de säkra versionerna (versionerna med _s suffixet) för CRT-funktionerna. I det här fallet är den allmänna strategin att ersätta funktionen med den _s versionen och sedan vanligtvis lägga till nödvändiga ytterligare buffertstorleksparametrar. I många fall är detta enkelt eftersom storleken är känd. I andra fall, när storleken inte är omedelbart tillgänglig, är det nödvändigt att lägga till ytterligare parametrar i funktionen som använder CRT-funktionen, eller kanske undersöka användningen av målbufferten och se vilka lämpliga storleksgränser som är.
Visual C++ ger en metod för att göra det enklare att säkra koden utan att lägga till så många storleksparametrar, genom att använda mallöverlagringar. Eftersom dessa överlagringar är mallar är de bara tillgängliga när de kompileras som C++, inte som C. Spyxxhk är ett C-projekt, så tricket fungerar inte för det. Men Spyxx är inte och vi kan använda tricket. Tricket är att lägga till en rad som den här på en plats där den kommer att kompileras i varje fil i projektet, till exempel i stdafx.h:
#define _CRT_SECURE_TEMPLATE_OVERLOADS 1
När du definierar att när bufferten är en matris, i stället för en rå pekare, härleds dess storlek från matristypen och används som storleksparameter, utan att du behöver ange den. Det hjälper till att minska komplexiteten i att skriva om koden. Du måste fortfarande ersätta funktionsnamnet med _s-versionen, men det kan ofta göras med en sök- och ersättningsoperation.
Returvärdena för vissa funktioner har ändrats. Till exempel returnerar _itoa_s (och _itow_s och makrot _itot_s) en felkod (errno_t) i stället för strängen. Så i dessa fall måste du flytta anropet till _itoa_s till en separat rad och ersätta det med buffertens identifierare.
Några vanliga fall: för memcpy, när vi bytte till memcpy_s, lade vi ofta till storleken på strukturen som kopieras till. För de flesta strängar och buffertar bestäms storleken på matrisen eller bufferten enkelt utifrån buffertdeklarationen eller genom att hitta var bufferten ursprungligen allokerades. I vissa situationer måste du bestämma hur stor buffert som faktiskt är tillgänglig, och om den informationen inte är tillgänglig i omfånget för den funktion som du ändrar bör den läggas till som ytterligare en parameter och anropskoden bör ändras för att tillhandahålla informationen.
Med dessa tekniker tog det ungefär en halv dag att konvertera koden för att använda de säkra CRT-funktionerna. Om du väljer att inte överlagla mallen och lägga till storleksparametrarna manuellt skulle det förmodligen ta två eller tre gånger mer tid.
Steg 13. /Zc:forScope - är inaktuell
Eftersom Visual C++ 6.0 överensstämmer kompilatorn med den aktuella standarden, vilket begränsar omfånget för variabler som deklareras i en loop till loopens omfång. Kompilatoralternativet /Zc:forScope (Force Conformance for Loop Scope in the project properties) styr om detta rapporteras som ett fel eller inte. Vi bör uppdatera koden så att den är överensstämmande och lägga till deklarationer precis utanför loopen. Om du vill undvika att göra kodändringarna kan du ändra inställningen i avsnittet Språk i C++-projektegenskaperna till No (/Zc:forScope-). Tänk dock på att /Zc:forScope- kan tas bort i en framtida version av Visual C++, så så småningom måste koden ändras för att överensstämma med standarden.
De här problemen är relativt enkla att åtgärda, men beroende på din kod kan det påverka mycket kod. Här är ett typiskt problem.
int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
for (int n = 0; mszStrings[0] != 0; n++)
mszStrings = _tcschr(mszStrings, 0) + 1;
return(n);
}
Ovanstående kod genererar felet:
'n': undeclared identifier
Detta beror på att kompilatorn har inaktuellt ett kompilatoralternativ som tillät kod som inte längre överensstämmer med C++-standarden. I standarden begränsar deklareringen av en variabel i en loop dess omfång endast till loopen, så det vanliga sättet att använda en loopräknare utanför loopen kräver att deklarationen av räknaren också flyttas utanför loopen, som i följande reviderade kod:
int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
int n;
for (n = 0; mszStrings[0] != 0; n++)
mszStrings = _tcschr(mszStrings, 0) + 1;
return(n);
}
Sammanfattning
Porting Spy++ från den ursprungliga Visual C++ 6.0-koden till den senaste kompilatorn tog cirka 20 timmars kodningstid under loppet av ungefär en vecka. Vi uppgraderade direkt genom åtta versioner av produkten från Visual Studio 6.0 till Visual Studio 2015. Detta är nu den rekommenderade metoden för alla uppgraderingar på stora och små projekt.
Se även
Portning och uppgradering: Exempel och fallstudier
Tidigare fallstudie: COM Spy