Panduan Porting: Spy++
Studi kasus porting ini dirancang untuk memberi Anda gambaran tentang seperti apa proyek porting yang khas, jenis masalah yang mungkin Anda temui, dan beberapa tips dan trik umum untuk mengatasi masalah porting. Ini tidak dimaksudkan untuk menjadi panduan pasti untuk porting, karena pengalaman porting proyek sangat tergantung pada spesifik kode.
Spy++
Spy++ adalah alat diagnostik GUI yang banyak digunakan untuk desktop Windows yang menyediakan semua jenis informasi tentang elemen antarmuka pengguna di desktop Windows. Ini menunjukkan hierarki jendela lengkap dan menyediakan akses ke metadata tentang setiap jendela dan kontrol. Aplikasi yang berguna ini telah dikirim dengan Visual Studio selama bertahun-tahun. Kami menemukan versi lamanya yang terakhir dikompilasi di Visual C++ 6.0 dan memindahkannya ke Visual Studio 2015. Pengalaman untuk Visual Studio 2017 atau Visual Studio 2019 harus hampir identik.
Kami menganggap kasus ini khas untuk porting aplikasi desktop Windows yang menggunakan MFC dan API Win32, terutama untuk proyek lama yang belum diperbarui dengan setiap rilis Visual C++ sejak Visual C++ 6.0.
Langkah 1. Mengonversi file proyek.
File proyek, dua file .dsw lama dari Visual C++ 6.0, dikonversi dengan mudah tanpa masalah yang memerlukan perhatian lebih lanjut. Satu proyek adalah aplikasi Spy++. Yang lain adalah SpyHk, ditulis dalam C, DLL pendukung. Proyek yang lebih kompleks mungkin tidak ditingkatkan dengan mudah, seperti yang dibahas di sini.
Setelah meningkatkan dua proyek, solusi kami terlihat seperti ini:
Kami memiliki dua proyek, satu dengan sejumlah besar file C++, dan dll lain yang ditulis dalam C.
Langkah 2. Masalah file header
Setelah membangun proyek yang baru dikonversi, salah satu hal pertama yang sering Anda temukan adalah bahwa file header yang digunakan proyek Anda tidak ditemukan.
Salah satu file yang tidak dapat ditemukan di Spy++ adalah verstamp.h. Dari pencarian Internet, kami menentukan bahwa ini berasal dari DAO SDK, teknologi data usang. Kami ingin mengetahui simbol apa yang digunakan dari file header itu, untuk melihat apakah file itu benar-benar diperlukan atau apakah simbol-simbol tersebut didefinisikan di tempat lain, jadi kami mengomentari deklarasi file header dan dikompresi ulang. Ternyata hanya ada satu simbol yang diperlukan, 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
Cara term mudah untuk menemukan simbol dalam file yang tersedia adalah dengan menggunakan Temukan di File (Ctrl+Shift+F) dan tentukan Visual C++ Sertakan Direktori. Kami menemukannya di ntverp.h. Kami mengganti verstamp.h termasuk dengan ntverp.h dan kesalahan ini menghilang.
Langkah 3. Pengaturan Linker OutputFile
Proyek lama terkadang memiliki file yang ditempatkan di lokasi yang tidak konvensional yang dapat menyebabkan masalah setelah peningkatan. Dalam hal ini, kita harus menambahkan $(SolutionDir)
ke jalur Sertakan di properti proyek untuk memastikan bahwa Visual Studio dapat menemukan beberapa file header yang ditempatkan di sana, bukan di salah satu folder proyek.
MSBuild mengeluh bahwa properti Link.OutputFile tidak cocok dengan nilai TargetPath dan TargetName , mengeluarkan 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 adalah output build (EXE, DLL, misalnya), dan biasanya dibangun dari $(TargetDir)$(TargetName)$(TargetExt)
, memberikan jalur, nama file, dan ekstensi. Ini adalah kesalahan umum saat memigrasikan proyek dari alat build Visual C++ lama (vcbuild.exe) ke alat build baru (MSBuild.exe). Karena perubahan alat build terjadi di Visual Studio 2010, Anda mungkin mengalami masalah ini setiap kali Anda memigrasikan proyek pra-2010 ke versi 2010 atau yang lebih baru. Masalah dasarnya adalah bahwa wizard migrasi proyek tidak memperbarui nilai Link.OutputFile karena tidak selalu memungkinkan untuk menentukan nilainya berdasarkan pengaturan proyek lainnya. Oleh karena itu, Anda biasanya harus mengaturnya secara manual. Untuk detail selengkapnya, lihat postingan ini di blog Visual C++.
Dalam hal ini, properti Link.OutputFile dalam proyek yang dikonversi diatur ke .\Debug\Spyxx.exe dan .\Release\Spyxx.exe untuk proyek Spy++, tergantung pada konfigurasinya. Taruhan terbaik adalah hanya mengganti nilai hardcoded ini dengan $(TargetDir)$(TargetName)$(TargetExt)
untuk Semua Konfigurasi. Jika tidak berhasil, Anda dapat menyesuaikan dari sana, atau mengubah properti di bagian Umum tempat nilai tersebut diatur (propertinya adalah Direktori Output, Nama Target, dan Ekstensi Target. Ingatlah bahwa jika properti yang Anda lihat menggunakan makro, Anda dapat memilih Edit di daftar dropdown untuk memunculkan kotak dialog yang memperlihatkan string akhir dengan substitusi makro yang dibuat. Anda dapat melihat semua makro yang tersedia dan nilainya saat ini dengan memilih tombol Makro .
Langkah 4. Memperbarui Versi Windows Target
Kesalahan berikutnya menunjukkan bahwa versi WINVER tidak lagi didukung di MFC. WINVER untuk Windows XP 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 tidak lagi didukung oleh Microsoft, jadi meskipun menargetkannya diizinkan di Visual Studio, Anda harus mengeluarkan dukungan untuk itu di aplikasi Anda, dan mendorong pengguna Anda untuk mengadopsi versi Baru Windows.
Untuk menghilangkan kesalahan, tentukan WINVER dengan memperbarui pengaturan Properti Proyek ke versi terendah Windows yang saat ini ingin Anda targetkan. Temukan tabel nilai untuk berbagai rilis Windows di sini.
File stdafx.h berisi beberapa definisi makro ini.
#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 akan kita atur ke Windows 7. Lebih mudah untuk membaca kode nanti jika Anda menggunakan makro untuk Windows 7 (_WIN32_WINNT_WIN7), daripada nilai itu sendiri (0x0601).
#define WINVER _WINNT_WIN32_WIN7 // Minimum targeted Windows version is Windows 7
Langkah 5. Kesalahan Linker
Dengan perubahan ini, proyek SpyHk (DLL) membangun tetapi menghasilkan kesalahan linker.
LINK : warning LNK4216: Exported entry point _DLLEntryPoint@12
Titik masuk untuk DLL tidak boleh diekspor. Titik masuk hanya dimaksudkan untuk dipanggil oleh loader ketika DLL pertama kali dimuat ke dalam memori, sehingga tidak boleh berada dalam tabel ekspor, yang untuk pemanggil lain. Kita hanya perlu memastikan itu tidak memiliki direktif yang __declspec(dllexport)
melekat padanya. Dalam spyxxhk.c, kita harus menghapusnya dari dua tempat, deklarasi dan definisi .DLLEntryPoint
Tidak pernah masuk akal untuk menggunakan direktif ini, tetapi versi sebelumnya dari linker dan compiler tidak menandainya sebagai masalah. Versi penghubung yang lebih baru memberikan peringatan.
// deleted __declspec(dllexport)
BOOL WINAPI DLLEntryPoint(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);
Proyek C DLL, SpyHK.dll, sekarang membangun dan menautkan tanpa kesalahan.
Langkah 6. File header yang lebih kedaluarsa
Pada titik ini kita mulai mengerjakan proyek utama yang dapat dieksekusi, Spyxx.
Beberapa file include lainnya tidak dapat ditemukan: ctl3d.h dan penwin.h. Meskipun mungkin berguna untuk mencari di Internet untuk mencoba mengidentifikasi apa yang menyertakan header, terkadang informasinya tidak berguna. Kami menemukan bahwa ctl3d.h adalah bagian dari Exchange Development Kit dan memberikan dukungan untuk gaya kontrol tertentu pada Windows 95, dan penwin.h berkaitan dengan Window Pen Computing, API usang. Dalam hal ini, kami hanya mengomentari #include
baris, dan menangani simbol yang tidak ditentukan seperti yang kami lakukan dengan verstamp.h. Semua yang berkaitan dengan Kontrol 3D atau Komputasi Pena dihapus dari proyek.
Mengingat proyek dengan banyak kesalahan kompilasi yang Anda hilangkan secara bertahap, tidak realistis untuk menemukan semua penggunaan API yang kedaluarsa segera ketika Anda menghapus #include
direktif. Kami tidak segera mendeteksinya, tetapi pada beberapa titik kemudian datang ke kesalahan bahwa WM_DLGBORDER tidak terdefinis. Ini sebenarnya hanya satu banyak simbol yang tidak terdefinisi yang berasal dari ctl3d.h. Setelah kami menentukan bahwa itu berkaitan dengan API yang kedaluarsa, kami menghapus semua referensi dalam kode ke dalamnya.
Langkah 7. Memperbarui kode iostream lama
Kesalahan berikutnya umum dengan kode C++ lama yang menggunakan iostream.
mstream.h(40): fatal error C1083: Cannot open include file: 'iostream.h': No such file or directory
Masalahnya adalah bahwa pustaka iostreams lama telah dihapus dan diganti. Kita harus mengganti iostream lama dengan standar yang lebih baru.
#include <iostream.h>
#include <strstrea.h>
#include <iomanip.h>
Ini adalah yang diperbarui meliputi:
#include <iostream>
#include <sstream>
#include <iomanip>
Dengan perubahan ini, kami memiliki masalah dengan ostrstream
, yang tidak lagi digunakan. Pengganti yang sesuai adalah ostringstream. Kami mencoba menambahkan typedef
untuk ostrstream
menghindari memodifikasi kode terlalu banyak, setidaknya sebagai awal.
typedef std::basic_ostringstream<TCHAR> ostrstream;
Saat ini proyek dibangun menggunakan MBCS (Set Karakter Multi-byte), begitu juga char
jenis data karakter yang sesuai. Namun, untuk memungkinkan pembaruan kode yang lebih mudah ke Unicode UTF-16, kami memperbarui ini ke TCHAR
, yang menyelesaikan ke char
atau wchar_t
tergantung pada apakah properti Set Karakter dalam pengaturan proyek diatur ke MBCS atau Unicode.
Beberapa bagian kode lainnya perlu diperbarui. Kami mengganti kelas ios
dasar dengan ios_base
, dan kami mengganti ostream adalah dengan basic_ostream<T>. Kami menambahkan dua typedef tambahan, dan bagian ini dikompilasi.
typedef std::basic_ostream<TCHAR> ostream;
typedef ios_base ios;
Menggunakan typedefs ini hanyalah solusi sementara. Untuk solusi yang lebih permanen, kita dapat memperbarui setiap referensi ke API yang diganti namanya atau kedaluarsa.
Berikut adalah kesalahan berikutnya.
error C2039: 'freeze': is not a member of 'std::basic_stringbuf<char,std::char_traits<char>,std::allocator<char>>'
Masalah berikutnya adalah bahwa basic_stringbuf
tidak memiliki freeze
metode. Metode freeze
ini digunakan untuk mencegah kebocoran memori di lama ostream
. Kita tidak membutuhkannya sekarang karena kita menggunakan yang baru ostringstream
. Kita dapat menghapus panggilan ke freeze
.
//rdbuf()->freeze(0);
Dua kesalahan berikutnya terjadi pada baris yang berdekatan. Yang pertama mengeluh tentang menggunakan ends
, yang merupakan manipulator IO pustaka lama iostream
yang menambahkan terminator null ke string. Kesalahan kedua ini menjelaskan bahwa output str
metode tidak dapat ditetapkan ke penunjuk non-const.
// 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'
Menggunakan pustaka stream baru, ends
tidak diperlukan karena string selalu dihentikan null, sehingga baris dapat dihapus. Untuk masalah kedua, masalahnya adalah bahwa sekarang str()
tidak mengembalikan penunjuk ke array karakter untuk string; itu mengembalikan std::string
jenis . Solusi untuk yang kedua adalah mengubah jenis menjadi LPCSTR
dan menggunakan c_str()
metode untuk meminta penunjuk.
//*this << ends;
LPCTSTR psz = str().c_str();
Kesalahan yang membingungkan kami untuk sementara waktu terjadi pada kode ini.
MOUT << _T(" chUser:'") << chUser
<< _T("' (") << (INT)(UCHAR)chUser << _T(')');
MOUT makro menyelesaikan *g_pmout
yang merupakan objek jenis mstream
. Kelas mstream
ini berasal dari kelas string output standar, std::basic_ostream<TCHAR>
. Namun dengan _T di sekitar string literal, yang kami masukkan dalam persiapan untuk mengonversi ke Unicode, resolusi kelebihan beban untuk operator << gagal dengan pesan kesalahan berikut:
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])'
Ada begitu banyak definisi operator << sehingga kesalahan semacam ini dapat mengintimidasi. Setelah melihat lebih dekat pada kelebihan beban yang tersedia, kita dapat melihat bahwa sebagian besar dari mereka tidak relevan, dan melihat lebih dekat mstream
pada definisi kelas, kami mengidentifikasi fungsi berikut yang menurut kami harus dipanggil dalam kasus ini.
mstream& operator<<(LPTSTR psz)
{
return (mstream&)ostrstream::operator<<(psz);
}
Alasannya tidak dipanggil adalah karena string harfiah memiliki jenis const wchar_t[10]
seperti yang Anda lihat dari baris terakhir pesan kesalahan panjang itu, sehingga konversi ke penunjuk non-const tidak otomatis. Namun operator tersebut tidak boleh memodifikasi parameter input, sehingga jenis parameter yang lebih sesuai adalah LPCTSTR
(const char*
saat mengkompilasi sebagai MBCS, dan const wchar_t*
sebagai Unicode), bukan LPTSTR
(char*
saat mengkompilasi sebagai MBCS, dan wchar_t*
sebagai Unicode). Membuat perubahan tersebut memperbaiki kesalahan ini.
Jenis konversi ini diizinkan di bawah pengkompilasi yang lebih lama dan kurang ketat, tetapi perubahan kesuaian yang lebih baru memerlukan kode yang lebih benar.
Langkah 8. Konversi kompilator yang lebih ketat
Kami juga mendapatkan banyak kesalahan seperti berikut:
error C2440: 'static_cast': cannot convert from 'UINT (__thiscall CHotLinkCtrl::* )(CPoint)' to 'LRESULT (__thiscall CWnd::* )(CPoint)'
Kesalahan terjadi dalam peta pesan yang hanya makro:
BEGIN_MESSAGE_MAP(CFindToolIcon, CWnd)
// other messages omitted...
ON_WM_NCHITTEST() // Error occurs on this line.
END_MESSAGE_MAP()
Masuk ke definisi makro ini, kita melihatnya mereferensikan fungsi 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)) },
Masalahnya ada hubungannya dengan ketidakcocokan dalam jenis fungsi pointer ke anggota. Masalahnya bukan konversi dari CHotLinkCtrl
sebagai jenis kelas ke CWnd
sebagai jenis kelas, karena itu adalah konversi turunan-ke-dasar yang valid. Masalahnya adalah jenis pengembalian: UINT vs. LRESULT. LRESULT memutuskan untuk LONG_PTR yang merupakan pointer 64-bit atau pointer 32-bit, tergantung pada jenis biner target, sehingga UINT tidak mengonversi ke jenis ini. Ini tidak jarang ketika meningkatkan kode yang ditulis sebelum 2005 karena jenis pengembalian banyak metode peta pesan berubah dari UINT ke LRESULT di Visual Studio 2005 sebagai bagian dari perubahan kompatibilitas 64-bit. Kami mengubah jenis pengembalian dari UINT dalam kode berikut ke LRESULT:
afx_msg UINT OnNcHitTest(CPoint point);
Setelah perubahan, kami memiliki kode berikut:
afx_msg LRESULT OnNcHitTest(CPoint point);
Karena ada sekitar sepuluh kemunculan fungsi ini semua di kelas yang berbeda yang berasal dari CWnd, sangat membantu untuk menggunakan Go to Definition (Keyboard: F12) dan Buka Deklarasi (Keyboard: Ctrl+F12) ketika kursor berada di fungsi di editor untuk menemukan ini dan menavigasi ke mereka dari jendela alat Temukan Simbol. Buka Definisi biasanya lebih berguna dari keduanya. Buka Deklarasi akan menemukan deklarasi selain deklarasi kelas yang menentukan, seperti deklarasi kelas teman atau referensi maju.
Langkah 9. Perubahan MFC
Kesalahan berikutnya juga berkaitan dengan jenis deklarasi yang diubah dan juga terjadi dalam makro.
error C2440: 'static_cast': cannot convert from 'void (__thiscall CFindWindowDlg::* )(BOOL,HTASK)' to 'void (__thiscall CWnd::* )(BOOL,DWORD)'
Masalahnya adalah bahwa parameter kedua diubah CWnd::OnActivateApp
dari HTASK ke DWORD. Perubahan ini terjadi dalam rilis Visual Studio 2002, Visual Studio .NET.
afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);
Kita harus memperbarui deklarasi OnActivateApp di kelas turunan yang sesuai sebagai berikut:
afx_msg void OnActivateApp(BOOL bActive, DWORD dwThreadId);
Pada titik ini, kita dapat mengkompilasi proyek. Namun, ada beberapa peringatan untuk dikerjakan, dan ada bagian opsional dari peningkatan, seperti mengonversi dari MBCS ke Unicode atau meningkatkan keamanan dengan menggunakan fungsi Secure CRT.
Langkah 10. Mengatasi peringatan kompilator
Untuk mendapatkan daftar lengkap peringatan, Anda harus melakukan Pembangunan Ulang Semua pada solusi daripada build biasa, hanya untuk memastikan bahwa semua yang sebelumnya dikompilasi akan dikompilasi ulang, karena Anda hanya mendapatkan laporan peringatan dari kompilasi saat ini. Pertanyaan lainnya adalah apakah akan menerima tingkat peringatan saat ini atau menggunakan tingkat peringatan yang lebih tinggi. Saat mentransfer banyak kode, terutama kode lama, menggunakan tingkat peringatan yang lebih tinggi mungkin sesuai. Anda mungkin juga ingin memulai dengan tingkat peringatan default lalu meningkatkan tingkat peringatan untuk mendapatkan semua peringatan. Jika Anda menggunakan /Wall
, Anda mendapatkan beberapa peringatan dalam file header sistem, sehingga banyak orang menggunakan /W4
untuk mendapatkan peringatan terbanyak pada kode mereka tanpa mendapatkan peringatan untuk header sistem. Jika Anda ingin peringatan muncul sebagai kesalahan, tambahkan /WX
opsi . Pengaturan ini ada di bagian C/C++ dari kotak dialog Properti Proyek.
Salah satu metode di CSpyApp
kelas menghasilkan peringatan tentang fungsi yang tidak lagi didukung.
void SetDialogBkColor() {CWinApp::SetDialogBkColor(::GetSysColor(COLOR_BTNFACE));}
Peringatannya adalah sebagai berikut.
warning C4996: 'CWinApp::SetDialogBkColor': CWinApp::SetDialogBkColor is no longer supported. Instead, handle WM_CTLCOLORDLG in your dialog
Pesan WM_CTLCOLORDLG sudah ditangani dalam kode Spy++, jadi satu-satunya perubahan yang diperlukan adalah menghapus referensi apa pun ke SetDialogBkColor
, yang tidak lagi diperlukan.
Peringatan berikutnya mudah diperbaiki dengan mengomentari nama variabel. Kami menerima peringatan berikut:
warning C4456: declaration of 'lpszBuffer' hides previous local declaration
Kode yang menghasilkan ini melibatkan makro.
DECODEPARM(CB_GETLBTEXT)
{
P2WPOUT();
P2LPOUTPTRSTR;
P2IFDATA()
{
PARM(lpszBuffer, PPACK_STRINGORD, ED2);
INDENT();
P2IFISORD(lpszBuffer)
{
P2OUTORD(lpszBuffer);
}
else
{
PARM(lpszBuffer, LPTSTR, ED2);
P2OUTS(lpszBuffer);
}
}
}
Penggunaan makro yang berat seperti dalam kode ini cenderung membuat kode lebih sulit untuk dipertahankan. Dalam hal ini, makro menyertakan deklarasi variabel. PARM makro didefinisikan sebagai berikut:
#define PARM(var, type, src)type var = (type)src
lpszBuffer
Oleh karena itu variabel akan dideklarasikan dua kali dalam fungsi yang sama. Tidak mudah untuk memperbaiki ini seperti jika kode tidak menggunakan makro (cukup hapus deklarasi jenis kedua). Apa adanya, kita memiliki pilihan yang tidak menguntungkan untuk memutuskan apakah akan menulis ulang kode makro sebagai kode biasa (tugas yang melelahkan dan mungkin rawan kesalahan) atau menonaktifkan peringatan.
Dalam hal ini, kami memilih untuk menonaktifkan peringatan. Kita dapat melakukannya dengan menambahkan pragma sebagai berikut:
#pragma warning(disable : 4456)
Saat menonaktifkan peringatan, Anda mungkin ingin membatasi efek menonaktifkan hanya pada kode yang Anda berikan peringatan, untuk menghindari menekan peringatan ketika mungkin memberikan informasi yang berguna. Kami menambahkan kode untuk memulihkan peringatan tepat setelah baris yang menghasilkannya, atau lebih baik lagi, karena peringatan ini terjadi dalam makro, gunakan kata kunci __pragma , yang berfungsi dalam makro (#pragma
tidak berfungsi dalam makro).
#define PARM(var, type, src)__pragma(warning(disable : 4456)) \
type var = (type)src \
__pragma(warning(default : 4456))
Peringatan berikutnya memerlukan beberapa revisi kode. API GetVersion
Win32 (dan GetVersionEx
) tidak digunakan lagi.
warning C4996: 'GetVersion': was declared deprecated
Kode berikut menunjukkan bagaimana versi diperoleh.
// check Windows version and set m_bIsWindows9x/m_bIsWindows4x/m_bIsWindows5x flags accordingly.
DWORD dwWindowsVersion = GetVersion();
Ini diikuti oleh banyak kode yang memeriksa nilai dwWindowsVersion untuk menentukan apakah kita berjalan di Windows 95, dan versi Windows NT mana. Karena ini semua sudah kedaluarsa, kami menghapus kode dan menangani referensi apa pun ke variabel tersebut.
Artikel Versi sistem operasi berubah di Windows 8.1 dan Windows Server 2012 R2 menjelaskan situasinya.
Ada metode di kelas yang mengkueri CSpyApp
versi sistem operasi: IsWindows9x
, IsWindows4x
, dan IsWindows5x
. Titik awal yang baik adalah mengasumsikan bahwa versi Windows yang ingin kami dukung (Windows 7 dan yang lebih baru) semuanya dekat dengan Windows NT 5 sejauh teknologi yang digunakan oleh aplikasi lama ini. Penggunaan metode ini adalah untuk menangani keterbatasan sistem operasi yang lebih lama. Jadi kami mengubah metode tersebut untuk mengembalikan TRUE untuk IsWindows5x
dan FALSE untuk yang lain.
BOOL IsWindows9x() {/*return(m_bIsWindows9x);*/ return FALSE; }
BOOL IsWindows4x() {/*return(m_bIsWindows4x);*/ return FALSE; }
BOOL IsWindows5x() {/*return(m_bIsWindows5x);*/ return TRUE; }
Itu hanya menyisakan beberapa tempat di mana variabel internal digunakan secara langsung. Karena kami menghapus variabel tersebut, kami mendapatkan beberapa kesalahan yang harus ditangani secara eksplisit.
error C2065: 'm_bIsWindows9x': undeclared identifier
void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_bIsWindows9x || hToolhelp32 != NULL);
}
Kita dapat mengganti ini dengan panggilan metode atau hanya meneruskan TRUE dan menghapus kasus khusus lama untuk Windows 9x.
void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
pCmdUI->Enable(TRUE /*!m_bIsWindows9x || hToolhelp32 != NULL*/);
}
Peringatan akhir pada tingkat default (3) ada hubungannya dengan bitfield.
treectl.cpp(1656): warning C4463: overflow; assigning 1 to bit-field that can only hold values from -1 to 0
Kode yang memicu ini adalah sebagai berikut.
m_bStdMouse = TRUE;
Deklarasi m_bStdMouse
menunjukkan bahwa itu adalah ladang bit.
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;
Kode ini ditulis sebelum jenis bool bawaan didukung di Visual C++. Dalam kode seperti itu, BOOL adalah typedef
untuk int
. Jenisnya int
adalah signed
jenis, dan representasi bit dari adalah signed int
menggunakan bit pertama sebagai bit tanda, sehingga bitfield jenis int dapat ditafsirkan sebagai mewakili 0 atau -1, mungkin bukan apa yang dimaksudkan.
Anda tidak akan tahu dengan melihat kode mengapa ini adalah bitfields. Apakah niat untuk menjaga ukuran objek tetap kecil, atau apakah ada di mana saja tata letak biner objek digunakan? Kami mengubah ini menjadi anggota BOOL biasa karena kami tidak melihat alasan untuk penggunaan bitfield. Menggunakan bitfield untuk menjaga ukuran objek tetap kecil tidak dijamin berfungsi. Itu tergantung pada bagaimana pengkompilasi menjabarkan jenisnya.
Anda mungkin bertanya-tanya apakah menggunakan jenis bool
standar di seluruh akan membantu. Banyak pola kode lama seperti jenis BOOL ditemukan untuk memecahkan masalah yang kemudian diselesaikan dalam C++standar, jadi mengubah dari BOOL ke bool
jenis bawaan hanyalah salah satu contoh perubahan seperti itu yang Anda pertimbangkan untuk dilakukan setelah Anda mendapatkan kode Anda yang awalnya berjalan di versi baru.
Setelah kami menangani semua peringatan yang muncul di tingkat default (tingkat 3) kami berubah menjadi tingkat 4 untuk menangkap beberapa peringatan tambahan. Yang pertama muncul adalah sebagai berikut:
warning C4100: 'nTab': unreferenced formal parameter
Kode yang menghasilkan peringatan ini adalah sebagai berikut.
virtual void OnSelectTab(int nTab) {};
Ini tampaknya cukup berbahaya, tetapi karena kami menginginkan kompilasi yang bersih dengan /W4
dan /WX
diatur, kami hanya mengomentari nama variabel, membiarkannya demi keterbacaan.
virtual void OnSelectTab(int /*nTab*/) {};
Peringatan lain yang kami terima berguna untuk pembersihan kode umum. Ada sejumlah konversi implisit dari int
atau unsigned int
ke WORD (yang merupakan typedef untuk unsigned short
). Ini melibatkan kemungkinan hilangnya data. Kami menambahkan cast ke WORD dalam kasus ini.
Peringatan tingkat 4 lain yang kami dapatkan untuk kode ini adalah:
warning C4211: nonstandard extension used: redefined extern to static
Masalah terjadi ketika variabel pertama kali dideklarasikan extern
, kemudian dinyatakan static
. Arti dari kedua penentu kelas penyimpanan ini saling eksklusif, tetapi ini diizinkan sebagai ekstensi Microsoft. Jika Anda ingin kode portabel ke kompilator lain, atau Anda ingin mengkompilasinya dengan /Za
(kompatibilitas ANSI), Anda akan mengubah deklarasi agar memiliki penentu kelas penyimpanan yang cocok.
Langkah 11. Porting dari MBCS ke Unicode
Perhatikan bahwa di dunia Windows, ketika kita mengatakan Unicode, kita biasanya berarti UTF-16. Sistem operasi lain seperti Linux menggunakan UTF-8, tetapi Windows umumnya tidak. MBCS versi MFC tidak digunakan lagi di Visual Studio 2013 dan 2015, tetapi tidak lagi tidak digunakan lagi di Visual Studio 2017. Jika menggunakan Visual Studio 2013 atau 2015, sebelum mengambil langkah untuk benar-benar memindahkan kode MBCS ke UTF-16 Unicode, kami mungkin ingin menghilangkan sementara peringatan bahwa MBCS tidak digunakan lagi, untuk melakukan pekerjaan lain atau menunda porting hingga waktu yang tepat. Kode saat ini menggunakan MBCS dan untuk melanjutkannya, kita perlu menginstal MFC versi ANSI/MBCS. Pustaka MFC yang agak besar bukan bagian dari pengembangan Visual Studio Desktop default dengan penginstalan C++ , sehingga harus dipilih dari komponen opsional di alat penginstal. Lihat Add-on MFC MBCS DLL. Setelah mengunduh ini dan memulai ulang Visual Studio, Anda dapat mengkompilasi dan menautkan dengan MBCS versi MFC, tetapi untuk menyingkirkan peringatan tentang MBCS jika Anda menggunakan Visual Studio 2013 atau 2015, Anda juga harus menambahkan NO_WARN_MBCS_MFC_DEPRECATION ke daftar makro yang telah ditentukan sebelumnya di bagian Praprosesor properti proyek, atau di awal file header stdafx.h Anda atau file header umum lainnya.
Kami sekarang memiliki beberapa kesalahan linker.
fatal error LNK1181: cannot open input file 'mfc42d.lib'
LNK1181 terjadi karena versi pustaka statis mfc yang kedaluarsa disertakan pada input linker. Ini tidak diperlukan lagi karena kita dapat menautkan MFC secara dinamis, jadi kita hanya perlu menghapus semua pustaka statis MFC dari properti Input di bagian Linker dari properti proyek. Proyek ini juga menggunakan /NODEFAULTLIB
opsi , dan sebaliknya mencantumkan semua dependensi pustaka.
msvcrtd.lib;msvcirtd.lib;kernel32.lib;user32.lib;gdi32.lib;advapi32.lib;Debug\SpyHk55.lib;%(AdditionalDependencies)
Sekarang mari kita benar-benar memperbarui kode Multi-byte Character Set (MBCS) lama ke Unicode. Karena ini adalah aplikasi Windows, terikat secara intim dengan platform desktop Windows, kami akan memindahkannya ke Unicode UTF-16 yang digunakan Windows. Jika Anda menulis kode lintas platform atau memindahkan aplikasi Windows ke platform lain, Anda mungkin ingin mempertimbangkan porting ke UTF-8, yang banyak digunakan pada sistem operasi lain.
Porting ke UTF-16 Unicode, kita harus memutuskan apakah kita masih ingin opsi untuk mengkompilasi ke MBCS atau tidak. Jika kita ingin memiliki opsi untuk mendukung MBCS, kita harus menggunakan makro TCHAR sebagai jenis karakter, yang diselesaikan ke atau char
wchar_t
, tergantung pada apakah _MBCS atau _UNICODE didefinisikan selama kompilasi. Beralih ke TCHAR dan versi TCHAR dari berbagai API alih-alih wchar_t
dan API terkait berarti Anda dapat kembali ke versi MBCS kode Anda hanya dengan mendefinisikan makro _MBCS alih-alih _UNICODE. Selain TCHAR, ada berbagai versi TCHAR seperti typedef, makro, dan fungsi yang banyak digunakan. Misalnya, LPCTSTR alih-alih LPCSTR, dan sebagainya. Dalam dialog properti proyek, di bawah Properti Konfigurasi, di bagian Umum , ubah properti Kumpulan Karakter dari Gunakan Kumpulan Karakter MBCS untuk Menggunakan Kumpulan Karakter Unicode. Pengaturan ini memengaruhi makro mana yang telah ditentukan sebelumnya selama kompilasi. Ada makro UNICODE dan makro _UNICODE. Properti proyek mempengaruhi keduanya secara konsisten. Header Windows menggunakan UNICODE di mana header Visual C++ seperti MFC menggunakan _UNICODE, tetapi ketika header didefinisikan, yang lain selalu ditentukan.
Panduan yang baik untuk porting dari MBCS ke UTF-16 Unicode menggunakan TCHAR ada. Kami memilih rute ini. Pertama, kita mengubah properti Set Karakter menjadi Menggunakan Set Karakter Unicode dan membangun kembali proyek.
Beberapa tempat dalam kode itu sudah menggunakan TCHAR, rupanya untuk mengantisipasi akhirnya mendukung Unicode. Beberapa tidak. Kami mencari instans CHAR, yang merupakan typedef
untuk char
, dan mengganti sebagian besar dengan TCHAR. Juga, kami mencari sizeof(CHAR)
. Setiap kali kami berubah dari CHAR ke TCHAR, kami biasanya harus berubah sizeof(TCHAR)
karena ini sering digunakan untuk menentukan jumlah karakter dalam string. Menggunakan jenis yang salah di sini tidak menghasilkan kesalahan kompilator, jadi ada baiknya membayar sedikit perhatian untuk kasus ini.
Jenis kesalahan ini sangat umum tepat setelah beralih ke Unicode.
error C2664: 'int wsprintfW(LPWSTR,LPCWSTR,...)': cannot convert argument 1 from 'CHAR [16]' to 'LPWSTR'
Berikut adalah contoh kode yang menghasilkan ini:
wsprintf(szTmp, "%d.%2.2d.%4.4d", rmj, rmm, rup);
Kami menempatkan _T di sekitar string literal untuk menghapus kesalahan.
wsprintf(szTmp, _T("%d.%2.2d.%4.4d"), rmj, rmm, rup);
Makro _T memiliki efek membuat string literal compile sebagai char
string atau wchar_t
string, tergantung pada pengaturan MBCS atau UNICODE. Untuk mengganti semua string dengan _T di Visual Studio, pertama-tama buka kotak Ganti Cepat (Keyboard: Ctrl+F) atau kotak ganti Dalam File (Keyboard: Ctrl+Shift+H), lalu pilih kotak centang Gunakan Ekspresi Reguler. Masukkan ((\".*?\")|('.+?'))
sebagai teks pencarian dan _T($1)
sebagai teks pengganti. Jika Anda sudah memiliki makro _T di sekitar beberapa string, prosedur ini akan menambahkannya lagi, dan mungkin juga menemukan kasus di mana Anda tidak ingin _T, seperti saat Anda menggunakan #include
, jadi yang terbaik adalah menggunakan Ganti Berikutnya daripada Ganti Semua.
Fungsi khusus ini, wsprintf, sebenarnya didefinisikan dalam header Windows, dan dokumentasi untuk itu merekomendasikan agar tidak digunakan, karena kemungkinan buffer diserbu. Tidak ada ukuran yang diberikan untuk szTmp
buffer, jadi tidak ada cara bagi fungsi untuk memeriksa apakah buffer dapat menyimpan semua data yang akan ditulis ke dalamnya. Lihat bagian berikutnya tentang porting ke Secure CRT, di mana kami memperbaiki masalah serupa lainnya. Kami akhirnya menggantinya dengan _stprintf_s.
Kesalahan umum lain yang akan Anda lihat dalam mengonversi ke Unicode adalah ini.
error C2440: '=': cannot convert from 'char *' to 'TCHAR *'
Kode yang menghasilkannya adalah sebagai berikut:
pParentNode->m_szText = new char[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);
Meskipun _tcscpy
fungsi digunakan, yang merupakan fungsi strcpy TCHAR untuk menyalin string, buffer yang dialokasikan adalah char
buffer. Ini mudah diubah menjadi TCHAR.
pParentNode->m_szText = new TCHAR[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);
Demikian pula, kami mengubah LPSTR (Long Pointer ke STRing) dan LPCSTR (Long Pointer ke Constant STRing) menjadi LPTSTR (Long Pointer ke TCHAR STRing) dan LPCTSTR (Long Pointer ke Constant TCHAR STRing) masing-masing, ketika dijamin oleh kesalahan kompilator. Kami memilih untuk tidak melakukan penggantian tersebut dengan menggunakan pencarian global dan mengganti, karena setiap situasi harus diperiksa secara individual. Dalam beberapa kasus, char
versi diinginkan, seperti ketika memproses pesan Windows tertentu yang menggunakan struktur Windows yang memiliki akhiran A . Dalam WINDOWS API, akhiran A berarti ASCII atau ANSI (dan juga berlaku untuk MBCS), dan akhiran W berarti karakter lebar, atau UTF-16 Unicode. Pola penamaan ini digunakan di header Windows, tetapi kami juga mengikutinya dalam kode Spy++ ketika kami harus menambahkan versi Unicode dari fungsi yang sudah ditentukan hanya dalam versi MBCS.
Dalam beberapa kasus, kami harus mengganti jenis untuk menggunakan versi yang diselesaikan dengan benar (WNDCLASS alih-alih WNDCLASSA misalnya).
Dalam banyak kasus, kami harus menggunakan versi generik (makro) dari API Win32 seperti GetClassName
(bukan GetClassNameA
). Dalam pernyataan pengalihan handler pesan, beberapa pesan bersifat spesifik MBCS atau Unicode, dalam kasus tersebut, kami harus mengubah kode untuk secara eksplisit memanggil versi MBCS, karena kami mengganti fungsi bernama secara generis dengan fungsi khusus A dan W, dan menambahkan makro untuk nama generik yang diselesaikan ke nama A atau W yang benar berdasarkan apakah UNICODE ditentukan. Di banyak bagian kode, ketika kita beralih untuk menentukan _UNICODE, versi W sekarang dipilih bahkan ketika versi A adalah apa yang diinginkan.
Ada beberapa tempat di mana tindakan khusus harus diambil. WideCharToMultiByte
Penggunaan atau MultiByteToWideChar
mungkin memerlukan tampilan yang lebih dekat. Berikut adalah salah satu contoh di mana WideCharToMultiByte
digunakan.
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;
}
Untuk mengatasi hal ini, kami harus memahami bahwa alasan hal ini dilakukan adalah untuk menyalin string karakter lebar yang mewakili nama font ke dalam buffer CString
internal dari , strFace
. Ini memerlukan kode yang sedikit berbeda untuk string multibyte CString
seperti untuk string karakter CString
lebar, jadi kami menambahkan #ifdef
dalam hal ini.
#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
Tentu saja, alih-alih wcscpy
kita benar-benar harus menggunakan wcscpy_s
, semakin aman versi. Bagian berikutnya membahas ini.
Sebagai pemeriksaan pada pekerjaan kami, kita harus mengatur ulang Set Karakter untuk Menggunakan Set Karakter Multibyte dan memastikan bahwa kode masih dikompilasi menggunakan MBCS serta Unicode. Tidak perlu dikatakan, lulus pengujian penuh harus dijalankan pada aplikasi yang dikompresi ulang setelah semua perubahan ini.
Dalam pekerjaan kami dengan solusi Spy++ ini, dibutuhkan sekitar dua hari kerja bagi pengembang C++ rata-rata untuk mengonversi kode ke Unicode. Itu tidak termasuk waktu pengulangan.
Langkah 12. Porting untuk menggunakan Secure CRT
Porting kode untuk menggunakan versi aman (versi dengan akhiran _s ) fungsi CRT berikutnya. Dalam hal ini, strategi umumnya adalah mengganti fungsi dengan versi _s dan kemudian, biasanya, menambahkan parameter ukuran buffer tambahan yang diperlukan. Dalam banyak kasus, ini mudah karena ukurannya diketahui. Dalam kasus lain, di mana ukurannya tidak segera tersedia, perlu untuk menambahkan parameter tambahan ke fungsi yang menggunakan fungsi CRT, atau mungkin memeriksa penggunaan buffer tujuan dan melihat batas ukuran yang sesuai.
Visual C++ menyediakan trik untuk mempermudah mendapatkan keamanan kode tanpa menambahkan parameter ukuran sebanyak mungkin, dan dengan menggunakan kelebihan beban templat. Karena kelebihan beban ini adalah templat, mereka hanya tersedia saat mengkompilasi sebagai C++, bukan karena C. Spyxxhk adalah proyek C, sehingga trik tidak akan berfungsi untuk itu. Namun, Spyxx tidak dan kita dapat menggunakan triknya. Caranya adalah menambahkan baris seperti ini di tempat di mana ia akan dikompilasi dalam setiap file proyek, seperti di stdafx.h:
#define _CRT_SECURE_TEMPLATE_OVERLOADS 1
Ketika Anda mendefinisikan itu, setiap kali buffer adalah array, bukan pointer mentah, ukurannya disimpulkan dari jenis array dan yang digunakan sebagai parameter ukuran, tanpa Anda harus menyediakannya. Itu membantu mengurangi kompleksitas penulisan ulang kode. Anda masih harus mengganti nama fungsi dengan versi _s , tetapi itu sering kali dapat dilakukan oleh operasi pencarian dan penggantian.
Nilai yang dikembalikan dari beberapa fungsi berubah. Misalnya, _itoa_s
(dan _itow_s
dan makro _itot_s
) mengembalikan kode kesalahan (errno_t
), bukan string. Jadi dalam kasus tersebut, Anda harus memindahkan panggilan ke _itoa_s
baris terpisah dan menggantinya dengan pengidentifikasi buffer.
Beberapa kasus umum: untuk memcpy
, saat beralih ke memcpy_s
, kami sering menambahkan ukuran struktur yang sedang disalin. Demikian pula, untuk sebagian besar string dan buffer, ukuran array atau buffer mudah ditentukan dari deklarasi buffer atau dengan menemukan di mana buffer awalnya dialokasikan. Untuk beberapa situasi, Anda perlu menentukan seberapa besar buffer yang sebenarnya tersedia, dan jika informasi tersebut tidak tersedia dalam cakupan fungsi yang Anda ubah, itu harus ditambahkan sebagai parameter tambahan dan kode panggilan harus dimodifikasi untuk memberikan informasi.
Dengan teknik ini, dibutuhkan sekitar setengah hari untuk mengonversi kode untuk menggunakan fungsi CRT yang aman. Jika Anda memilih untuk tidak ke templat kelebihan beban dan untuk menambahkan parameter ukuran secara manual, mungkin akan memakan waktu dua kali atau tiga kali lebih lama.
Langkah 13. /Zc:forScope- tidak digunakan lagi
Karena Visual C++ 6.0, pengkompilasi sesuai dengan standar saat ini, yang membatasi cakupan variabel yang dideklarasikan dalam perulangan ke cakupan perulangan. Opsi kompilator /Zc:forScope (Force Conformance for Loop Scope di properti proyek) mengontrol apakah ini dilaporkan sebagai kesalahan atau tidak. Kita harus memperbarui kode agar sesuai, dan menambahkan deklarasi tepat di luar perulangan. Untuk menghindari perubahan kode, Anda dapat mengubah pengaturan tersebut di bagian Bahasa dari properti proyek C++ menjadi No (/Zc:forScope-)
. Namun, perlu diingat bahwa /Zc:forScope-
mungkin dihapus dalam rilis Visual C++di masa mendatang, jadi akhirnya kode Anda perlu berubah agar sesuai dengan standar.
Masalah ini relatif mudah diperbaiki, tetapi tergantung pada kode Anda, itu mungkin memengaruhi banyak kode. Berikut adalah masalah umum.
int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
for (int n = 0; mszStrings[0] != 0; n++)
mszStrings = _tcschr(mszStrings, 0) + 1;
return(n);
}
Kode di atas menghasilkan kesalahan:
'n': undeclared identifier
Ini terjadi karena pengkompilasi telah menghentikan opsi pengkompilasi yang memungkinkan kode yang tidak lagi sesuai dengan standar C++. Dalam standar, mendeklarasikan variabel di dalam perulangan membatasi cakupannya hanya untuk perulangan, sehingga praktik umum menggunakan penghitung perulangan di luar perulangan mengharuskan deklarasi penghitung juga dipindahkan di luar perulangan, seperti dalam kode yang direvisi berikut:
int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
int n;
for (n = 0; mszStrings[0] != 0; n++)
mszStrings = _tcschr(mszStrings, 0) + 1;
return(n);
}
Ringkasan
Porting Spy++ dari kode Visual C++ 6.0 asli ke kompilator terbaru membutuhkan waktu sekitar 20 jam pengkodean selama sekitar seminggu. Kami meningkatkan secara langsung melalui delapan rilis produk dari Visual Studio 6.0 ke Visual Studio 2015. Ini sekarang merupakan pendekatan yang direkomendasikan untuk semua peningkatan pada proyek besar dan kecil.
Lihat juga
Porting dan Peningkatan: Contoh dan Studi Kasus
Studi kasus sebelumnya: COM Spy