Aracılığıyla paylaş


Taşıma Kılavuzu: Spy++

Bu taşıma örneği çalışması tipik bir taşıma projesinin nasıl olduğu, karşılaşabileceğiniz sorun türleri ve taşıma sorunlarını gidermeye yönelik bazı genel ipuçları ve püf noktaları hakkında size bir fikir vermek üzere tasarlanmıştır. Bir projeyi taşıma deneyimi kodun özelliklerine çok bağlı olduğundan, taşıma için kesin bir kılavuz olması amaçlanmamıştır.

Spy++

Spy++, Windows masaüstünde kullanıcı arabirimi öğeleri hakkında her türlü bilgiyi sağlayan, Windows masaüstü için yaygın olarak kullanılan bir GUI tanılama aracıdır. Tam pencere hiyerarşisini gösterir ve her pencere ve denetimle ilgili meta verilere erişim sağlar. Bu kullanışlı uygulama, Visual Studio ile birlikte uzun yıllar boyunca gönderildi. En son Visual C++ 6.0'da derlenmiş ve Visual Studio 2015'e taşımaya yönelik eski bir sürümünü bulduk. Visual Studio 2017 veya Visual Studio 2019 deneyimi neredeyse aynı olmalıdır.

Özellikle Visual C++ 6.0 sürümünden bu yana Visual C++'ın her sürümüyle güncelleştirilmemiş eski projeler için MFC ve Win32 API kullanan Windows masaüstü uygulamalarını taşımada bu durumun tipik olduğunu değerlendirdik.

1. Adım. Proje dosyasını dönüştürme

Visual C++ 6.0'dan iki eski .dsw dosyası olan proje dosyası, daha fazla dikkat gerektiren bir sorun olmadan kolayca dönüştürüldü. Bir proje Spy++ uygulamasıdır. Diğeri, destekleyen bir DLL olan C ile yazılmış SpyHk'tir. Burada açıklandığı gibi daha karmaşık projeler kolayca yükseltilmeyebilir.

İki proje yükseltildikten sonra çözümümüz şöyle görünüyordu:

Spy plus plus Solution'ın ekran görüntüsü.

Biri çok sayıda C++ dosyası, diğeri de C ile yazılmış bir DLL olmak üzere iki projemiz var.

2. Adım. Üst bilgi dosyası sorunları

Yeni dönüştürülen bir proje derledikten sonra, sık sık bulacağınız ilk şeylerden biri, projenizin kullandığı üst bilgi dosyalarının bulunmamasıdır.

Spy++ içinde bulunamayan dosyalardan biri verstamp.h'ydi. Bir İnternet aramasından, bunun eski bir veri teknolojisi olan DAO SDK'sından geldiğini belirledik. Bu üst bilgi dosyasından hangi simgelerin kullanıldığını öğrenmek, bu dosyanın gerçekten gerekli olup olmadığını veya bu simgelerin başka bir yerde tanımlanıp tanımlanmadığını görmek istedik, bu nedenle üst bilgi dosyası bildirimini açıklama satırı yaptık ve yeniden derledik. Tek bir simgenin gerekli olduğu ortaya çıktı, 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

Kullanılabilir dosyalarda simge bulmanın en kolay yolu, Dosyalardave Visual C++ Ekleme Dizinleri belirtmektir. Ntverp.h'de bulduk. verstamp.h include değerini ntverp.h ile değiştirdik ve bu hata kayboldu.

3. Adım. Bağlayıcı ÇıkışDosyası ayarı

Eski projelerde bazen, yükseltmeden sonra sorunlara neden olabilecek sıra dışı konumlara yerleştirilmiş dosyalar bulunur. Bu durumda, Visual Studio'nun $(SolutionDir) yolunu eklemeliyiz.

MSBuild, Link.OutputFile özelliğinin TargetPath ve TargetName değerleriyle eşleşmediğinden şikayet eder ve MSB8012 verir.

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 derleme çıkışıdır (örneğin, EXE, DLL) ve normalde yolundan $(TargetDir)$(TargetName)$(TargetExt)oluşturulur ve yolu, dosya adını ve uzantıyı verir. Bu, projeleri eski Visual C++ derleme aracından (vcbuild.exe) yeni derleme aracına (MSBuild.exe) geçirirken sık karşılaşılan bir hatadır. Derleme aracı değişikliği Visual Studio 2010'da gerçekleştiğinden, 2010 öncesi bir projeyi 2010 veya sonraki bir sürüme her geçirdiğinizde bu sorunla karşılaşabilirsiniz. Temel sorun, değerinin diğer proje ayarlarına göre belirlenmesi her zaman mümkün olmadığından, proje geçiş sihirbazının Link.OutputFile değerini güncelleştirmemesidir. Bu nedenle, genellikle el ile ayarlamanız gerekir. Diğer ayrıntılar için Visual C++ blogundaki bu gönderiye bakın.

Bu durumda, dönüştürülen projedeki Link.OutputFile özelliği, yapılandırmaya bağlı olarak Spy++ projesi için .\Debug\Spyxx.exe ve .\Release\Spyxx.exe olarak ayarlanmıştır. En iyi sonuç, bu sabit kodlanmış değerleri Tüm Yapılandırmalar için ile $(TargetDir)$(TargetName)$(TargetExt) değiştirmektir. Bu işe yaramazsa, buradan özelleştirebilir veya bu değerlerin ayarlandığı Genel bölümünde özellikleri değiştirebilirsiniz (özellikler Çıkış Dizini, Hedef Adı ve Hedef Uzantıdır). Görüntülediğiniz özellik makro kullanıyorsa, makro değiştirmelerinin yapıldığı son dizeyi gösteren bir iletişim kutusu açmak için açılan listede Düzenle'yi seçebileceğinizi unutmayın. Makrolar düğmesini seçerek tüm kullanılabilir makroları ve bunların geçerli değerlerini görüntüleyebilirsiniz.

4. Adım. Hedef Windows Sürümünü Güncelleştirme

Sonraki hata, WINVER sürümünün artık MFC'de desteklenmediğini gösterir. Windows XP için WINVER 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 artık Microsoft tarafından desteklenmemektedir, bu nedenle Visual Studio'da hedeflemeye izin verilse de, uygulamalarınızda bu desteği kullanıma almalı ve kullanıcılarınızı yeni Windows sürümlerini benimsemeye teşvik etmelisiniz.

Hatadan kurtulmak için, Proje Özellikleri ayarını şu anda hedeflemek istediğiniz En düşük Windows sürümüne güncelleştirerek WINVER'i tanımlayın. Çeşitli Windows sürümleri için değerler tablosunu burada bulabilirsiniz.

stdafx.h dosyası bu makro tanımlarından bazılarını içeriyordu.

#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'i Windows 7 olarak ayarlayacağız. Daha sonra windows 7 (_WIN32_WINNT_WIN7) için makroyu kullanırsanız, değerin kendisi (0x0601) yerine kodu okumak daha kolaydır.

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

Adım 5. Bağlayıcı Hataları

Bu değişikliklerle, SpyHk (DLL) projesi derlenir ancak bağlayıcı hatası oluşturur.

LINK : warning LNK4216: Exported entry point _DLLEntryPoint@12

DLL'nin giriş noktası dışarı aktarılmamalıdır. Giriş noktası yalnızca DLL belleğe ilk yüklendiğinde yükleyici tarafından çağrılmaya yöneliktir, bu nedenle diğer arayanlar için olan dışarı aktarma tablosunda olmamalıdır. Yalnızca yönergesinin __declspec(dllexport) eklenmediğinden emin olmamız gerekir. spyxxhk.c'de, bildirimi ve tanımı DLLEntryPointolmak üzere iki yerden kaldırmamız gerekir. Bu yönergeyi kullanmak hiçbir zaman mantıklı olmadı, ancak bağlayıcının ve derleyicinin önceki sürümleri sorun olarak işaretlemedi. Bağlayıcının daha yeni sürümleri bir uyarı verir.

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

SpyHK.dll C DLL projesi artık hatasız bir şekilde derlenip bağlantı oluşturuyor.

6. Adım. Diğer güncel olmayan üst bilgi dosyaları

Bu noktada ana yürütülebilir proje olan Spyxx üzerinde çalışmaya başlıyoruz.

Diğer birkaç ekleme dosyası bulunamadı: ctl3d.h ve penwin.h. Üst bilgiyi neyin içerdiğini belirlemeye çalışmak için İnternet'de arama yapmak yararlı olsa da, bazen bilgiler o kadar yararlı olmaz. ctl3d.h dosyasının Exchange Geliştirme Seti'nin bir parçası olduğunu ve Windows 95'te belirli bir denetim stili için destek sağladığını ve penwin.h'nin eski bir API olan Window Pen Computing ile ilgili olduğunu öğrendik. Bu durumda, yalnızca satırı yorumlayarak #include verstamp.h ile yaptığımız gibi tanımlanmamış sembollerle ilgileniriz. 3B Denetimler veya Kalem Bilgi İşlem ile ilgili her şey projeden kaldırıldı.

Aşamalı olarak ortadan kaldırdığınız birçok derleme hatasına sahip bir proje göz önünde bulundurulduğunda, yönergeyi kaldırdığınızda #include güncel olmayan BIR API'nin tüm kullanımlarını hemen bulmak gerçekçi değildir. Bunu hemen algılamadık, ancak daha sonraki bir noktada WM_DLGBORDER tanımsız olduğunu belirten bir hatayla karşılaştık. Aslında ctl3d.h'den gelen bir çok tanımsız simgeden yalnızca biridir. Eski bir API ile ilişkili olduğunu belirledikten sonra, koddaki tüm başvuruları kaldırdık.

7. Adım. Eski iostreams kodunu güncelleştirme

Sonraki hata, iostreams kullanan eski C++ kodunda yaygındır.

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

Sorun, eski iostreams kitaplığının kaldırılıp değiştirilmesidir. Eski iostream'leri daha yeni standartlarla değiştirmeliyiz.

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

Güncelleştirilenler şunlardır:

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

Bu değişiklikle, artık kullanılmayan ile ostrstreamilgili sorunlarımız var. Uygun değişim ostringstream'dir. Kodun en azından başlangıç olarak çok fazla değiştirilmesini önlemek için typedef eklemeye ostrstream çalışıyoruz.

typedef std::basic_ostringstream<TCHAR> ostrstream;

Proje şu anda MBCS (Çok Baytlı Karakter Kümesi) kullanılarak oluşturulmuş olup, uygun karakter veri türü de bu şekildedir char . Ancak, koduN UTF-16 Unicode'a daha kolay güncelleştirilebilmesini TCHARsağlamak için bunu olarak güncelleştiriyoruz. Bu, proje ayarlarındaki Karakter KümesicharMBCS veya wchar_t Unicode olarak ayarlanıp ayarlanmadığına bağlı olarak veya olarak çözümleniyor.

Birkaç kod parçasının daha güncelleştirilmiş olması gerekir. temel sınıfını ios ile ios_basedeğiştirdik ve ostream is değerini basic_ostream<T> ile değiştirdik. İki ek tür tanımı ekliyoruz ve bu bölüm derliyor.

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

Bu tür tanımlarını kullanmak yalnızca geçici bir çözümdür. Daha kalıcı bir çözüm için, yeniden adlandırılan veya eski API'ye yapılan her başvuruyu güncelleştirebiliriz.

Sonraki hata aşağıdadır.

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

Bir sonraki sorun, bir basic_stringbuf yöntemi olmamasıdırfreeze. freeze yöntemi, eski ostreamiçinde bellek sızıntısını önlemek için kullanılır. Yeni ostringstream'yi kullandığımıza göre artık buna ihtiyacımız yok. çağrısı freezesilinebilir.

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

Sonraki iki hata bitişik satırlarda oluştu. İlki, eski ends kitaplığın bir dizeye null sonlandırıcı ekleyen GÇ manipülatörünü kullanmakla iostreamilgili şikayet eder. Bu hataların ikincisinde, yöntemin çıkışının str sabit olmayan bir işaretçiye atanamadığı açıklanmaktadır.

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

Dize her zaman null olarak sonlandırıldığından, yeni akış kitaplığını ends kullanmak gerekli değildir, böylece satır kaldırılabilir. İkinci sorun, şimdi str() bir dize için karakter dizisine bir işaretçi döndürmemesidir; türü döndürür std::string . İkincinin çözümü, türü LPCSTR olarak değiştirmek ve işaretçiyi istemek için yöntemini kullanmaktır c_str() .

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

Bu kodda bir süre bizi şaşırtan bir hata oluştu.

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

Makro MOUT türündeki bir nesneye *g_pmoutçözümleniyormstream. mstream sınıfı standart çıkış dizesi sınıfından std::basic_ostream<TCHAR>türetilir. Ancak Unicode'a dönüştürme hazırlığı yaptığımız dize değişmez değerinin etrafında _T olduğunda, işleç << için aşırı yükleme çözümlemesi aşağıdaki hata iletisiyle başarısız olur:

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

Bu tür bir hatanın göz korkutucu olabileceği kadar çok işleç << tanımı vardır. Kullanılabilir aşırı yüklemelere daha yakından baktıktan sonra, çoğunun ilgisiz olduğunu görebiliriz ve sınıf tanımına mstream daha yakından baktıktan sonra, bu durumda çağrılması gerektiğini düşündüğümüz aşağıdaki işlevi belirledik.

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

Çağrılmama nedeni, dize değişmez değerinin bu uzun hata iletisinin son satırında görebileceğiniz türe const wchar_t[10] sahip olmasıdır, bu nedenle sabit olmayan bir işaretçiye dönüştürme otomatik değildir. Ancak bu işleç giriş parametresini değiştirmemelidir, bu nedenle daha uygun parametre türü (LPCTSTRconst char*MBCS olarak derlenirken ve const wchar_t* Unicode olarak), değil LPTSTR (char*MBCS olarak derlenirken ve wchar_t* Unicode olarak). Bu değişikliğin yapılması bu hatayı düzeltir.

Bu tür dönüştürmeye eski, daha az katı derleyici altında izin veriliyordu, ancak daha yeni uyumluluk değişiklikleri daha doğru kod gerektiriyordu.

8. Adım: Derleyicinin daha katı dönüştürmeleri

Ayrıca aşağıdaki gibi birçok hatayla da karşımıza çıkar:

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

Hata, yalnızca bir makro olan bir ileti eşlemesinde oluşur:

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

Bu makronun tanımına gittiğimizde işlevine OnNcHitTestbaşvuracağını görüyoruz.

#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)) },

Sorunun, üye işlev türlerine yönelik işaretçideki uyuşmazlık ile ilgili olması gerekir. Bu geçerli bir türetilmiş tabana dönüştürme olduğundan, sorun sınıf türü CHotLinkCtrl olarak 'den CWnd sınıf türü olarak'a dönüştürme değildir. Sorun dönüş türüdür: UINT ve LRESULT. LRESULT, hedef ikili türüne bağlı olarak 64 bit işaretçi veya 32 bit işaretçi olan LONG_PTR çözümlendiğinden UINT bu türe dönüştürülmüyor. Birçok ileti eşleme yönteminin dönüş türü, 64 bit uyumluluk değişikliklerinin bir parçası olarak Visual Studio 2005'te UINT'den LRESULT'a değiştirildiğinden, 2005'ten önce yazılmış kod yükseltilirken bu durum sık karşılaşılan bir durum değildir. Aşağıdaki koddaki UINT dönüş türünü LRESULT olarak değiştiriyoruz:

afx_msg UINT OnNcHitTest(CPoint point);

Değişiklik sonrasında aşağıdaki koda sahibiz:

afx_msg LRESULT OnNcHitTest(CPoint point);

Bu işlevin tümü CWnd'den türetilen farklı sınıflarda yaklaşık on kez oluştuğundan, imleç düzenleyicideki işlev üzerindeyken Bunları bulmak ve Sembol Bul araç penceresinden bunlara gitmek için Tanıma Git (Klavye: F12) ve Bildirime Git (Klavye: +) işlevlerini kullanmak yararlı olur. Tanım'a gidin, genellikle ikisinin daha kullanışlı olmasıdır. Bildirim'e gidin, arkadaş sınıfı bildirimleri veya iletme başvuruları gibi tanımlama sınıfı bildirimi dışındaki bildirimleri bulur.

9. Adım: MFC Değişiklikleri

Sonraki hata, değiştirilen bildirim türüyle de ilgilidir ve bir makroda da oluşur.

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

Sorun, ikinci parametresinin CWnd::OnActivateApp HTASK'den DWORD'ye değiştirilmesidir. Bu değişiklik Visual Studio, Visual Studio .NET'in 2002 sürümünde meydana geldi.

afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);

Türetilmiş sınıflarda OnActivateApp bildirimlerini aşağıdaki gibi güncelleştirmemiz gerekir:

afx_msg void OnActivateApp(BOOL bActive, DWORD dwThreadId);

Bu noktada projeyi derleyebiliyoruz. Bununla birlikte, üzerinden geçilmesi gereken birkaç uyarı vardır ve yükseltmenin MBCS'den Unicode'a dönüştürme veya Secure CRT işlevlerini kullanarak güvenliği geliştirme gibi isteğe bağlı bölümleri vardır.

10. Adım Derleyici uyarılarını ele alma

Uyarıların tam listesini almak için, yalnızca geçerli derlemeden uyarı raporları aldığınızdan, önceden derlenmiş olan her şeyin yeniden derlendiğinden emin olmak için, normal bir derleme yerine çözümde Tümünü Yeniden Derle işlemi yapmalısınız. Diğer soru ise geçerli uyarı düzeyini kabul etmek mi yoksa daha yüksek bir uyarı düzeyi kullanmak mı olduğudur. Özellikle eski kod olmak üzere çok fazla kod taşırken, daha yüksek bir uyarı düzeyi kullanmak uygun olabilir. Ayrıca varsayılan uyarı düzeyiyle başlayıp tüm uyarıları almak için uyarı düzeyini artırmak isteyebilirsiniz. kullanıyorsanız /Wall, sistem üst bilgi dosyalarında bazı uyarılar alırsınız, bu nedenle birçok kişi sistem üst bilgileri için uyarı almadan kodunda en çok uyarı almak için kullanır /W4 . Uyarıların hata olarak gösterilmesini istiyorsanız seçeneğini ekleyin /WX . Bu ayarlar, Proje Özellikleri iletişim kutusunun C/C++ bölümünde bulunur.

sınıfındaki CSpyApp yöntemlerden biri, artık desteklenmeyen bir işlev hakkında uyarı üretir.

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

Uyarı aşağıdaki gibidir.

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

İleti WM_CTLCOLORDLG Spy++ kodunda zaten işlendi, bu nedenle gereken tek değişiklik artık gerekli olmayan öğesine yapılan başvuruları SetDialogBkColorsilmekti.

Bir sonraki uyarı, değişken adını açıklama satırı yaparak kolayca düzeltilebilir. Aşağıdaki uyarıyı aldık:

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

Bunu üreten kod bir makro içerir.

DECODEPARM(CB_GETLBTEXT)
{
  P2WPOUT();

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

    INDENT();

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

Bu kodda olduğu gibi makroların yoğun kullanımı, kodun bakımını zorlaştırma eğilimindedir. Bu durumda, makrolar değişkenlerin bildirimlerini içerir. MAKRO PARM'i aşağıdaki gibi tanımlanır:

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

Bu nedenle değişken aynı lpszBuffer işlevde iki kez bildirılır. Kodun makro kullanmaması (ikinci tür bildirimini kaldırmanız yeterlidir) gibi bunu düzeltmek o kadar kolay değildir. Bu nedenle, makro kodunu sıradan bir kod olarak yeniden yazmaya (yorucu ve muhtemelen hataya açık bir görev) veya uyarıyı devre dışı bırakmaya karar vermek zorunda kalmak gibi talihsiz bir seçimimiz var.

Bu durumda uyarıyı devre dışı bırakacağız. Bunu aşağıdaki gibi bir pragma ekleyerek yapabiliriz:

#pragma warning(disable : 4456)

Bir uyarıyı devre dışı bırakırken, yararlı bilgiler sağlayabilecek uyarıyı gizlememek için devre dışı bırakma efektini yalnızca uyarıyı üreten kodla kısıtlamak isteyebilirsiniz. Uyarıyı üreten satırın hemen arkasına geri yüklemek için kod ekliyoruz veya daha iyisi, bu uyarı bir makroda oluştuğundan, makrolarda çalışan __pragma anahtar sözcüğünü kullanın (#pragmamakrolarda çalışmaz).

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

Sonraki uyarı için bazı kod düzeltmeleri gerekir. Win32 API GetVersion (ve GetVersionEx) kullanım dışıdır.

warning C4996: 'GetVersion': was declared deprecated

Aşağıdaki kod, sürümün nasıl edinilmiş olduğunu gösterir.

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

Bunun ardından, Windows 95'te çalıştırılıp çalıştırılmayacağımızı ve Windows NT'nin hangi sürümünü çalıştıracağımızı belirlemek için dwWindowsVersion değerini inceleyen çok sayıda kod bulunur. Bunların hepsi eski olduğundan kodu kaldırır ve bu değişkenlere yapılan tüm başvurularla ilgileniriz.

Windows 8.1 ve Windows Server 2012 R2'de işletim sistemi sürümü değişiklikleri makalesinde durum açıklanmaktadır.

sınıfında işletim sistemi sürümünü sorgulayan yöntemler CSpyApp vardır: IsWindows9x, IsWindows4xve IsWindows5x. İyi bir başlangıç noktası, desteklemek istediğimiz Windows sürümlerinin (Windows 7 ve üzeri) bu eski uygulama tarafından kullanılan teknolojiler açısından Windows NT 5'e yakın olduğunu varsaymaktır. Bu yöntemlerin kullanım alanları, eski işletim sistemlerinin sınırlamalarıyla ilgilenmekti. Bu nedenle bu yöntemleri, diğerleri için IsWindows5x TRUE ve FALSE döndürecek şekilde değiştirdik.

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

Bu, iç değişkenlerin doğrudan kullanıldığı yalnızca birkaç yer bıraktı. Bu değişkenleri kaldırdığımız için açıkça ele alınması gereken birkaç hatayla karşımıza çıkar.

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

Bunu bir yöntem çağrısıyla değiştirebilir veya YALNıZCA TRUE geçirip Windows 9x için eski özel durumu kaldırabiliriz.

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

Varsayılan düzeydeki (3) son uyarının bit alanıyla ilgisi vardır.

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

Bunu tetikleyen kod aşağıdaki gibidir.

m_bStdMouse = TRUE;

bildirimi m_bStdMouse bunun bir bit alanı olduğunu gösterir.

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;

Bu kod, Visual C++'da yerleşik bool türü desteklenmeden önce yazılmıştır. Bu kodda BOOL, için typedefbir int idi. Tür int bir signed türdür ve a'nın signed int bit gösterimi ilk biti işaret biti olarak kullanmaktır, bu nedenle int türünün bit alanı 0 veya -1'i temsil ediyor olarak yorumlanabilir; büyük olasılıkla amaçlanan değildir.

Bunların neden bitalanları olduğunu koda bakarak bilemezsiniz. Amaç nesnenin boyutunu küçük tutmak mıydı yoksa nesnenin ikili düzeninin kullanıldığı herhangi bir yer var mı? Bitfield kullanımı için herhangi bir neden görmediğimiz için bunları sıradan BOOL üyeleriyle değiştirdik. Bir nesnenin boyutunu küçük tutmak için bit alanları kullanmanın çalışması garanti değildir. Bu, derleyicinin türü nasıl yerleştirdiğinden bağlıdır.

Genel olarak standart türü bool kullanmanın yararlı olup olmadığını merak edebilirsiniz. BOOL türü gibi eski kod desenlerinin çoğu, daha sonra standart C++ ile çözülen sorunları çözmek için icat edilmiştir, bu nedenle BOOL'den yerleşik türe bool geçmek, kodunuzu başlangıçta yeni sürümde çalıştırdıktan sonra yapmayı düşündüğünüz bu tür bir değişikliğin yalnızca bir örneğidir.

Varsayılan düzeyde (düzey 3) görünen tüm uyarılarla ilgilendikten sonra birkaç ek uyarı yakalamak için düzey 4 olarak değiştirdik. İlk görüntülenen aşağıdaki gibidir:

warning C4100: 'nTab': unreferenced formal parameter

Bu uyarıyı oluşturan kod aşağıdaki gibidir.

virtual void OnSelectTab(int nTab) {};

Bu yeterince zararsız görünüyor, ancak ile /W4 temiz bir derleme ve /WX ayarlamak istediğimiz için, değişken adını yorumladık ve okunabilirlik adına bıraktık.

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

Aldığımız diğer uyarılar genel kod temizleme için yararlı oldu. veya WORD'den intunsigned int (için unsigned shortbir tür tanımıdır) örtük dönüştürme sayısı vardır. Bunlar olası bir veri kaybı içerir. Bu durumlarda WORD'e bir yayın ekledik.

Bu kod için aldığımız bir diğer düzey 4 uyarısı:

warning C4211: nonstandard extension used: redefined extern to static

Sorun, bir değişken ilk olarak bildirildiğinde externve daha sonra bildirildiğinde staticoluşur. Bu iki depolama sınıfı tanımlayıcısının anlamı birbirini dışlar, ancak microsoft uzantısı olarak buna izin verilir. Kodun diğer derleyicilere taşınabilir olmasını istiyorsanız veya (ANSI uyumluluğu) ile /Za derlemek istiyorsanız, bildirimleri eşleşen depolama sınıfı tanımlayıcılarına sahip olacak şekilde değiştirirsiniz.

11. Adım MBCS'den Unicode'a taşıma

Windows dünyasında Unicode dediğimizde genellikle UTF-16 anlamına geldiğini unutmayın. Linux gibi diğer işletim sistemleri UTF-8 kullanır, ancak Windows genellikle kullanmaz. MFC'nin MBCS sürümü Visual Studio 2013 ve 2015'te kullanım dışı bırakılmıştır ancak artık Visual Studio 2017'de kullanım dışı bırakılmıştır. Visual Studio 2013 veya 2015 kullanıyorsanız, MBCS kodunu UTF-16 Unicode'a taşıma adımını gerçekleştirmeden önce, başka bir iş yapmak veya taşıma işlemini uygun bir zamana kadar ertelemek için MBCS'nin kullanım dışı olduğuna ilişkin uyarıları geçici olarak ortadan kaldırmak isteyebiliriz. Geçerli kod MBCS kullanır ve devam etmek için MFC'nin ANSI/MBCS sürümünü yüklememiz gerekir. Oldukça büyük MFC kitaplığı, C++ yüklemesiyle varsayılan Visual Studio Desktop geliştirmesinin bir parçası olmadığından, yükleyicideki isteğe bağlı bileşenlerden seçilmelidir. Bkz. MFC MBCS DLL Eklentisi. Bunu indirip Visual Studio'yu yeniden başlattıktan sonra MFC'nin MBCS sürümüyle derleyip bağlantı oluşturabilirsiniz, ancak Visual Studio 2013 veya 2015 kullanıyorsanız MBCS ile ilgili uyarılardan kurtulmak için, proje özelliklerinin Önişlemci bölümünde önceden tanımlanmış makrolar listenize NO_WARN_MBCS_MFC_DEPRECATION eklemeniz gerekir. veya stdafx.h üst bilgi dosyanızın veya diğer ortak üst bilgi dosyanızın başında.

Şimdi bazı bağlayıcı hatalarımız var.

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

LNK1181, bağlayıcı girişinde eski bir mfc statik kitaplık sürümü bulunduğundan oluşur. MFC'yi dinamik olarak bağlayabildiğimizden bu artık gerekli değildir, bu nedenle proje özelliklerinin Bağlayıcı bölümündeki Input özelliğinden tüm MFC statik kitaplıklarını kaldırmamız yeterlidir. Bu proje aynı zamanda seçeneğini de kullanıyor /NODEFAULTLIB ve bunun yerine tüm kitaplık bağımlılıklarını listeler.

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

Şimdi eski Çok Baytlı Karakter Kümesi (MBCS) kodunu Unicode olarak güncelleştirelim. Bu, Windows masaüstü platformuna yakından bağlı bir Windows uygulaması olduğundan, bunu Windows'un kullandığı UTF-16 Unicode'a taşırız. Platformlar arası kod yazıyorsanız veya bir Windows uygulamasını başka bir platforma taşırken, diğer işletim sistemlerinde yaygın olarak kullanılan UTF-8'e taşımayı düşünebilirsiniz.

UTF-16 Unicode'a taşıma, hala MBCS'ye derleme seçeneği isteyip istemediğimiz konusunda karar vermemiz gerekir. MBCS'yi destekleme seçeneğine sahip olmak istiyorsak, derleme sırasında _MBCS veya _UNICODE tanımlanıp tanımlanmadığına charwchar_tbağlı olarak veya olarak çözümlenen karakter türü olarak TCHAR makrosunu kullanmalıyız. ve ilişkili API'leri yerine wchar_t TCHAR ve çeşitli API'lerin TCHAR sürümlerine geçiş yapmak, yalnızca _UNICODE yerine _MBCS makro tanımlayarak kodunuzun MBCS sürümüne geri dönebileceğiniz anlamına gelir. TCHAR'ya ek olarak, yaygın olarak kullanılan tür tanımları, makrolar ve işlevler gibi çeşitli TCHAR sürümleri de mevcuttur. Örneğin, LPCSTR yerine LPCTSTR vb. Proje özellikleri iletişim kutusundaki Yapılandırma Özellikleri'nin altındaki Genel bölümünde, Karakter Kümesi özelliğini MBCS Karakter Kümesi Kullan'dan Unicode Karakter Kümesi Kullan olarak değiştirin. Bu ayar, derleme sırasında hangi makroların önceden tanımlanmış olduğunu etkiler. Hem UNICODE makro hem de _UNICODE makro vardır. Proje özelliği her ikisini de tutarlı bir şekilde etkiler. Windows üst bilgileri, MFC gibi Visual C++ üst bilgilerinin _UNICODE kullandığı ancak biri tanımlandığında diğerinin her zaman tanımlandığı UNICODE kullanır.

TCHAR kullanarak MBCS'den UTF-16 Unicode'a taşımaya yönelik iyi bir kılavuz vardır. Bu yolu seçiyoruz. İlk olarak, Karakter Kümesi özelliğini Unicode Karakter Kümesi Kullan olarak değiştirip projeyi yeniden derleyeceğiz.

Koddaki bazı yerlerde zaten TCHAR kullanılıyordu ve sonunda Unicode'un desteklenmesi beklendi. Bazıları değildi. için bir typedefcharolan CHAR örneklerini aradık ve bunların çoğunu TCHAR ile değiştirdik. Ayrıca, aradık sizeof(CHAR). CHAR'dan TCHAR'ye her değiştirdiğimizde, bu genellikle bir dizedeki karakter sayısını belirlemek için kullanıldığından olarak değiştirmek sizeof(TCHAR) zorunda kaldık. Burada yanlış türün kullanılması derleyici hatası oluşturmaz, bu nedenle bu olaya biraz dikkat etmeye değer.

Bu tür bir hata, Unicode'a geçtikten hemen sonra çok yaygındır.

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

Bunu üreten bir kod örneği aşağıda verilmişti:

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

Hatayı kaldırmak için dize değişmez değerinin çevresine _T koyduk.

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

_T makro, MBCS veya UNICODE ayarına bağlı olarak dize değişmez değeri derlemesini dize char veya dize olarak wchar_t yapma etkisine sahiptir. Visual Studio'da tüm dizeleri _T değiştirmek için, önce Hızlı Değiştir (Klavye: ) kutusunu veya Dosyalarda Değiştir (Klavye: +) kutusunu açın, ardından Normal İfadeleri Kullan onay kutusunu seçin. Arama metni olarak ve ((\".*?\")|('.+?')) yerine geçen metin olarak girin_T($1). Bazı dizelerin çevresinde zaten _T makronuz varsa, bu yordam makroyu yeniden ekler ve kullanırken olduğu gibi #include_T istemediğiniz durumlar da bulabilir. Bu nedenle, Tümünü Değiştir yerine Sonrakini Değiştir'i kullanmak en iyisidir.

Bu özel işlev olan wsprintf aslında Windows üst bilgilerinde tanımlanmıştır ve belgelerde olası arabellek taşması nedeniyle kullanılmaması önerilir. Arabellek için szTmp boyut verilmediğinden, işlevin arabelleğin ona yazılacak tüm verileri tutabileceğini denetlemesinin bir yolu yoktur. Diğer benzer sorunları giderdiğimiz Güvenli CRT'ye taşıma hakkında sonraki bölüme bakın. Bunu _stprintf_s ile değiştirdik.

Unicode'a dönüştürürken göreceğiniz bir diğer yaygın hata da bu hatadır.

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

Bunu üreten kod aşağıdaki gibidir:

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

Bir dizeyi _tcscpy kopyalamak için TCHAR strcpy işlevi olan işlev kullanılmış olsa da, ayrılan arabellek bir char arabellekti. Bu kolayca TCHAR olarak değiştirilir.

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

Benzer şekilde, bir derleyici hatası tarafından garanti edildiğinde SıRAsıyla LPSTR (LONG Pointer to STRing) ve LPCSTR (Long Pointer to Constant STRing) ile LPTSTR (Long Pointer to TCHAR STRing) ve LPCTSTR (Long Pointer to Constant TCHAR STRing) değerlerini değiştirdik. Her durumun ayrı ayrı incelenmesi gerektiğinden, genel arama ve değiştirme kullanarak bu tür değiştirmeler yapmamayı seçtik. Bazı durumlarda, A soneki char olan Windows yapılarını kullanan belirli Windows iletilerini işlerken olduğu gibi sürüm istendi. Windows API'sinde, A soneki ASCII veya ANSI anlamına gelir (ve MBCS için de geçerlidir) ve W soneki geniş karakterler veya UTF-16 Unicode anlamına gelir. Bu adlandırma düzeni Windows üst bilgilerinde kullanılır, ancak yalnızca MBCS sürümünde zaten tanımlanmış bir işlevin Unicode sürümünü eklememiz gerektiğinde Spy++ kodunda da bunu takip ettik.

Bazı durumlarda, doğru çözümlenen bir sürümü kullanmak için bir türü değiştirmemiz gerekiyordu (örneğin, WNDCLASSA yerine WNDCLASS).

Çoğu durumda gibi GetClassName bir Win32 API'sinin (yerine GetClassNameA) genel sürümünü (makro) kullanmak zorunda kaldık. İleti işleyicisi anahtar deyiminde, bazı iletiler MBCS veya Unicode'a özgüdür; bu durumlarda, genel olarak adlandırılmış işlevleri A ve W'ye özgü işlevlerle değiştirdiğimiz ve UNICODE'un tanımlanıp tanımlanmadığına bağlı olarak doğru A veya W adına çözümlenen genel ad için bir makro eklediğimiz için kodu açıkça MBCS sürümünü çağıracak şekilde değiştirmemiz gerekiyordu. Kodun birçok bölümünde, _UNICODE tanımlamaya geçiş yaptığımızda, artık A sürümü istenen sürüm olsa bile W sürümü seçilir.

Özel eylemlerin yapılması gereken birkaç yer vardır. veya WideCharToMultiByte kullanımı MultiByteToWideChar daha yakından bakmanızı gerektirebilir. Burada kullanılan örneklerden biri WideCharToMultiByte verilmişti.

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

Bunu ele almak için, bunun yapılmasının nedeninin, bir yazı tipinin adını temsil eden geniş bir karakter dizesini iç CStringarabelleğe strFacekopyalamak olduğunu anlamamız gerekiyordu. Bu, çok baytlı CString dizeler için geniş karakter CString dizelerinde olduğu gibi biraz farklı bir kod gerektirdiği için bu durumda bir #ifdef ekledik.

#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

Tabii ki, gerçekten kullanmak yerine wcscpy daha güvenli bir sürüm kullanmalıyız wcscpy_s. Sonraki bölüm bunu ele alır.

Çalışmamızda bir denetim olarak Karakter Kümesi'ni Çok Baytlı Karakter Kümesi Kullan olarak sıfırlamalı ve kodun MBCS ve Unicode kullanarak derlendiğinden emin olmamız gerekir. Tüm bu değişikliklerden sonra yeniden derlenen uygulamada tam test geçişinin yürütülmesi gerektiğini söylemeye gerek yok.

Bu Spy++ çözümüyle yaptığımız çalışmada, ortalama bir C++ geliştiricisinin kodu Unicode'a dönüştürmesi yaklaşık iki iş günü sürdü. Bu, yeniden test süresini içermiyor.

12. Adım: Güvenli CRT'yi kullanmak için taşıma

CRT işlevlerinin güvenli sürümlerini (_s soneki olan sürümler) kullanmak için kodu taşıma işlemi sıradadır. Bu durumda, genel strateji işlevi _s sürümüyle değiştirmek ve ardından genellikle gerekli ek arabellek boyutu parametrelerini eklemektir. Çoğu durumda, boyut bilindiğinden bu basittir. Boyutun hemen kullanılamadığı diğer durumlarda, CRT işlevini kullanan işleve ek parametreler eklemek veya hedef arabelleğin kullanımını incelemek ve uygun boyut sınırlarının ne olduğunu görmek gerekir.

Visual C++, çok sayıda boyut parametresi eklemeden ve şablon aşırı yüklemelerini kullanarak kodun güvenliğini sağlamayı kolaylaştıran bir püf noktası sağlar. Bu aşırı yüklemeler şablon olduğundan, yalnızca C++ olarak derlenirken kullanılabilir, C olarak değil. Spyxxhk bir C projesidir, bu nedenle püf noktası bu işe yaramaz. Ancak Spyxx değildir ve bu hileyi kullanabiliriz. İşin püf noktası, stdafx.h gibi projenin her dosyasında derleneceği bir yere bunun gibi bir satır eklemektir:

#define _CRT_SECURE_TEMPLATE_OVERLOADS 1

Bunu tanımladığınızda, arabellek bir ham işaretçi yerine bir dizi olduğunda, boyutu dizi türünden çıkarılır ve bunu sağlamak zorunda kalmadan boyut parametresi olarak kullanılır. Bu, kodu yeniden yazmanın karmaşıklığını azaltmaya yardımcı olur. İşlev adını yine de _s sürümüyle değiştirmeniz gerekir, ancak bu genellikle bir arama ve değiştirme işlemiyle yapılabilir.

Bazı işlevlerin dönüş değerleri değişti. Örneğin, _itoa_s (ve _itow_s makro _itot_s) dizesi yerine bir hata kodu ()errno_t döndürür. Bu durumda çağrıyı ayrı bir satıra _itoa_s taşımanız ve arabelleğin tanımlayıcısıyla değiştirmeniz gerekir.

Yaygın durumlardan bazıları: için memcpyöğesine geçiş memcpy_syaparken, genellikle kopyalanan yapının boyutunu ekledik. Benzer şekilde, çoğu dize ve arabellek için, dizinin veya arabelleğin boyutu, arabelleğin bildiriminden veya arabelleğin özgün olarak nereye ayrıldığı bulunarak kolayca belirlenir. Bazı durumlarda, bir arabelleğin gerçekten ne kadar büyük kullanılabilir olduğunu belirlemeniz gerekir ve bu bilgiler değiştirdiğiniz işlevin kapsamında mevcut değilse, ek bir parametre olarak eklenmelidir ve arama kodu bilgileri sağlayacak şekilde değiştirilmelidir.

Bu tekniklerle kodun güvenli CRT işlevlerini kullanacak şekilde dönüştürülmesi yaklaşık yarım gün sürdü. Şablon aşırı yüklemelerini seçmemeyi ve boyut parametrelerini el ile eklemeyi seçerseniz, büyük olasılıkla iki veya üç kat daha fazla zaman alır.

Adım 13. /Zc:forScope- kullanım dışı bırakıldı

Visual C++ 6.0'dan bu yana, derleyici geçerli standarda uygundur ve bu da döngüde bildirilen değişkenlerin kapsamını döngü kapsamıyla sınırlar. Derleyici seçeneği /Zc:forScope (Proje özelliklerinde Döngü Kapsamı için Uyumluluğu Zorla) bunun hata olarak bildirilip bildirilmeyeceğini denetler. Kodumuzu uyumlu olacak şekilde güncelleştirmeli ve döngünün hemen dışında bildirimler eklemeliyiz. Kod değişikliklerini önlemek için, C++ proje özelliklerinin Dil bölümündeki bu ayarı olarak No (/Zc:forScope-)değiştirebilirsiniz. Ancak, Visual C++'ın gelecekteki bir sürümünde kaldırılabileceğini unutmayın /Zc:forScope- , bu nedenle sonunda kodunuzun standarda uyacak şekilde değiştirilmesi gerekir.

Bu sorunları çözmek nispeten kolaydır, ancak kodunuz bağlı olarak çok fazla kodu etkileyebilir. İşte tipik bir sorun.

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

Yukarıdaki kod şu hatayı oluşturur:

'n': undeclared identifier

Bunun nedeni, derleyicinin artık C++ standardına uymayan koda izin veren bir derleyici seçeneğini kullanım dışı bırakmış olmasıdır. Standartta, bir değişkenin döngü içinde bildirilmesi, kapsamını yalnızca döngüyle kısıtlar, bu nedenle döngü dışında bir döngü sayacı kullanmanın yaygın uygulaması, aşağıdaki düzeltilen kodda olduğu gibi sayacın bildiriminin de döngü dışında taşınmasını gerektirir:

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

Özet

Spy++'ı özgün Visual C++ 6.0 kodundan en son derleyiciye taşıma yaklaşık bir hafta boyunca yaklaşık 20 saat kodlama süresi aldı. Ürünün visual studio 6.0 sürümünden Visual Studio 2015'e doğrudan sekiz sürümüne yükseltildi. Bu, artık büyük ve küçük projelerdeki tüm yükseltmeler için önerilen yaklaşımdır.

Ayrıca bkz.

Taşıma ve Yükseltme: Örnekler ve Örnek Olay İncelemeleri
Önceki örnek olay incelemesi: COM Spy