Sdílet prostřednictvím


Průvodce přenosem: Spy++

Tato případová studie přenosu je navržená tak, aby vám poskytla představu o tom, jak typický projekt přenosu vypadá, jaké typy problémů se můžete setkat, a několik obecných tipů a triků pro řešení problémů s přenosem. Nemá být konečným vodítkem pro přenos, protože zkušenosti s přenosem projektu jsou velmi závislé na specifikách kódu.

Spy++

Spy++ je široce používaný nástroj pro diagnostiku grafického uživatelského rozhraní pro stolní počítače s Windows, který poskytuje nejrůznější informace o prvcích uživatelského rozhraní na ploše Windows. Zobrazuje úplnou hierarchii oken a poskytuje přístup k metadatům o každém okně a ovládacím prvku. Tato užitečná aplikace byla již mnoho let dodávána se sadou Visual Studio. Našli jsme starou verzi, která byla naposledy zkompilována v sadě Visual C++ 6.0 a portovala ji do sady Visual Studio 2015. Prostředí pro Visual Studio 2017 nebo Visual Studio 2019 by mělo být téměř stejné.

Tento případ jsme považovali za typický pro přenos desktopových aplikací systému Windows, které používají rozhraní MFC a rozhraní WIN32 API, zejména pro staré projekty, které nebyly aktualizovány s každou verzí visual C++ od verze Visual C++ od verze Visual C++ 6.0.

Krok 1. Převod souboru projektu

Soubor projektu, dva staré soubory .dsw z Visual C++ 6.0, byly snadno převedeny bez problémů, které vyžadují další pozornost. Jeden projekt je aplikace Spy++. Druhý je SpyHk, napsaný v jazyce C, podpůrné knihovny DLL. Složitější projekty nemusí být tak jednoduché, jak je popsáno zde.

Po upgradu těchto dvou projektů naše řešení vypadalo takto:

Screenshot of the Spy plus plus Solution.

Máme dva projekty, jeden s velkým počtem souborů C++ a další knihovnu DLL napsanou v jazyce C.

Krok 2. Problémy se souborem hlaviček

Při vytváření nově převedeného projektu je jednou z prvních věcí, kterou často najdete, že se nenašly soubory hlaviček, které váš projekt používá.

Jeden ze souborů, které nelze najít v nástroji Spy++, byl verstamp.h. Z internetového vyhledávání jsme zjistili, že to pochází ze sady DAO SDK, zastaralé datové technologie. Chtěli jsme zjistit, jaké symboly se z tohoto souboru hlaviček používaly, abychom zjistili, jestli byl tento soubor skutečně potřebný nebo jestli byly tyto symboly definované jinde, takže jsme zakomentovali deklaraci souboru záhlaví a znovu ji zkompilovali. Ukázalo se, že je potřeba jenom jeden symbol, 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

Nejjednodušší způsob, jak najít symbol v dostupných souborech include, je použít příkaz Najít v souborech (Ctrl+Shift+F) a určit adresáře zahrnutí jazyka Visual C++. Našli jsme ho v ntverp.h. Nahradili jsme verstamp.h za ntverp.h a tato chyba zmizela.

Krok 3. Nastavení výstupního souboru linkeru

Starší projekty někdy obsahují soubory umístěné v nekonvenčních umístěních, které můžou po upgradu způsobit problémy. V tomto případě musíme do vlastností projektu přidat $(SolutionDir) cestu Include, abychom zajistili, že Visual Studio najde některé soubory hlaviček, které jsou tam umístěné, a ne do jedné ze složek projektu.

NÁSTROJ MSBuild si stěžuje, že vlastnost Link.OutputFile neodpovídá hodnotám TargetPath a TargetName , které vystavují 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 je výstup sestavení (EXE, DLL, například) a je obvykle vytvořen z $(TargetDir)$(TargetName)$(TargetExt), dává cestu, název souboru a přípony. Jedná se o běžnou chybu při migraci projektů ze starého nástroje sestavení Visual C++ (vcbuild.exe) na nový nástroj sestavení (MSBuild.exe). Vzhledem k tomu, že došlo ke změně nástroje sestavení v sadě Visual Studio 2010, může k tomuto problému dojít při každé migraci projektu před 2010 na verzi 2010 nebo novější. Základním problémem je, že průvodce migrací projektu neaktualizuje hodnotu Link.OutputFile , protože není vždy možné určit, jaká hodnota by měla být založena na ostatních nastaveních projektu. Proto ho obvykle musíte nastavit ručně. Další podrobnosti najdete v tomto příspěvku na blogu Visual C++.

V tomto případě byla vlastnost Link.OutputFile v převedeného projektu nastavena na .\Debug\Spyxx.exe a .\Release\Spyxx.exe pro projekt Spy++ v závislosti na konfiguraci. Nejlepší volbou je jednoduše nahradit tyto pevně zakódované hodnoty $(TargetDir)$(TargetName)$(TargetExt) pro všechny konfigurace. Pokud to nepomůže, můžete je tam přizpůsobit nebo změnit vlastnosti v části Obecné, kde jsou tyto hodnoty nastavené (vlastnosti jsou Výstupní adresář, Cílový název a Rozšíření cíle). Mějte na paměti, že pokud vlastnost, kterou si prohlížíte, používá makra, můžete v rozevíracím seznamu zvolit možnost Upravit a zobrazit dialogové okno, které zobrazuje konečný řetězec s nahrazováním maker. Výběrem tlačítka Makra můžete zobrazit všechna dostupná makra a jejich aktuální hodnoty.

Krok 4. Aktualizace cílové verze Systému Windows

Další chyba značí, že prostředí MFC už nepodporuje verzi WINVER. WINVER pro Systém Windows XP je 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.

Systém Windows XP již není společností Microsoft podporován, takže i když je cílení povoleno v sadě Visual Studio, měli byste pro ni ve svých aplikacích postupně ukončovat podporu a povzbuzovat uživatele, aby přijali nové verze Windows.

Chcete-li se této chyby zbavit, definujte WINVER aktualizací nastavení Vlastností projektu na nejnižší verzi Systému Windows, na kterou chcete aktuálně cílit. Tady najdete tabulku hodnot pro různé verze Windows.

Soubor stdafx.h obsahoval některé z těchto definic maker.

#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 nastavíme na Windows 7. Pokud použijete makro pro Windows 7 (_WIN32_WINNT_WIN7), a ne samotnou hodnotu (0x0601), je lepší kód později přečíst.

#define WINVER _WINNT_WIN32_WIN7 // Minimum targeted Windows version is Windows 7

Krok 5. Chyby linkeru

Při těchto změnách se projekt SpyHk (DLL) sestaví, ale vytvoří chybu linkeru.

LINK : warning LNK4216: Exported entry point _DLLEntryPoint@12

Vstupní bod knihovny DLL by neměl být exportován. Vstupní bod je určen pouze k volání zavaděčem při prvním načtení knihovny DLL do paměti, takže by neměl být v tabulce exportu, která je určena pro ostatní volající. Jen musíme zajistit, aby k ní nebyla připojena direktiva __declspec(dllexport) . V spyxxhk.c, musíme jej odstranit ze dvou míst, prohlášení a definice DLLEntryPoint. Nikdy nemělo smysl používat tuto direktivu, ale předchozí verze linkeru a kompilátoru ji neoznačily jako problém. Novější verze linkeru zobrazí upozornění.

// deleted __declspec(dllexport)
BOOL WINAPI DLLEntryPoint(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);

Projekt knihovny C DLL, SpyHK.dll, nyní sestaví a odkazuje bez chyby.

Krok 6. Zastaralé soubory hlaviček

V tuto chvíli začneme pracovat na hlavním spustitelném projektu Spyxx.

Nelze najít několik dalších souborů zahrnutí: ctl3d.h a penwin.h. I když může být užitečné hledat na internetu, abyste se pokusili zjistit, co záhlaví obsahovalo, někdy informace nejsou tak užitečné. Zjistili jsme, že ctl3d.h byl součástí sady Exchange Development Kit a poskytoval podporu určitého stylu ovládacích prvků ve Windows 95 a penwin.h souvisí s Windows Pen Computing, zastaralé rozhraní API. V tomto případě jednoduše zakomentujeme #include řádek a řešíme nedefinované symboly stejně jako u verstamp.h. Všechno, co souvisí s 3D ovládacími prvky nebo pen computingem, bylo z projektu odebráno.

Vzhledem k projektu s mnoha chybami kompilace, které postupně odstraňujete, není reálné najít všechny použití zastaralého rozhraní API okamžitě při odebrání direktivy #include . Nezjistila jsme ji okamžitě, ale v nějakém pozdějším okamžiku přišla na chybu, která WM_DLGBORDER nebyla definována. Je to vlastně jen jeden mnoho nedefinovaných symbolů, které pocházejí z ctl3d.h. Jakmile zjistíme, že souvisí se zastaralým rozhraním API, odebrali jsme všechny odkazy v kódu.

Krok 7. Aktualizace starého kódu iostreams

Další chyba je společná se starým kódem C++, který používá iostreamy.

mstream.h(40): fatal error C1083: Cannot open include file: 'iostream.h': No such file or directory

Problémem je, že původní knihovna iostreams byla odebrána a nahrazena. Staré iostreamy musíme nahradit novějšími standardy.

#include <iostream.h>
#include <strstrea.h>
#include <iomanip.h>

Mezi tyto aktualizace patří:

#include <iostream>
#include <sstream>
#include <iomanip>

S touto změnou máme problémy s ostrstream, které se už nepoužívají. Vhodným nahrazením je ostringstream. Snažíme se přidat typedef , ostrstream aby se zabránilo příliš změně kódu, a to alespoň jako začátek.

typedef std::basic_ostringstream<TCHAR> ostrstream;

V současné době je projekt sestaven pomocí mbCS (Vícebajtová znaková sada), takže char je vhodný datový typ znaku. Abychom však umožnili jednodušší aktualizaci kódu na UTF-16 Unicode, aktualizujeme to na TCHAR, který se přeloží na char vlastnost wchar_t znakové sady v nastavení projektu nebo v závislosti na tom, zda je vlastnost znaková sada v nastavení projektu nastavena na MBCS nebo Unicode.

Je potřeba aktualizovat několik dalších částí kódu. Základní třídu iosios_basejsme nahradili a nahradili jsme ostream je basic_ostream<T>. Přidáme dva další definice typedef a tato část se zkompiluje.

typedef std::basic_ostream<TCHAR> ostream;
typedef ios_base ios;

Použitítěchtoch V případě trvalejšího řešení bychom mohli aktualizovat každý odkaz na přejmenované nebo zastaralé rozhraní API.

Tady je další chyba.

error C2039: 'freeze': is not a member of 'std::basic_stringbuf<char,std::char_traits<char>,std::allocator<char>>'

Dalším problémem je, že basic_stringbuf nemá metodu freeze . Metoda freeze se používá k zabránění nevrácení paměti ve starém ostream. Nepotřebujeme ho teď, když používáme nový ostringstream. Volání můžeme odstranit freeze.

//rdbuf()->freeze(0);

Na sousedních řádcích došlo k dalším dvěma chybám. První si stěžuje na použití ends, což je vstupně-výstupní manipulátor staré iostream knihovny, který přidává ukončovací znak null do řetězce. Druhá z těchto chyb vysvětluje, že výstup str metody nelze přiřadit k nekonstančnímu ukazateli.

// 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'

Použití nové knihovny datových proudů není potřeba, ends protože řetězec je vždy ukončen s hodnotou null, aby bylo možné řádek odebrat. U druhého problému problém spočívá v tom, že teď str() nevrátí ukazatel na pole znaků pro řetězec; vrátí std::string typ. Řešením druhé je změnit typ na LPCSTR metodu c_str() a použít ji k vyžádání ukazatele.

//*this << ends;
LPCTSTR psz = str().c_str();

Při tomto kódu došlo k chybě, která nás na chvíli pletla.

MOUT << _T(" chUser:'") << chUser
<< _T("' (") << (INT)(UCHAR)chUser << _T(')');

Funkce MOUT makra se přeloží na *g_pmout objekt typu mstream. Třída mstream je odvozena od standardní výstupní třídy řetězce, std::basic_ostream<TCHAR>. Nicméně s _T kolem řetězcového literálu, který jsme umístili do přípravy na převod na Unicode, přetížit rozlišení operátoru << selže s následující chybovou zprávou:

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])'

Existuje tolik definic operátorů << , že tento druh chyby může být zastrašující. Po bližším pohledu na dostupná přetížení vidíme, že většina z nich je irelevantní a podrobněji se podíváme na mstream definici třídy, identifikovali jsme následující funkci, kterou si myslíme, že by v tomto případě měla být volána.

mstream& operator<<(LPTSTR psz)
{
  return (mstream&)ostrstream::operator<<(psz);
}

Důvodem, proč se nevolá, je to, že řetězcový literál má typ const wchar_t[10] , jak vidíte na posledním řádku této dlouhé chybové zprávy, takže převod na ukazatel mimo const není automatický. Tento operátor by však neměl upravovat vstupní parametr, takže vhodnější typ parametru je LPCTSTR (const char* při kompilaci jako MBCS a const wchar_t* Unicode), ne LPTSTR (char* při kompilaci jako MBCS a wchar_t* unicode). Provedení této změny opraví tuto chybu.

Tento typ převodu byl povolen ve starším, méně striktním kompilátoru, ale novější změny shody vyžadují přesnější kód.

Krok 8. Přísnější převody kompilátoru

Zobrazí se také mnoho chyb, jako je následující:

error C2440: 'static_cast': cannot convert from 'UINT (__thiscall CHotLinkCtrl::* )(CPoint)' to 'LRESULT (__thiscall CWnd::* )(CPoint)'

K chybě dochází v mapě zpráv, která je jednoduše makrem:

BEGIN_MESSAGE_MAP(CFindToolIcon, CWnd)
// other messages omitted...
ON_WM_NCHITTEST() // Error occurs on this line.
END_MESSAGE_MAP()

Přechod na definici tohoto makra, vidíme, že odkazuje na funkci 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)) },

Problém se musí s neshodou v ukazateli na typy členské funkce udělat. Problém není převodem typu CHotLinkCtrl třídy na CWnd typ třídy, protože se jedná o platný převod odvozený na základní. Problém je návratový typ: UINT vs. LRESULT. Funkce LRESULT se přeloží na LONG_PTR, což je 64bitový ukazatel nebo 32bitový ukazatel v závislosti na cílovém binárním typu, takže UINT se na tento typ nepřevedl. To není neobvyklé při upgradu kódu napsaného před 2005, protože návratový typ mnoha metod mapování zpráv se změnil z UINT na LRESULT v sadě Visual Studio 2005 jako součást 64bitové změny kompatibility. Návratový typ z UINT změníme v následujícím kódu na LRESULT:

afx_msg UINT OnNcHitTest(CPoint point);

Po změně máme následující kód:

afx_msg LRESULT OnNcHitTest(CPoint point);

Vzhledem k tomu, že existuje přibližně deset výskytů této funkce ve všech různých třídách odvozených z CWnd, je užitečné použít přejít k definici (klávesnice: F12) a Přejít na deklaraci (klávesnice: Ctrl+F12), když je kurzor na funkci v editoru, abyste je našli a přejděte k nim z okna nástroje Najít symbol. Přechod na definici je obvykle užitečnější ze dvou. Příkaz Přejít na deklaraci najde deklarace jiné než definice deklarace třídy, jako jsou deklarace přátelských tříd nebo předávané odkazy.

Krok 9. Změny mfc

Další chyba také souvisí se změněným typem deklarace a také v makrech.

error C2440: 'static_cast': cannot convert from 'void (__thiscall CFindWindowDlg::* )(BOOL,HTASK)' to 'void (__thiscall CWnd::* )(BOOL,DWORD)'

Problémem je, že druhý parametr CWnd::OnActivateApp změny z HTASK na DWORD. K této změně došlo v 2002 vydání sady Visual Studio, Visual Studio .NET.

afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);

Musíme odpovídajícím způsobem aktualizovat deklarace OnActivateApp v odvozených třídách následujícím způsobem:

afx_msg void OnActivateApp(BOOL bActive, DWORD dwThreadId);

V tuto chvíli můžeme projekt zkompilovat. Existuje však několik upozornění, která je potřeba provést, a existují volitelné části upgradu, například převod z MBCS na Unicode nebo zlepšení zabezpečení pomocí funkcí Secure CRT.

Krok 10. Adresování upozornění kompilátoru

Pokud chcete získat úplný seznam upozornění, měli byste v řešení místo běžného sestavení provést znovu sestavit vše , jen abyste měli jistotu, že vše, co bylo dříve zkompilováno, bude rekompilováno, protože z aktuální kompilace se zobrazí pouze zprávy upozornění. Druhou otázkou je, jestli chcete přijmout aktuální úroveň upozornění, nebo použít vyšší úroveň upozornění. Při přenosu velkého množství kódu, zejména starého kódu, může být použití vyšší úrovně upozornění vhodné. Můžete také začít s výchozí úrovní upozornění a pak zvýšit úroveň upozornění, aby se zobrazila všechna upozornění. Pokud používáte /Wall, zobrazí se v souborech hlaviček systému několik upozornění, takže mnoho lidí používá /W4 k získání největších upozornění v kódu, aniž by se zobrazovala upozornění pro záhlaví systému. Pokud chcete, aby se upozornění zobrazovala jako chyby, přidejte tuto /WX možnost. Tato nastavení jsou v části C/C++ dialogového okna Vlastnosti projektu.

Jedna z metod třídy CSpyApp vytváří upozornění na funkci, která již není podporována.

void SetDialogBkColor() {CWinApp::SetDialogBkColor(::GetSysColor(COLOR_BTNFACE));}

Upozornění je následující.

warning C4996: 'CWinApp::SetDialogBkColor': CWinApp::SetDialogBkColor is no longer supported. Instead, handle WM_CTLCOLORDLG in your dialog

Zpráva WM_CTLCOLORDLG již byla zpracována v kódu nástroje Spy++, takže jedinou požadovanou změnou bylo odstranit všechny odkazy na SetDialogBkColor, které již není potřeba.

Další upozornění bylo jednoduché opravit zakomentováním názvu proměnné. Obdrželi jsme následující upozornění:

warning C4456: declaration of 'lpszBuffer' hides previous local declaration

Kód, který to vygeneruje, zahrnuje makro.

DECODEPARM(CB_GETLBTEXT)
{
  P2WPOUT();

  P2LPOUTPTRSTR;
  P2IFDATA()
  {
    PARM(lpszBuffer, PPACK_STRINGORD, ED2);

    INDENT();

    P2IFISORD(lpszBuffer)
    {
      P2OUTORD(lpszBuffer);
    }
    else
    {
      PARM(lpszBuffer, LPTSTR, ED2);
      P2OUTS(lpszBuffer);
    }
  }
}

Těžké použití maker, jako je v tomto kódu, má tendenci znesnadnit údržbu kódu. V tomto případě makra obsahují deklarace proměnných. Parm makra je definován takto:

#define PARM(var, type, src)type var = (type)src

Proměnná se lpszBuffer proto deklaruje dvakrát ve stejné funkci. Není to jednoduché opravit, protože by to bylo v případě, že kód nepoužívá makra (jednoduše odeberte druhou deklaraci typu). Jak je tomu tak, máme nešťastnou volbu rozhodnout, zda přepsat kód makra jako běžný kód (zdlouhavý a pravděpodobně náchylný k chybám) nebo zakázat upozornění.

V tomto případě se rozhodneme upozornění zakázat. Můžeme to udělat tak, že přidáme direktivu pragma následujícím způsobem:

#pragma warning(disable : 4456)

Při zakazování upozornění můžete chtít zakázat účinek zakázání jenom na kód, který upozornění vygeneruje, aby se zabránilo potlačení upozornění, když může poskytnout užitečné informace. Přidáme kód pro obnovení upozornění hned za řádek, který ho vytvoří, nebo ještě lépe, protože k tomuto upozornění dochází v makrech, použijte klíčové slovo __pragma , které funguje v makrech (#pragma nefunguje v makrech).

#define PARM(var, type, src)__pragma(warning(disable : 4456))  \
type var = (type)src \
__pragma(warning(default : 4456))

Další upozornění vyžaduje některé revize kódu. Rozhraní API GetVersion Win32 (a GetVersionEx) je zastaralé.

warning C4996: 'GetVersion': was declared deprecated

Následující kód ukazuje, jak se získá verze.

// check Windows version and set m_bIsWindows9x/m_bIsWindows4x/m_bIsWindows5x flags accordingly.
DWORD dwWindowsVersion = GetVersion();

Následuje spousta kódu, který zkoumá hodnotu dwWindowsVersion, abychom zjistili, jestli používáme Windows 95 a jakou verzi systém Windows NT. Vzhledem k tomu, že je všechno zastaralé, odebereme kód a postaráme se o všechny odkazy na tyto proměnné.

Článek Změny verze operačního systému ve Windows 8.1 a Windows Serveru 2012 R2 vysvětluje situaci.

Existují metody ve CSpyApp třídě, které dotazují verzi operačního systému: IsWindows9x, IsWindows4xa IsWindows5x. Dobrým výchozím bodem je předpokládat, že verze Windows, které máme v úmyslu podporovat (Windows 7 a novější), jsou blízko systém Windows NT 5, pokud jde o technologie používané touto starší aplikací. Použití těchto metod bylo řešit omezení starších operačních systémů. Proto jsme tyto metody změnili tak, aby vrátily hodnotu PRAVDA a IsWindows5x NEPRAVDA pro ostatní.

BOOL IsWindows9x() {/*return(m_bIsWindows9x);*/ return FALSE;  }
BOOL IsWindows4x() {/*return(m_bIsWindows4x);*/ return FALSE;  }
BOOL IsWindows5x() {/*return(m_bIsWindows5x);*/ return TRUE;  }

To nechalo jen několik míst, kde se interní proměnné používaly přímo. Vzhledem k tomu, že jsme tyto proměnné odebrali, zobrazí se několik chyb, které je potřeba explicitně řešit.

error C2065: 'm_bIsWindows9x': undeclared identifier
void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
  pCmdUI->Enable(m_bIsWindows9x || hToolhelp32 != NULL);
}

Mohli bychom ho nahradit voláním metody nebo jednoduše předat true a odebrat starý speciální případ pro Windows 9x.

void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
  pCmdUI->Enable(TRUE /*!m_bIsWindows9x || hToolhelp32 != NULL*/);
}

Konečné upozornění na výchozí úrovni (3) musí být v bitovém poli.

treectl.cpp(1656): warning C4463: overflow; assigning 1 to bit-field that can only hold values from -1 to 0

Kód, který to aktivuje, je následující.

m_bStdMouse = TRUE;

m_bStdMouse Deklarace označuje, že se jedná o bitové pole.

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;

Tento kód byl napsán před předdefinovaný typ bool byl podporován v jazyce Visual C++. V takovém kódu byla boOL pro typedefint. Typ int je signed typ a bitová reprezentace je signed int použít první bit jako znaménko, takže bitové pole typu int by mohlo být interpretováno jako reprezentace 0 nebo -1, pravděpodobně není to, co bylo zamýšleno.

Nevěděli byste tím, že se podíváte na kód, proč se jedná o bitová pole. Byl záměr zachovat velikost objektu malý nebo tam, kde se používá binární rozložení objektu? Změnili jsme je na běžné členy BOOL, protože jsme neviděli žádný důvod pro použití bitového pole. Použití bitových polí k zachování malé velikosti objektu není zaručeno, že bude fungovat. Záleží na tom, jak kompilátor stanoví typ.

Možná vás zajímá, jestli by použití standardního typu bool v celém prostředí bylo užitečné. Mnoho starých vzorů kódu, jako je typ BOOL, byly vynalezeny k řešení problémů, které byly později vyřešeny ve standardním jazyce C++, takže změna z boOL na bool předdefinovaný typ je jen jedním z příkladů takové změny, kterou byste měli zvážit po počátečním spuštění kódu v nové verzi.

Jakmile se podíváme na všechna upozornění, která se zobrazují na výchozí úrovni (úroveň 3), změnili jsme se na úroveň 4, abychom zachytili několik dalších upozornění. První, co se má objevit, bylo následující:

warning C4100: 'nTab': unreferenced formal parameter

Kód, který toto upozornění vytvořil, byl následující.

virtual void OnSelectTab(int nTab) {};

Zdá se, že je to dostatečně neškodné, ale protože jsme chtěli čistou kompilaci s /W4 a /WX setem, jednoduše jsme zakomentovali název proměnné a nechali jsme ji kvůli čitelnosti.

virtual void OnSelectTab(int /*nTab*/) {};

Další upozornění, která jsme obdrželi, byly užitečné pro obecné vyčištění kódu. Existuje několik implicitních převodů z int nebo unsigned int do WORDu (což je typedef pro unsigned short). To zahrnuje možnou ztrátu dat. V těchto případech jsme do WORDu přidali přetypování.

Další upozornění úrovně 4, které jsme získali pro tento kód, bylo:

warning C4211: nonstandard extension used: redefined extern to static

K problému dochází, když byla proměnná poprvé deklarována extern, pak později deklarována static. Význam těchto dvou specifikátorů třídy úložiště se vzájemně vylučují, ale to je povoleno jako rozšíření Microsoftu. Pokud byste chtěli, aby byl kód přenosný do jiných kompilátorů nebo jste ho chtěli zkompilovat ( /Za kompatibilita ANSI), mohli byste změnit deklarace tak, aby měly odpovídající specifikátory třídy úložiště.

Krok 11. Přenos z MBCS do Unicode

Všimněte si, že ve Windows, když říkáme Unicode, obvykle znamená UTF-16. Jiné operační systémy, jako je Linux, používají UTF-8, ale Windows obecně ne. Verze KNIHOVNY MFC mbCS byla v sadě Visual Studio 2013 a 2015 zastaralá, ale už není v sadě Visual Studio 2017 zastaralá. Pokud používáte Sadu Visual Studio 2013 nebo 2015, před provedením kroku na port MBCS kódu MBCS do kódování UTF-16 Unicode můžeme dočasně eliminovat upozornění, že služba MBCS je zastaralá, aby bylo možné provést jinou práci nebo odložit přenos až do vhodného času. Aktuální kód používá službu MBCS a pokračovat v tom, že potřebujeme nainstalovat verzi PROSTŘEDÍ MFC ANSI/MBCS. Spíše velká knihovna MFC není součástí výchozího vývoje desktopových aplikací sady Visual Studio s instalací jazyka C++ , takže musí být vybrána z volitelných součástí instalačního programu. Viz doplněk MFC MBCS DLL. Po stažení tohoto a restartování sady Visual Studio můžete zkompilovat a propojit s verzí KNIHOVNY MFC mbCS, ale pokud používáte Visual Studio 2013 nebo 2015, měli byste do seznamu předdefinovaných maker v části Preprocesor vlastností projektu přidat také NO_WARN_MBCS_MFC_DEPRECATION. nebo na začátku souboru hlavičky stdafx.h nebo jiného společného hlavičkového souboru.

Teď máme nějaké chyby linkeru.

fatal error LNK1181: cannot open input file 'mfc42d.lib'

LNK1181 dochází, protože zastaralá verze knihovny MFC je součástí vstupu linkeru. Už to není potřeba, protože mfc můžeme dynamicky propojit, takže stačí odebrat všechny statické knihovny MFC z vlastnosti Vstupu v části Linker vlastností projektu. Tento projekt také používá /NODEFAULTLIB možnost a místo toho vypíše všechny závislosti knihovny.

msvcrtd.lib;msvcirtd.lib;kernel32.lib;user32.lib;gdi32.lib;advapi32.lib;Debug\SpyHk55.lib;%(AdditionalDependencies)

Teď pojďme skutečně aktualizovat starý kód vícebajtové znakové sady (MBCS) na Unicode. Vzhledem k tomu, že se jedná o aplikaci pro Windows, která je úzce svázaná s desktopovou platformou Windows, budeme ji portovat do kódování UTF-16 Unicode, které windows používá. Pokud píšete kód pro různé platformy nebo portujete aplikaci pro Windows na jinou platformu, můžete zvážit přenos do UTF-8, který se běžně používá v jiných operačních systémech.

Portováním do kódování UTF-16 Unicode se musíme rozhodnout, jestli chceme, aby se možnost zkompilovat do mbCS, nebo ne. Pokud chceme mít možnost podporovat mbCS, měli bychom použít makro TCHAR jako typ znaku, který se přeloží na buď char nebo wchar_t, v závislosti na tom, jestli je během kompilace definován _MBCS nebo _UNICODE. Přepnutí na TCHAR a verze TCHAR různých rozhraní API místo wchar_t přidružených rozhraní API znamená, že se můžete vrátit k verzi kódu MBCS jednoduše definováním _MBCS makra místo _UNICODE. Kromě TCHAR existuje celá řada verzí TCHAR, jako jsou široce používané definice typedef, makra a funkce. Například LPCTSTR místo LPCSTR atd. V dialogovém okně vlastností projektu v části Vlastnosti konfigurace v části Obecné změňte vlastnost Znaková sada z použít znakové sady MBCS na použití znakové sady Unicode. Toto nastavení má vliv na předdefinované makro během kompilace. K dispozici je makro UNICODE i _UNICODE makro. Vlastnost projektu ovlivňuje oba konzistentně. Hlavičky Systému Windows používají unicode, kde hlavičky jazyka Visual C++, jako je například MFC, používají _UNICODE, ale když je definován, druhá je vždy definována.

Dobrý průvodce portováním z MBCS do kódování UTF-16 Unicode pomocí TCHAR existuje. Tuto trasu zvolíme. Nejprve změníme vlastnost Znaková sada na použití znakové sady Unicode a znovu sestavíme projekt.

Některá místa v kódu už používala TCHAR, zřejmě v očekávání, že nakonec podporuje Unicode. Někteří nebyli. Hledali jsme instance znaku CHAR, což je typedef znak , chara nahradili jsme většinu z nich TCHAR. Také jsme hledali sizeof(CHAR). Kdykoli jsme se změnili z ZNAK na TCHAR, museli jsme se obvykle změnit na to, na sizeof(TCHAR) co se často použilo k určení počtu znaků v řetězci. Použití nesprávného typu zde nevytvoří chybu kompilátoru, takže stojí za to věnovat trochu pozornosti tomuto případu.

Tento typ chyby je velmi běžný hned po přepnutí na Unicode.

error C2664: 'int wsprintfW(LPWSTR,LPCWSTR,...)': cannot convert argument 1 from 'CHAR [16]' to 'LPWSTR'

Tady je příklad kódu, který vytvoří toto:

wsprintf(szTmp, "%d.%2.2d.%4.4d", rmj, rmm, rup);

Kolem řetězcového literálu jsme dali _T, abychom chybu odebrali.

wsprintf(szTmp, _T("%d.%2.2d.%4.4d"), rmj, rmm, rup);

Makro _T má vliv na kompilaci řetězcového literálu wchar_t jako char řetězce nebo řetězce v závislosti na nastavení funkce MBCS nebo UNICODE. Pokud chcete nahradit všechny řetězce _T v sadě Visual Studio, nejprve otevřete pole Rychlé nahrazení (klávesnice: Ctrl+F) nebo Nahradit v souborech (klávesnice: Ctrl+Shift+H) a pak zvolte zaškrtávací políčko Použít regulární výrazy. Zadejte ((\".*?\")|('.+?')) jako hledaný text a _T($1) jako náhradní text. Pokud už máte makro _T kolem některých řetězců, tento postup ho znovu přidá a může také najít případy, kdy nechcete _T, například při použití #include, takže je nejlepší použít nahradit další místo nahradit vše.

Tato konkrétní funkce wsprintf je ve skutečnosti definována v hlavičkách Windows a dokumentace pro ni doporučuje, aby se nepoužívala kvůli možnému přetečení vyrovnávací paměti. Pro vyrovnávací paměť není udělena szTmp žádná velikost, takže neexistuje způsob, jak funkce zkontrolovat, že vyrovnávací paměť může obsahovat všechna data, která se do ní zapisují. Podívejte se na další část týkající se přenosu do zabezpečeného CRT, ve kterém řešíme další podobné problémy. Nakonec jsme ho nahradili _stprintf_s.

Další běžnou chybou, kterou uvidíte při převodu na Unicode, je toto.

error C2440: '=': cannot convert from 'char *' to 'TCHAR *'

Kód, který ho vytvoří, je následující:

pParentNode->m_szText = new char[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);

I když byla _tcscpy použita funkce, což je funkce TCHAR strcpy pro kopírování řetězce, vyrovnávací paměť, která byla přidělena, byla char vyrovnávací paměť, která byla přidělena vyrovnávací paměti. To se snadno změní na TCHAR.

pParentNode->m_szText = new TCHAR[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);

Podobně jsme změnili LPSTR (dlouhý ukazatel na STRing) a LPCSTR (dlouhý ukazatel na konstantní string) na LPTSTR (dlouhý ukazatel na TCHAR STRing) a LPCTSTR (dlouhý ukazatel na konstantu TCHAR STRing) v uvedeném pořadí, pokud to zaručuje chyba kompilátoru. Rozhodli jsme se, že takové nahrazení nebudeme provádět pomocí globálního vyhledávání a nahrazení, protože každá situace se musela zkoumat jednotlivě. V některýchpřípadechchmch char Přípona A v rozhraní API systému Windows znamená ASCII nebo ANSI (a platí také pro MBCS) a přípona W znamená široké znaky nebo kódování UTF-16 Unicode. Tento vzor pojmenování se používá v hlavičkách Systému Windows, ale také jsme ho sledovali v kódu Spy++, když jsme museli přidat verzi Unicode funkce, která již byla definována pouze ve verzi MBCS.

V některých případech jsme museli nahradit typ pro použití verze, která se správně překládá (například WNDCLASS místo WNDCLASSA).

V mnoha případech jsme museli použít obecnou verzi (makro) rozhraní API Win32, například GetClassName (místo GetClassNameA). V příkazu switch obslužné rutiny zprávy jsou některé zprávy specifické pro MBCS nebo Unicode, v těchto případech jsme museli změnit kód tak, aby explicitně volal verzi MBCS, protože jsme nahradili obecně pojmenované funkcemi A a W a přidali makro pro obecný název, který se přeloží na správný název A nebo W na základě toho, jestli je definován kód UNICODE. Když jsme v mnoha částech kódu přepnuli na definování _UNICODE, verze W se teď vybere i v případě, že je verze A chtěná.

Na několika místech bylo nutné provést zvláštní akce. Jakékoli použití WideCharToMultiByte nebo MultiByteToWideChar může vyžadovat bližší pohled. Tady je jeden příklad, kde WideCharToMultiByte se používalo.

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;
}

Abychom to vyřešili, museli jsme pochopit, že důvodem bylo zkopírování širokého znakového řetězce představujícího název písma do vnitřní vyrovnávací paměti CString, strFace. To vyžadovalo trochu jiný kód pro vícebajtové CString řetězce jako pro široké řetězce znakůCString, takže jsme v tomto případě přidali.#ifdef

#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

Samozřejmě, místo wcscpy toho bychom měli používat wcscpy_s, bezpečnější verzi. Tento problém řeší další část.

Při kontrole naší práce bychom měli obnovit znakovou sadu tak, aby používala vícebajtovou znakovou sadu, a ujistěte se, že se kód stále kompiluje pomocí mbCS i Unicode. Bez nutnosti říct, že po všech těchto změnách by se měl v rekompilované aplikaci spustit úplná testovací úspěšnost.

V naší práci s tímto řešením Spy++ trvalo přibližně dva pracovní dny, než průměrný vývojář C++ převedl kód na Unicode. To nezahrnuly dobu opětovného testování.

Krok 12. Přenosy pro použití zabezpečeného CRT

Přenos kódu tak, aby používal zabezpečené verze (verze s příponou _s ) funkcí CRT, je dále. V tomto případě je obecnou strategií nahradit funkci verzí _s a pak obvykle přidat požadované další parametry velikosti vyrovnávací paměti. V mnoha případech je to jednoduché, protože velikost je známá. V jiných případech, kdy velikost není okamžitě dostupná, je nutné do funkce, která používá funkci CRT, přidat další parametry, nebo třeba prozkoumat využití cílové vyrovnávací paměti a zjistit, jaké jsou příslušné limity velikosti.

Visual C++ poskytuje trik, který usnadňuje zabezpečení kódu bez přidání tolik parametrů velikosti, a to pomocí přetížení šablony. Vzhledem k tomu, že tato přetížení jsou šablony, jsou k dispozici pouze při kompilaci jako C++, ne jako C. Spyxxhk je projekt jazyka C, takže trik pro to nebude fungovat. Spyxx však není a můžeme použít trik. Trikem je přidat řádek podobný tomuto na místě, kde bude zkompilován v každém souboru projektu, například v stdafx.h:

#define _CRT_SECURE_TEMPLATE_OVERLOADS 1

Když tuto hodnotu definujete, vždy, když je vyrovnávací paměť pole, nikoli nezpracovaný ukazatel, jeho velikost se odvodí z typu pole a použije se jako parametr velikosti, aniž byste ho museli zadat. To pomáhá snížit složitost přepsání kódu. Název funkce stále musíte nahradit verzí _s , ale to může často probíhat pomocí operace hledání a nahrazení.

Návratové hodnoty některých funkcí se změnily. Například _itoa_s (a _itow_s makro _itot_s) vrátí kód chyby (errno_t), nikoli řetězec. V takových případech tedy musíte volání _itoa_s přesunout na samostatný řádek a nahradit ho identifikátorem vyrovnávací paměti.

Některé běžné případy: pro memcpy, při přechodu na memcpy_s, jsme často přidali velikost struktury, do které se kopíruje. Podobně u většiny řetězců a vyrovnávacích pamětí je velikost pole nebo vyrovnávací paměti snadno určena z deklarace vyrovnávací paměti nebo zjištěním, kde byla vyrovnávací paměť původně přidělena. V některých situacích je potřeba určit, jak velká vyrovnávací paměť je ve skutečnosti dostupná, a pokud tyto informace nejsou k dispozici v oboru funkce, kterou upravujete, měla by se přidat jako další parametr a volající kód by měl být upraven tak, aby poskytoval informace.

S těmito technikami trvalo přibližně půl dne, než kód převedl na používání zabezpečených funkcí CRT. Pokud se rozhodnete, že šablonu nepřetížíte a parametry velikosti přidáte ručně, bude pravděpodobně trvat dvakrát nebo třikrát déle.

Krok 13. /Zc:forScope- je zastaralá

Vzhledem k tomu, že Visual C++ 6.0, kompilátor odpovídá aktuálnímu standardu, který omezuje rozsah proměnných deklarovaných ve smyčce na obor smyčky. Možnost kompilátoru /Zc:forScope (vynucení shody pro obor smyčky ve vlastnostech projektu) určuje, jestli se jedná o chybu nebo ne. Kód bychom měli aktualizovat tak, aby odpovídal, a přidat deklarace přímo mimo smyčku. Abyste se vyhnuli změnám kódu, můžete toto nastavení změnit v části Jazyk ve vlastnostech projektu C++ na No (/Zc:forScope-). Mějte ale na paměti, že /Zc:forScope- se může odebrat v budoucí verzi Visual C++, takže nakonec bude potřeba změnit kód tak, aby odpovídal standardu.

Tyto problémy se dají poměrně snadno opravit, ale v závislosti na kódu můžou mít vliv na spoustu kódu. Tady je typický problém.

int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
  for (int n = 0; mszStrings[0] != 0; n++)
  mszStrings = _tcschr(mszStrings, 0) + 1;
  return(n);
}

Výše uvedený kód způsobí chybu:

'n': undeclared identifier

K tomu dochází, protože kompilátor zastaralá možnost kompilátoru, která povolila kód, který již nevyhovuje standardu C++. Ve standardu deklarování proměnné uvnitř smyčky omezuje jeho rozsah pouze na smyčku, takže běžný postup použití čítače smyčky mimo smyčku vyžaduje, aby deklarace čítače byla přesunuta i mimo smyčku, jak je uvedeno v následujícím revidovaném kódu:

int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
  int n;
  for (n = 0; mszStrings[0] != 0; n++)
  mszStrings = _tcschr(mszStrings, 0) + 1;
  return(n);
}

Shrnutí

Přenos nástroje Spy++ z původního kódu Visual C++ 6.0 do nejnovějšího kompilátoru trvalo přibližně 20 hodin kódování v průběhu týdne. Upgradovali jsme přímo prostřednictvím osmi verzí produktu ze sady Visual Studio 6.0 na Visual Studio 2015. Tento postup se teď doporučuje pro všechny upgrady u velkých a malých projektů.

Viz také

Přenos a upgrade: Příklady a případové studie
Předchozí případová studie: COM Spy