포팅 가이드: Spy++Porting Guide: Spy++

이 포팅 사례 연구는 일반적인 포팅 프로젝트, 발생할 수 있는 문제 유형 및 포팅 문제를 다루기 위한 몇 가지 일반적인 팁과 트릭에 대한 아이디어를 제공하도록 설계되었습니다.This porting case study is designed to give you an idea of what a typical porting project is like, the types of problems you might encounter, and some general tips and tricks for addressing porting problems. 프로젝트 포팅 경험은 코드의 세부 사항에 따라 달라지므로 결정적인 포팅 가이드는 아닙니다.It's not meant to be a definitive guide to porting, since the experience of porting a project depends very much on the specifics of the code.

Spy++Spy++

Spy++는 Windows 데스크톱의 사용자 인터페이스 요소에 대해 모든 종류의 정보를 제공하는 널리 사용되는 Windows 데스크톱용 GUI 진단 도구입니다.Spy++ is a widely used GUI diagnostic tool for the Windows desktop that provides all sorts of information about user interface elements on the Windows desktop. 창의 전체 계층 구조를 표시하고 각 창과 컨트롤에 대한 메타데이터에 액세스할 수 있게 합니다.It shows the complete hierarchy of windows and provides access to metadata about each window and control. 이 유용한 응용 프로그램은 수년 동안 Visual Studio와 함께 제공되었습니다.This useful application has shipped with Visual Studio for many years. Visual C++ 6.0에서 마지막으로 컴파일된 이전 버전이 Visual Studio 2015로 이식되었습니다.We found an old version of it that was last compiled in Visual C++ 6.0 and ported it to Visual Studio 2015. Visual Studio 2017의 환경은 거의 동일해야 합니다.The experience for Visual Studio 2017 should be almost identical.

이 사례가 특히 Visual C++ 6.0 이후 Visual C++의 각 릴리스로 업데이트되지 않은 오래된 프로젝트에 대해 MFC 및 Win32 API를 사용하는 Windows 데스크톱 응용 프로그램을 포팅하는 일반적인 경우라고 간주했습니다.We considered this case to be typical for porting Windows desktop applications that use MFC and the Win32 API, especially for old projects that have not been updated with each release of Visual C++ since Visual C++ 6.0.

1단계.Step 1. 프로젝트 파일 변환Converting the project file.

프로젝트 파일인 Visual C++ 6.0의 이전 .dsw 파일 두 개는 추가로 주의가 필요한 문제없이 쉽게 변환되었습니다.The project file, two old .dsw files from Visual C++ 6.0, converted easily with no issues that require further attention. 한 프로젝트는 Spy++ 응용 프로그램입니다.One project is the Spy++ application. 다른 프로젝트는 지원 DLL인 C로 작성된 SpyHk입니다.The other is SpyHk, written in C, a supporting DLL. 보다 복잡한 프로젝트는 여기서 설명한 것처럼 쉽게 업그레이드되지 않을 수도 있습니다.More complex projects might not upgrade as easily, as discussed here.

두 프로젝트를 업그레이드한 후 솔루션은 다음과 같았습니다.After upgrading the two projects, our solution looked like this:

Spy++ 솔루션The Spy++ Solution

하나는 많은 C++ 파일을 포함하고 다른 하나는 C로 작성된 DLL을 포함하는 두 개의 프로젝트가 있습니다.We have two projects, one with a large number of C++ files, and another a DLL that's written in C.

2단계.Step 2. 헤더 파일 문제Header file problems

새로 변환된 프로젝트를 빌드할 때 첫 번째로 발견하는 사항 중 하나는 대체로 프로젝트에서 사용하는 헤더 파일이 없다는 것입니다.Upon building a newly converted project, one of the first things you'll often find is that header files that your project uses are not found.

Spy++에서 찾을 수 없는 파일 중 하나는 verstamp.h였습니다.One of the files that couldn't be found in Spy++ was verstamp.h. 인터넷 검색을 통해 이 파일이 오래된 데이터 기술인 DAO SDK에서 제공된 것을 확인했습니다.From an Internet search, we determined that this came from a DAO SDK, an obsolete data technology. 해당 헤더 파일에서 사용된 기호를 찾아 파일이 정말 필요한지 또는 해당 기호가 다른 곳에서 정의되었는지 확인하고 싶었으므로 헤더 파일 선언을 주석으로 처리하고 다시 컴파일했습니다.We wanted to find out what symbols were being used from that header file, to see if that file was really needed or if those symbols were defined elsewhere, so we commented out the header file declaration and recompiled. 필요한 기호는 VER_FILEFLAGSMASK 하나뿐이었습니다.It turns out there is just one symbol that is needed, 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  

사용 가능한 포함 파일에서 기호를 찾는 가장 쉬운 방법은 파일에서 찾기(Ctrl+Shift+F)를 사용하고 Visual C++ 포함 디렉터리를 지정하는 것입니다.The easiest way to find a symbol in the available include files is to use Find in Files (Ctrl+Shift+F) and specify Visual C++ Include Directories. ntverp.h에서 해당 기호를 찾았습니다.We found it in ntverp.h. verstamp.h 포함 파일을 ntverp.h로 바꾼 후 이 오류가 사라졌습니다.We replaced the verstamp.h include with ntverp.h and this error disappeared.

3단계.Step 3. 링커 OutputFile 설정Linker OutputFile setting

이전 프로젝트는 업그레이드한 후 문제가 발생할 수 있는 위치에 파일이 배치된 경우가 있습니다.Older projects sometimes have files placed in unconventional locations that can cause problems after upgrading. 이 경우 Visual Studio에서 프로젝트 폴더 중 하나가 아니라 여기에 배치된 일부 헤더 파일을 찾을 수 있도록 프로젝트 속성의 포함 경로에 $(SolutionDir)을 추가해야 합니다.In this case, we have to add $(SolutionDir) to the Include path in the project properties to ensure that Visual Studio can find some header files that are placed there, rather than in one of the project folders.

MSBuild에서 Link.OutputFile 속성이 TargetPath 및 TargetName 값과 일치하지 않는다고 보고하고 MSB8012를 실행합니다.MSBuild complains that the Link.OutputFile property does not match the TargetPath and TargetName values, issuing 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은 빌드 출력(예: EXE, DLL)이며, 일반적으로 $(TargetDir)$(TargetName)$(TargetExt)에서 생성되고 경로, 파일 이름 및 확장명을 제공합니다.Link.OutputFile is the build output (EXE, DLL, for example), and it is normally constructed from $(TargetDir)$(TargetName)$(TargetExt), giving the path, filename and extension. 이는 이전 Visual C++ 빌드 도구(vcbuild.exe)에서 새 빌드 도구(MSBuild.exe)로 프로젝트를 마이그레이션하는 경우의 일반적인 오류입니다.This is a common error when migrating projects from the old Visual C++ build tool (vcbuild.exe) to the new build tool (MSBuild.exe). Visual Studio 2010에서 빌드 도구가 변경되었으므로 2010 이전 프로젝트를 2010 이상 버전으로 마이그레이션할 때마다 이 문제가 발생할 수 있습니다.Since the build tool change occurred in Visual Studio 2010, you might encounter this issue whenever you migrate a pre-2010 project to a 2010 or later version. 기본적인 문제는 해당 값이 다른 프로젝트 설정을 기반으로 해야 하는지 확인할 수 없는 경우가 있기 때문에 프로젝트 마이그레이션 마법사가 Link.OutputFile을 업데이트하지 않는 것입니다.The basic problem is that the project migration wizard doesn’t update the Link.OutputFile value since it’s not always possible to determine what its value should be based on the other project settings. 따라서 일반적으로 수동으로 설정해야 합니다.Therefore, you usually have to set it manually. 자세한 내용은 Visual C++ 블로그에서 이 게시물을 참조하세요.For more details, see this post on the Visual C++ blog.

이 경우 변환된 프로젝트의 Link.OutputFile 속성이 구성에 따라 Spy++ 프로젝트에 대한 .\Debug\Spyxx.exe 및 .\Release\Spyxx.exe로 설정되었습니다.In this case, the Link.OutputFile property in the converted project was set to .\Debug\Spyxx.exe and .\Release\Spyxx.exe for the Spy++ project, depending on the configuration. 모든 구성에 대해 이러한 하드 코딩된 값을 $(TargetDir)$(TargetName)$(TargetExt)로 바꾸는 것이 가장 좋습니다.The best bet is to simply replace these hardcoded values with $(TargetDir)$(TargetName)$(TargetExt) for All Configurations. 이 방법이 효과가 없을 경우 여기서 사용자 지정하거나, 해당 값이 설정된 일반 섹션의 속성을 변경할 수 있습니다(속성은 출력 디렉터리, 대상 이름대상 확장임).If that doesn’t work, you can customize from there, or change the properties in the General section where those values are set (the properties are Output Directory, Target Name, and Target Extension. 보려는 속성이 매크로를 사용하는 경우 드롭다운 목록에서 편집을 선택하여 매크로 대체가 수행된 최종 문자열을 보여 주는 대화 상자를 표시할 수 있습니다.Remember that if the property you are viewing uses macros, you can choose Edit in the dropdown list to bring up a dialog box that shows the final string with the macro substitutions made. 매크로 단추를 선택하여 사용 가능한 모든 매크로 및 현재 값을 볼 수 있습니다.You can view all available macros and their current values by choosing the Macros button.

4단계.Step 4. 대상 Windows 버전 업데이트Updating the Target Windows Version

다음 오류는 WINVER 버전이 MFC에서 더 이상 지원되지 않음을 나타냅니다.The next error indicates that WINVER version is no longer supported in MFC. Windows XP에 대한 WINVER은 0x0501입니다.WINVER for Windows XP is 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는 Microsoft에서 더 이상 지원되지 않으므로 Visual Studio 2015에서 대상으로 지정할 수는 있지만 응용 프로그램에서 지원을 단계적으로 중단하고 사용자가 새 버전의 Windows를 채택하도록 장려해야 합니다.Windows XP is no longer supported by Microsoft, so even though targeting it is allowed in Visual Studio 2015, you should be phasing out support for it in your applications, and encouraging your users to adopt new versions of Windows.

오류를 제거하려면 프로젝트 속성 설정을 현재 대상으로 지정하려는 가장 낮은 Windows 버전으로 업데이트하여 WINVER을 정의합니다.To get rid of the error, define WINVER by updating the Project Properties setting to the lowest version of Windows you currently want to target. 다양한 Windows 릴리스에 대한 값 테이블은 여기서 확인할 수 있습니다.Find a table of values for various Windows releases here.

stdafx.h 파일에 이러한 매크로 정의 중 일부가 포함되어 있었습니다.The stdafx.h file contained some of these macro definitions.

#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을 Windows 7으로 설정하겠습니다.WINVER we will set to Windows 7. 값 자체(0x0601)보다 Windows 7(_WIN32_WINNT_WIN7)에 대한 매크로를 사용하면 나중에 코드를 읽기가 더 쉽습니다.It’s easier to read the code later if you use the macro for Windows 7 (_WIN32_WINNT_WIN7), rather than the value itself (0x0601).

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

5단계.Step 5. 링커 오류Linker Errors

이렇게 변경하면 SpyHk(DLL) 프로젝트가 빌드되지만 링커 오류가 발생합니다.With these changes, the SpyHk (DLL) project builds but produces a linker error.

LINK : warning LNK4216: Exported entry point _DLLEntryPoint@12  

DLL에 대한 진입점을 내보내면 안 됩니다.The entry point for a DLL should not be exported. 진입점은 DLL을 메모리에 처음 로드할 때 로더에서 호출되는 용도로만 사용되므로 다른 호출자를 위한 내보내기 테이블에 포함되면 안 됩니다.The entry point is only intended to be called by the loader when the DLL is first loaded into memory, so it should not be in the export table, which is for other callers. __declspec(dllexport) 지시문이 연결되어 있지 않은지 확인하기만 하면 됩니다.We just need to make sure it does not have the __declspec(dllexport) directive attached to it. spyxxhk.c의 두 위치, 즉 DLLEntryPoint의 선언과 정의에서 제거해야 합니다.In spyxxhk.c, we have to remove it from two places, the declaration and definition of DLLEntryPoint. 이전에도 이 지시문을 사용하는 것은 타당하지 않았지만 이전 버전의 링커 및 컴파일러에서 문제로 플래그를 지정하지 않았습니다.It never made sense to use this directive, but previous versions of the linker and compiler did not flag it as problem. 최신 버전의 링커에서는 경고를 제공합니다.The newer versions of the linker give a warning.

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

C DLL 프로젝트인 SpyHK.dll이 이제 오류 없이 빌드되고 연결됩니다.The C DLL project, SpyHK.dll, now builds and links without error.

6단계.Step 6. 더 오래된 헤더 파일More outdated header files

이 시점에서 기본 실행 파일 프로젝트인 Spyxx에서 작업을 시작합니다.At this point we start working on the main executable project, Spyxx.

몇 개의 다른 포함 파일(ctl3d.h 및 penwin.h)을 찾을 수 없습니다.A couple of other include files could not be found: ctl3d.h and penwin.h. 인터넷을 검색하여 헤더에 포함된 항목을 식별하는 것이 유용할 수도 있지만 정보가 유용하지 않은 경우도 있습니다.While it might be helpful to search the Internet to try to identify what included the header, sometimes the information isn’t that helpful. ctl3d.h는 Exchange Development Kit의 일부이고 Windows 95에서 특정 스타일의 컨트롤에 대한 지원을 제공했으며, penwin.h는 사용되지 않는 API인 창 펜 컴퓨팅과 관련이 있는 것을 확인했습니다.We found out that ctl3d.h was part of the Exchange Development Kit and provided support for a certain style of controls on Windows 95, and penwin.h relates to Window Pen Computing, an obsolete API. 이 경우 #include 줄을 주석으로 처리하고, verstamp.h와 동일한 방식으로 정의되지 않은 기호를 처리합니다.In this case, we simply comment out the #include line, and deal with the undefined symbols as we did with verstamp.h. 3D 컨트롤 또는 펜 컴퓨팅과 관련된 모든 항목이 프로젝트에서 제거되었습니다.Everything that relates to 3D Controls or Pen Computing was removed from the project.

단계적으로 제거 중인 많은 컴파일 오류가 있는 프로젝트의 경우 #include 지시문을 제거할 때 바로 오래된 API의 모든 사용을 찾는 것은 비현실적입니다.Given a project with many compilation errors that you are gradually eliminating, it's not realistic to find all the uses of an outdated API right away when you remove the #include directive. 즉시 감지하지 못하고 나중에 WM_DLGBORDER이 정의되지 않았다는 오류가 발생했습니다.We didn't detect it immediately, but rather at some later point came to an error that WM_DLGBORDER was undefined. 이는 실제로 ctl3d.h에서 제공되는, 정의되지 않은 많은 기호 중 하나일 뿐입니다.It is actually just one many undefined symbols that come from ctl3d.h. 오래된 API와 관련이 있음을 확인한 후 코드에서 해당 참조를 모두 제거했습니다.Once we've determined that it relates to an outdated API, we removed all references in code to it.

7단계.Step 7. 이전 iostreams 코드 업데이트Updating old iostreams code

다음 오류는 iostreams를 사용하는 이전 C++ 코드에서 일반적으로 발생합니다.The next error is common with old C++ code that uses iostreams.

mstream.h(40): 오류 C1083: 포함 파일을 열 수 없습니다 'iostream.h': 해당 파일 또는 디렉터리가 없습니다.mstream.h(40): fatal error C1083: Cannot open include file: 'iostream.h': No such file or directory

문제는 이전 iostreams 라이브러리가 제거 및 대체된 것입니다.The issue is that the old iostreams library has been removed and replaced. 이전 iostreams를 최신 표준으로 바꾸어야 합니다.We have to replace the old iostreams with the newer standards.

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

다음은 업데이트된 포함 파일입니다.These are the updated includes:

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

이렇게 변경하면 더 이상 사용되는 ostrstream에서 문제가 발생합니다.With this change, we have problems with ostrstream, which is no longer used. 적절한 대체 항목은 ostringstream입니다.The appropriate replacement is ostringstream. 최소한 시작하면서 코드를 너무 많이 수정하지 않도록 ostrstream의 typedef를 추가하려고 합니다.We try adding a typedef for ostrstream to avoid modifying the code too much, at least as a start.

typedef std::basic_ostringstream<TCHAR> ostrstream;  

현재 프로젝트는 MBCS(멀티바이트 문자 집합)를 사용하여 빌드되므로 char가 적절한 문자 데이터 형식입니다.Currently the project is building using MBCS (Multi-byte Character Set), so char is the appropriate character data type. 그러나 코드를 UTF-16 유니코드로 업데이트하기 쉽도록 프로젝트 설정의 문자 집합 속성이 MBCS 또는 유니코드로 설정되었는지에 따라 char 또는 wchar_t로 확인되는 TCHAR로 업데이트합니다.However, to allow an easier update the code to UTF-16 Unicode, we update this to TCHAR, which resolves to char or wchar_t depending on whether the Character Set property in the project settings is set to MBCS or Unicode.

다른 몇 가지 코드 부분도 업데이트해야 합니다.A few other pieces of code need to be updated. 기본 클래스 ios를 ios_base로 바꾸고 ostream을 basic_ostream<T>로 바꾸었습니다.We replaced the base class ios with ios_base, and we replaced ostream is by basic_ostream<T>. 두 개의 추가 typedef를 추가하면 이 섹션이 컴파일됩니다.We add two additional typedefs, and this section compiles.

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

이러한 typedef를 사용하는 것은 임시 솔루션일 뿐입니다.Using these typedefs is just a temporary solution. 더 영구적인 솔루션을 위해 이름이 바뀌었거나 오래된 API에 대한 각 참조를 업데이트할 수 있습니다.For a more permanent solution, we could update each reference to the renamed or outdated API.

그다음 오류는 다음과 같습니다.Here’s the next error.

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

다음 문제는 basic_stringbuf에 freeze 메서드가 없는 것입니다.The next issue is that basic_stringbuf doesn’t have a freeze method. freeze 메서드는 이전 ostream에서 메모리 누수를 방지하는 데 사용됩니다.The freeze method is used to prevent a memory leak in the old ostream. 이제 새 ostringstream을 사용하므로 이 메서드는 필요하지 않습니다.We don’t need it now that we’re using the new ostringstream. freeze 호출을 삭제할 수 있습니다.We can delete the call to freeze.

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

인접한 줄에서 다음 두 오류가 발생했습니다.The next two errors occurred on adjacent lines. 첫 번째 오류는 문자열에 null 종결자를 추가하는 이전 iostream 라이브러리의 IO 조작자인 끝 사용에 대한 것입니다.The first complains about using ends, which is the old iostream library’s IO manipulator that adds a null terminator to a string. 두 번째 오류는 str 메서드의 출력을 const 이외의 포인터에 할당할 수 없음을 설명합니다.The second of these errors explains that the output of the str method can’t be assigned to a non-const pointer.

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

새 스트림 라이브러리를 사용할 경우 문자열이 항상 null로 종료되기 때문에 끝이 필요하지 않으므로 해당 줄을 제거할 수 있습니다.Using the new stream library, ends is not needed since the string is always null-terminated, so that line can be removed. 두 번째 문제의 경우 이제 str()이 문자열의 문자 배열에 대한 포인터를 반환하지 않는 것입니다. std::string 형식을 반환합니다.For the second issue, the problem is that now str() doesn’t return a pointer to the character array for a string; it returns the std::string type. 두 번째 문제에 대한 솔루션은 형식을 LPCSTR로 변경하고 c_str() 메서드를 사용하여 포인터를 요청하는 것입니다.The solution to the second is to change the type to LPCSTR and use the c_str() method to request the pointer.

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

오랫동안 해결되지 않은 오류가 이 코드에서 발생했습니다.An error that puzzled us for a while occurred on this code.

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

MOUT 매크로는 mstream 형식의 개체인 *g_pmout으로 확인됩니다.The macro MOUT resolves to *g_pmout which is an object of type mstream. mstream 클래스는 표준 출력 문자열 클래스인 std::basic_ostream<TCHAR>.에서 파생됩니다. 그러나 유니코드로 변환하기 위해 삽입한 문자열 리터럴 앞뒤의 _T로 인해 << 연산자에 대한 오버로드 확인이 실패하고 다음 오류 메시지가 표시됩니다.The mstream class is derived from the standard output string class, std::basic_ostream<TCHAR>. However with _T around the string literal, which we put in in preparation for converting to Unicode, the overload resolution for operator << fails with the following error message:

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

연산자 << 정의가 너무 많아서 이러한 종류의 오류는 위협적일 수 있습니다.There are so many operator << definitions that this kind of error can be intimidating. 사용 가능한 오버로드를 자세히 살펴보면 대부분 관련이 없는 것을 확인할 수 있으며, mstream 클래스 정의를 자세히 살펴본 후 이 경우에 호출되어야 한다고 생각하는 다음 함수를 식별했습니다.After looking more closely at the available overloads, we can see that most of them are irrelevant, and looking more closely at the mstream class definition, we identified the following function that we think should be called in this case.

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

이 함수가 호출되지 않은 것은 긴 오류 메시지의 마지막 줄에서 볼 수 있듯이 문자열 리터럴이 const wchar_t[10] 형식이어서 const 이외의 포인터로 자동 변환되지 않았기 때문입니다.The reason it isn't called is because the string literal has the type const wchar_t[10] as you can see from the last line of that long error message, so the conversion to a non-const pointer is not automatic. 그러나 해당 연산자는 입력 매개 변수를 수정하면 안 되므로 더 적절한 매개 변수 형식은 LPTSTR(MBCS로 컴파일하는 경우 char* 및 유니코드로 컴파일하는 경우 wchar_t*)이 아니라 LPCTSTR(MBCS로 컴파일하는 경우 const char* 및 유니코드로 컴파일하는 경우 const wchar_t*)입니다.However that operator should not modify the input parameter, so the more appropriate parameter type is LPCTSTR (const char* when compiling as MBCS, and const wchar_t* as Unicode), not LPTSTR (char* when compiling as MBCS, and wchar_t* as Unicode). 이렇게 변경하면 이 오류가 수정됩니다.Making that change fixes this error.

이러한 형식의 변환은 덜 엄격한 이전 컴파일러에서 허용되었지만 최근의 규칙 변경에 따라 보다 올바른 코드가 필요합니다.This type of conversion was allowed under the older, less strict compiler, but more recent conformance changes require more correct code.

8단계.Step 8. 컴파일러의 보다 엄격한 변환The compiler's more strict conversions

다음과 같은 많은 오류도 발생합니다.We also get many errors like the following:

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

단순히 매크로인 메시지 맵에서 오류가 발생합니다.The error occurs in a message map that is simply a macro:

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

이 매크로의 정의로 이동하면 OnNcHitTest 함수를 참조하는 것을 확인할 수 있습니다.Going to the definition of this macro, we see it references the function 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)) },  

문제는 멤버 함수 형식에 대한 포인터의 불일치와 관련이 있습니다.The problem has to do with the mismatch in the pointer to member function types. 클래스 형식인 CHotLinkCtrl에서 클래스 형식인 CWnd로의 변환은 유효한 파생 형식-기본 형식 변환이기 때문에 문제가 아닙니다.The problem isn’t the conversion from CHotLinkCtrl as a class type to CWnd as the class type, since that is a valid derived-to-base conversion. 문제는 반환 형식 UINT 및 LRESULT입니다.The problem is the return type: UINT vs. LRESULT. LRESULT는 대상 이진 형식에 따라 64비트 포인터 또는 32비트 포인터인 LONG_PTR로 확인되므로 UINT는 이 형식으로 변환되지 않습니다.LRESULT resolves to LONG_PTR which is a 64-bit pointer or a 32-bit pointer, depending on the target binary type, so UINT does not convert to this type. 이 문제는 Visual Studio 2005에서 64비트 호환성 변경의 일부로 많은 메시지 맵 메서드의 반환 형식이 UINT에서 LRESULT로 변경되었기 때문에 2005 이전에 작성된 코드를 업그레이드하는 경우에 자주 발생합니다.This is not uncommon when upgrading code written before 2005 since the return type of many message map methods changed from UINT to LRESULT in Visual Studio 2005 as part of the 64-bit compatibility changes. 다음 코드에서 반환 형식을 UINT에서 LRESULT로 변경합니다.We change the return type from UINT in the following code to LRESULT:

afx_msg UINT OnNcHitTest(CPoint point);  

변경한 후의 코드는 다음과 같습니다.After the change we have the following code:

afx_msg LRESULT OnNcHitTest(CPoint point);  

CWnd에서 파생된 서로 다른 클래스에 이 함수가 모두 10개 정도 있기 때문에 편집기에서 해당 함수에 커서가 있을 때 정의로 이동(키보드: F12) 및 선언으로 이동(키보드: Ctrl+F12)을 사용하여 찾은 다음 기호 찾기 도구 창에서 함수로 이동하면 도움이 됩니다.Since there are about ten occurrences of this function all in different classes derived from CWnd, it’s helpful to use Go to Definition (Keyboard: F12) and Go to Declaration (Keyboard: Ctrl+F12) when the cursor is on the function in the editor to locate these and navigate to them from the Find Symbol tool window. 일반적으로 정의로 이동이 둘 중에서 더 유용합니다.Go to Definition is usually the more useful of the two. 선언으로 이동은 friend 클래스 선언이나 정방향 참조와 같은 정의하는 클래스 선언 이외의 선언을 찾습니다.Go to Declaration will find declarations other than the defining class declaration, such as friend class declarations or forward references.

9단계.Step 9. MFC 변경MFC Changes

그다음 오류도 변경된 선언 형식과 관련이 있으며 매크로에서도 발생합니다.The next error also relates to a changed declaration type and also occurs in a macro.

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

문제는 CWnd::OnActivateApp의 두 번째 매개 변수가 HTASK에서 DWORD로 변경된 것입니다.The issue is that the second parameter of CWnd::OnActivateApp changed from HTASK to DWORD. 이 변경은 Visual Studio 2002 릴리스, Visual Studio .NET에서 발생했습니다.This change occurred in the 2002 release of Visual Studio, Visual Studio .NET.

afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);  

파생 클래스에서 OnActivateApp의 선언을 다음과 같이 적절하게 업데이트해야 합니다.We have to update the declarations of OnActivateApp in derived classes accordingly as follows:

afx_msg void OnActivateApp(BOOL bActive, DWORD dwThreadId);  

이 시점에서 프로젝트를 컴파일할 수 있습니다.At this point, we are able to compile the project. 그러나 작업할 몇 가지 경고가 있으며, MBCS에서 유니코드로 변환 또는 보안 CRT 함수를 사용한 보안 향상 등 업그레이드의 선택적 부분이 있습니다.There are a few warnings to work through, however, and there are optional parts of the upgrade, such as converting from MBCS to Unicode or improving security by using the Secure CRT functions.

10단계.Step 10. 컴파일러 경고 처리Addressing compiler warnings

경고의 전체 목록을 가져오려면 현재 컴파일의 경고 보고서만 가져오기 때문에 일반 빌드 대신 솔루션에 대해 모두 다시 빌드를 수행하여 이전에 컴파일된 모든 항목이 다시 컴파일되도록 해야 합니다.To get a full list of warnings, you should do a Rebuild All on the solution rather than an ordinary build, just to make sure that everything that previously compiled will be recompiled, since you only get warning reports from the current compilation. 다른 질문은 현재 경고 수준을 수락할지 또는 더 높은 경고 수준을 사용할지 여부입니다.The other question is whether to accept the current warning level or use a higher warning level. 많은 코드, 특히 이전 코드를 포팅하는 경우 더 높은 경고 수준을 사용하는 것이 적합할 수 있습니다.When porting a lot of code, especially old code, using a higher warning level might be appropriate. 기본 경고 수준으로 시작한 후 경고 수준을 높여 모든 경고를 가져올 수도 있습니다.You might also want to start with the default warning level and then increase the warning level to get all warnings. /Wall을 사용하는 경우 시스템 헤더 파일의 일부 경고를 가져오므로 대체로 /W4를 사용하여 시스템 헤더에 대한 경고를 가져오지 않고 코드에 대한 경고를 최대한 가져옵니다.If you use /Wall, you get some warnings in the system header files, so many people use /W4 to get the most warnings on their code without getting warnings for system headers. 경고를 오류로 표시하려는 경우 /WX 옵션을 추가합니다.If you want warnings to show up as errors, add the /WX option. 이러한 설정은 프로젝트 속성 대화 상자의 C/C++ 섹션에 있습니다.These settings are in the C/C++ section of the Project Properties dialog box.

CSpyApp 클래스의 메서드 중 하나는 더 이상 지원되지 않는 함수에 대한 경고를 생성합니다.One of the methods in the CSpyApp class produces a warning about a function that is no longer supported.

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

경고는 다음과 같습니다.The warning is as follows.

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

WM_CTLCOLORDLG 메시지는 Spy++ 코드에서 이미 처리되었으므로 더 이상 필요하지 않은 SetDialogBkColor에 대한 참조만 삭제하면 됩니다.The message WM_CTLCOLORDLG was already handled in Spy++ code, so the only change required was to delete any references to SetDialogBkColor, which is no longer needed.

그다음 경고는 변수 이름을 주석으로 처리하여 수정했습니다.The next warning was straightforward to fix by commenting out the variable name. 다음 경고가 표시되었습니다.We received the following warning:

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

이 경고를 생성하는 코드는 매크로를 포함합니다.The code that produces this involves a macro.

DECODEPARM(CB_GETLBTEXT)  
{  
  P2WPOUT();  

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

    INDENT();  

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

이 코드와 같이 매크로를 많이 사용하면 코드를 유지 관리하기 어려워집니다.Heavy use of macros as in this code tends to make code harder to maintain. 이 경우 매크로가 변수 선언을 포함합니다.In this case, the macros include the declarations of the variables. PARM 매크로는 다음과 같이 정의됩니다.The macro PARM is defined as follows:

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

따라서 lpszBuffer 변수가 동일한 함수에서 두 번 선언됩니다.Therefore the lpszBuffer variable gets declared twice in the same function. 이 문제는 코드에서 매크로를 사용하지 않는 경우와 같이(두 번째 형식 선언 제거) 간단하게 수정할 수 없습니다.It's not that straightfoward to fix this as it would be if the code were not using macros (simply remove the second type declaration). 매크로 코드를 일반 코드로 다시 작성할지(시간이 걸리고 오류가 발생할 가능성이 있는 작업), 또는 경고를 사용하지 않도록 설정할지 결정해야 합니다.As it is, we have the unfortunate choice of having to decide whether to rewrite the macro code as ordinary code (a tedious and possibly error-prone task) or disable the warning.

이 경우 경고를 사용하지 않도록 설정합니다.In this case, we opt to disable the warning. 다음과 같이 pragma를 추가하면 됩니다.We can do that by adding a pragma as follows:

#pragma warning(disable : 4456)  

경고를 사용하지 않도록 설정할 때 유용한 정보를 제공할 수 있는 경고가 표시되도록 경고를 생성하는 코드에만 설정이 적용되도록 제한하는 것이 좋습니다.When disabling a warning, you might want to restrict the disabling effect to just the code you that produces the warning, to avoid suppressing the warning when it might provide useful information. 경고를 생성하는 줄 바로 뒤에 경고를 복원하는 코드를 추가하거나, 이 경고가 매크로에서 발생하므로 매크로에서 작동하는 __pragma 키워드를 사용합니다(#pragma는 매크로에서 작동하지 않음).We add code to restore the warning just after the line that produces it, or better yet, since this warning occurs in a macro, use the __pragma keyword, which works in macros (#pragma does not work in macros).

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

그다음 경고는 일부 코드 수정이 필요합니다.The next warning requires some code revisions. Win32 API GetVersion(및 GetVersionEx)는 사용되지 않습니다.The Win32 API GetVersion (and GetVersionEx) is deprecated.

warning C4996: 'GetVersion': was declared deprecated  

다음 코드에서는 버전을 가져오는 방법을 보여 줍니다.The following code shows how the version is obtained.

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

이 코드 뒤에는 dwWindowsVersion 값을 검사하여 Windows 95에서 실행 중인지 여부 및 Windows NT 버전을 확인하는 많은 코드가 있습니다.This is followed by a lot of code that examines the dwWindowsVersion value to determine whether we're running on Windows 95, and which version of Windows NT. 모두 오래된 코드이므로 코드를 제거하고 해당 변수에 대한 참조를 모두 처리합니다.Since this is all outdated, we remove the code and deal with any references to those variables.

Operating system version changes in Windows 8.1 and Windows Server 2012 R2(Windows 8.1 및 Windows Server 2012 R2의 운영 체제 버전 변경 내용) 문서에서 이 상황을 설명합니다.The article Operating system version changes in Windows 8.1 and Windows Server 2012 R2 explains the situation.

CSpyApp 클래스에는 운영 체제 버전을 쿼리하는 메서드(IsWindows9x, IsWindows4x 및 IsWindows5x)가 있습니다.There are methods in the CSpyApp class that query the operating system version: IsWindows9x, IsWindows4x and IsWindows5x. 이 오래된 응용 프로그램에서 사용하는 기술과 관련해서 지원하려는 Windows 버전(Windows 7 이상)이 모두 Windows NT 5에 가깝다는 가정에서 시작하는 것이 좋습니다.A good starting point is to assume that the versions of Windows that we intend to support (Windows 7 and later) are all close to Windows NT 5 as far the technologies used by this older application is concerned. 이러한 메서드는 이전 운영 체제의 제한 사항을 처리하는 데 사용됩니다.The uses of these methods were to deal with limitations of the older operating systems. 따라서 IsWindows5x에 대해 TRUE를 반환하고 다른 값에 대해 FALSE를 반환하도록 이러한 메서드를 변경했습니다.So we changed those methods to return TRUE for IsWindows5x and FALSE for the others.

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

이렇게 하면 내부 변수가 직접 사용된 위치가 몇 개밖에 남지 않습니다.That left only a few places where the internal variables were used directly. 해당 변수를 제거했으므로 명시적으로 처리해야 하는 몇 개의 오류가 발생합니다.Since we removed those variables, we get a few errors that have to deal with explicitly.

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

메서드 호출로 바꾸거나, TRUE를 전달하고 Windows 9x에 대한 이전 특수 사례를 제거할 수 있습니다.We could replace this with a method call or simply pass TRUE and remove the old special case for Windows 9x.

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

기본 수준(3)에서 최종 경고는 비트 필드와 관련이 있습니다.The final warning at the default level (3) has to do with a bitfield.

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

이 경고를 트리거하는 코드는 다음과 같습니다.The code that triggers this is as follows.

m_bStdMouse = TRUE;  

m_bStdMouse 선언은 비트 필드임을 나타냅니다.The declaration of m_bStdMouse indicates that it is a bitfield.

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;  

이 코드는 기본 제공 bool 형식이 Visual C++에서 지원되기 전에 작성되었습니다.This code was written before the built-in bool type was supported in Visual C++. 이러한 코드에서 BOOL은 int의 typedef였습니다. Int 형식은 부호 있는 형식이고 서명된 int의 비트 표현은 첫째 비트를 부호 비트로 사용하는 것이므로 int 형식의 비트 필드는 의도한 것과 달리 0 또는 -1을 나타내는 것으로 해석될 수 있었습니다.In such code, BOOL was a typedef for int. The type int is a signed type, and the bit representation of a signed int is to use the first bit as a sign bit, so a bitfield of type int could be interpreted as representing 0 or -1, probably not what was intended.

코드에서는 비트 필드인 이유를 알 수 없습니다.You wouldn't know by looking at the code why these are bitfields. 의도가 개체 크기를 작게 유지하는 것인가요, 또는 개체의 이진 레이아웃이 사용되는 곳이 있나요?Was the intent to keep the size of the object small, or is there anywhere where the binary layout of the object is used? 비트 필드의 사용 이유를 찾지 못했기 때문에 일반 BOOL 멤버로 변경했습니다.We changed these to ordinary BOOL members since we didn't see any reason for the use of a bitfield. 비트 필드를 사용하여 개체 크기를 작게 유지하는 것은 효과가 보장되지 않습니다.Using bitfields to keep an object's size small isn't guaranteed to work. 컴파일러가 형식을 레이아웃하는 방식에 따라 달라집니다.It depends on how the compiler lays out the type.

전체에서 표준 bool 형식을 사용하는 것이 유용한지 궁금할 수도 있습니다.You might wonder if using the standard type bool throughout would be helpful. BOOL 형식과 같은 이전 코드 패턴은 대부분 표준 C++에서 해결된 문제를 해결하기 위한 것이므로 BOOL에서 bool 기본 제공 형식으로 변경하는 것은 새 버전에서 코드를 처음 실행한 후 고려할 변경 내용 중 한 가지 예일 뿐입니다.Many of the old code patterns such as the BOOL type were invented to solve problems that were later solved in standard C++, so changing from BOOL to the bool built-in type is just one example of such a change that you consider doing after you get your code initially running in the new version.

기본 수준(수준 3)에서 표시되는 모든 경고를 처리한 후 몇 가지 추가 경고를 catch하기 위해 수준 4로 변경했습니다.Once we've dealt with all the warnings that appear at the default level (level 3) we changed to level 4 to catch a few additional warnings. 처음 표시되는 경고는 다음과 같습니다.The first to appear was as follows:

warning C4100: 'nTab': unreferenced formal parameter  

이 경고를 생성하는 코드는 다음과 같습니다.The code that produced this warning was as follows.

virtual void OnSelectTab(int nTab) {};  

무해한 것처럼 보이지만 /W4 및 /WX 집합에서 오류 없는 컴파일을 원했기 때문에 변수 이름을 주석으로 처리하고 읽기 쉽도록 그대로 두었습니다.This seems harmless enough, but since we wanted a clean compilation with /W4 and /WX set, we simply commented out the variable name, leaving it for the sake of readability.

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

표시된 다른 경고는 일반적인 코드 정리에 유용했습니다.Other warnings we received were useful for general code cleanup. int 또는 unsigned int에서 WORD(unsigned short의 typedef)로의 암시적 변환이 많습니다.There are a number of implicit conversions from int or unsigned int to WORD (which is a typedef for unsigned short). 이러한 변환으로 인해 데이터가 손실될 수 있습니다.These involve a possible loss of data. 이 경우 WORD로 캐스트를 추가했습니다.We added a cast to WORD in these cases.

이 코드에 대해 표시된 다른 수준 4 경고는 다음과 같습니다.Another level 4 warning we got for this code was:

warning C4211: nonstandard extension used: redefined extern to static  

변수가 처음에 extern으로 선언된 후 나중에 static으로 선언된 경우 문제가 발생합니다.The problem occurs when a variable was first declared extern, then later declared static. 이러한 두 저장소 클래스 지정자의 의미는 양립할 수 없지만 Microsoft 확장으로 허용됩니다.The meaning of these two storage class specifiers is mutually exclusive, but this is allowed as a Microsoft extension. 코드를 다른 컴파일러로 포팅할 수 있게 하거나 /Za(ANSI 호환성)를 사용하여 컴파일하려는 경우 일치하는 저장소 클래스 지정자를 포함하도록 선언을 변경합니다.If you wanted the code to be portable to other compilers, or you wanted to compile it with /Za (ANSI compatibility), you would change the declarations to have matching storage class specifiers.

11단계.Step 11. MBCS에서 유니코드로 포팅Porting from MBCS to Unicode

Windows 환경에서 유니코드를 말할 때는 일반적으로 UTF-16을 의미합니다.Note that in the Windows world, when we say Unicode, we usually mean UTF-16. Linux와 같은 다른 운영 체제는 UTF-8을 사용하지만 Windows는 일반적으로 사용하지 않습니다.Other operating systems such as Linux use UTF-8, but Windows generally does not. 실제로 MBCS 코드를 UTF-16 유니코드로 포팅하는 단계를 수행하기 전에 다른 작업을 수행하거나 편리한 시간까지 포팅을 연기하기 위해 MBCS가 사용되지 않는다는 경고를 일시적으로 제거하는 것이 좋습니다.Before taking the step to actually port MBCS code to UTF-16 Unicode, we might want to temporarily eliminate the warnings that MBCS is deprecated, in order to do other work or postpone the porting until a convenient time. 현재 코드는 MBCS를 사용하며, 계속 MBCS를 사용하려면 MBCS 버전의 MFC를 다운로드해야 합니다.The current code uses MBCS and to continue with that we need to download the MBCS version of MFC. 다소 큰 이 라이브러리는 기본 Visual Studio 설치에서 제거되었으므로 별도로 다운로드해야 합니다.This rather large library was removed from the default Visual Studio installation, so it must be downloaded separately. MFC MBCS DLL 추가 기능을 참조하세요.See MFC MBCS DLL Add-on. 다운로드하고 Visual Studio를 다시 시작한 후 MBCS 버전의 MFC를 사용하여 컴파일 및 연결할 수 있지만, MBCS에 대한 경고를 제거하려면 프로젝트 속성의 전처리기 섹션에 있는 미리 정의된 매크로 목록이나 stdafx.h 헤더 파일 또는 기타 공용 헤더 파일의 시작 부분에 NO_WARN_MBCS_MFC_DEPRECATION도 추가해야 합니다.Once you download this and restart Visual Studio, you can compile and link with the MBCS version of MFC, but to get rid of the warnings about MBCS, you should also add NO_WARN_MBCS_MFC_DEPRECATION to your list of predefined macros in the Preprocessor section of project properties, or at the beginning of your stdafx.h header file or other common header file.

이제 일부 링커 오류가 있습니다.We now have some linker errors.

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

mfc의 오래된 정적 라이브러리 버전이 링커 입력에 포함되어 있으므로 LNK1181이 발생합니다.LNK1181 occurs because an outdated static library version of mfc is included on the linker input. MFC를 동적으로 연결할 수 있기 때문에 이 버전은 더 이상 필요하지 않으므로 프로젝트 속성의 링커 섹션에 있는 입력 속성에서 MFC 정적 라이브러리를 모두 제거해야 합니다.This isn’t required anymore since we can link MFC dynamically, so we just need to remove all MFC static libraries from the Input property in the Linker section of the project properties. 또한 이 프로젝트는 /NODEFAULTLIB 옵션을 사용하며, 대신 모든 라이브러리 종속성을 나열합니다.This project is also using the /NODEFAULTLIB option, and instead it lists all the library dependencies.

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

이제 실제로 이전 MBCS(멀티바이트 문자 집합) 코드를 유니코드로 업데이트하겠습니다.Now let us actually update the old Multi-byte Character Set (MBCS) code to Unicode. Windows 데스크톱 플랫폼에 깊이 연결된 Windows 응용 프로그램이므로 Windows에서 사용하는 UTF-16 유니코드로 포팅하겠습니다.Since this is a Windows application, intimately tied to the Windows desktop platform, we will port it to UTF-16 Unicode that Windows uses. 플랫폼 간 코드를 작성하거나 Windows 응용 프로그램을 다른 플랫폼으로 포팅하는 경우 다른 운영 체제에서 널리 사용되는 UTF-8로 포팅하는 것이 좋습니다.If you are writing cross-platform code or porting a Windows application to another platform, you might want to consider porting to UTF-8, which is widely used on other operating systems.

UTF-16 유니코드로 포팅하는 경우 MBCS로 컴파일하는 옵션을 원하는지 여부를 결정해야 합니다.Porting to UTF-16 Unicode, we must decide whether we still want the option to compile to MBCS or not. MBCS를 지원하는 옵션을 포함하려는 경우 컴파일하는 동안 _MBCS 또는 _UNICODE가 정의되었는지에 따라 char 또는 wchar_t로 확인되는 TCHAR 매크로를 문자 형식으로 사용해야 합니다.If we want to have the option to support MBCS, we should use the TCHAR macro as the character type, which resolves to either char or wchar_t, depending on whether _MBCS or _UNICODE is defined during compilation. wchar_t 및 관련된 API 대신 TCHAR 및 TCHAR 버전의 다양한 API로 전환하면 간단히 _UNICODE 대신 _MBCS 매크로를 정의하여 코드의 MBCS 버전으로 돌아갈 수 있습니다.Switching to TCHAR and the TCHAR versions of various APIs instead of wchar_t and its associated APIs means that you can get back to an MBCS version of your code simply by defining _MBCS macro instead of _UNICODE. TCHAR 외에도 널리 사용되는 typedef, 매크로 및 함수의 다양한 TCHAR 버전이 있습니다.In addition to TCHAR, a variety of TCHAR versions of such as widely used typedefs, macros, and functions exists. 예를 들어 LPCSTR 대신 LPCTSTR을 사용합니다.For example, LPCTSTR instead of LPCSTR, and so on. 프로젝트 속성 대화 상자의 구성 속성 아래, 일반 섹션에서 문자 집합 속성을 MBCS 문자 집합 사용에서 유니코드 문자 집합 사용으로 변경합니다.In the project properties dialog, under Configuration Properties, in the General section, change the Character Set property from Use MBCS Character Set to Use Unicode Character Set. 이 설정은 컴파일하는 동안 미리 정의되는 매크로에 영향을 줍니다.This setting affects which macro is predefined during compilation. UNICODE 매크로와 _UNICODE 매크로가 둘 다 있습니다.There is both a UNICODE macro and a _UNICODE macro. 프로젝트 속성은 두 매크로에 일관되게 적용됩니다.The project property affects both consistently. Windows 헤더는 unicode를 사용하고 MFC와 같은 Visual C++ 헤더는 _UNICODE를 사용하지만 하나가 정의될 때 다른 하나도 항상 정의됩니다.Windows headers use UNICODE where Visual C++ headers such as MFC use _UNICODE, but when one is defined, the other is always defined.

TCHAR를 사용하여 MBCS에서 UTF-16 유니코드로 포팅하는 방법에 대한 유용한 가이드가 있습니다.A good guide to porting from MBCS to UTF-16 Unicode using TCHAR exists. 이 경로를 선택합니다.We choose this route. 먼저, 문자 집합 속성을 유니코드 문자 집합 사용으로 변경하고 프로젝트를 다시 빌드합니다.First, we change the Character Set property to Use Unicode Character Set and rebuild the project.

코드에는 궁극적으로 유니코드를 지원하기 위해 이미 TCHAR를 사용 중인 부분도 있고Some places in the code were already using TCHAR, apparently in anticipation of eventually supporting Unicode. 그렇지 않은 부분도 있습니다.Some were not. char의 typedef인 CHAR 인스턴스를 검색하고 대부분 TCHAR로 바꾸었습니다.We searched for instances of CHAR, which is a typedef for char, and replaced most of them with TCHAR. 또한 sizeof (CHAR)를 찾았습니다.Also, we looked for sizeof (CHAR). CHAR에서 TCHAR로 변경할 때마다 일반적으로 문자열의 문자 수를 확인하는 데 주로 사용되는 sizeof(TCHAR)로 변경해야 했습니다.Whenever we changed from CHAR to TCHAR, we usually had to change to sizeof(TCHAR) since this was often used to determine the number of characters in a string. 여기서 잘못된 형식을 사용해도 컴파일러 오류가 생성되지 않으므로 이 경우에 약간 주의할 가치가 있습니다.Using the wrong type here does not produce a compiler error, so it's worth paying a bit of attention to this case.

이 형식의 오류는 유니코드로 전환한 직후에 일반적으로 발생합니다.This type of error is very common just after switching to Unicode.

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

다음은 이 오류를 생성하는 코드의 예입니다.Here’s an example of code that produces this:

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

오류를 제거하기 위해 문자열 앞뒤에 _T를 넣습니다.We put _T around the string literal to remove the error.

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

_T 매크로는 문자열 리터럴이 MBCS 또는 UNICODE 설정에 따라 char 문자열이나 wchar_t 문자열로 컴파일되게 하는 효과가 있습니다.The _T macro has the effect of making a string literal compile as a char string or a wchar_t string, depending on the setting of MBCS or UNICODE. Visual Studio에서 모든 문자열을 _T로 바꾸려면 먼저 빠른 바꾸기(키보드: Ctrl+F) 상자 또는 파일에서 바꾸기(키보드: Ctrl+Shift+H)를 열고 정규식 사용 확인란을 선택합니다.To replace all strings with _T in Visual Studio, first open the Quick Replace (Keyboard: Ctrl+F) box or the Replace In Files (Keyboard: Ctrl+Shift+H), then choose the Use Regular Expressions checkbox. ((\".*?\")|('.+?'))를 검색 텍스트로 입력하고 _T($1)를 바꿀 텍스트로 입력합니다.Enter ((\".*?\")|('.+?')) as the search text and _T($1) as the replacement text. _T 매크로가 일부 문자열 앞뒤에 이미 있는 경우 이 절차에서 다시 추가하며, #include를 사용하는 경우와 같이 _T를 원하지 않는 경우도 있으므로 모두 바꾸기 대신 다음 찾기를 사용하는 것이 가장 좋습니다.If you already have the _T macro around some strings, this procedure will add it again, and it might also find cases where you don't want _T, such as when you use #include, so it's best to use Replace Next rather than Replace All.

이 특정 함수 wsprintf는 실제로 Windows 헤더에서 정의되며, 해당 설명서에서 가능한 버퍼 오버런으로 인해 사용하지 않도록 권장합니다.This particular function, wsprintf, is actually defined in the Windows headers, and the documentation for it recommends that it not be used, due to possible buffer overrun. szTmp 버퍼에 대한 크기가 지정되지 않으므로 함수에서 버퍼가 기록되는 모든 데이터를 포함할 수 있는지 확인할 방법이 없습니다.No size is given for the szTmp buffer, so there is no way for the function to check that the buffer can hold all the data to be written to it. 보안 CRT로 포팅하는 방법에 대한 다음 섹션을 참조하세요. 여기서는 다른 유사한 문제를 해결합니다.See the next section about porting to the Secure CRT, in which we fix other similar problems. 결국 _stprintf_s로 바꾸었습니다.We ended up replacing it with _stprintf_s.

유니코드로 변환할 때 표시되는 다른 일반적인 오류는 다음과 같습니다.Another common error you’ll see in converting to Unicode is this.

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

이 오류를 생성하는 코드는 다음과 같습니다.The code that produces it is as follows:

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

문자열을 복사하기 위한 TCHAR strcpy 함수인 _tcscpy 함수가 사용되었지만 할당된 버퍼는 char 버퍼였습니다.Even though the _tcscpy function was used, which is the TCHAR strcpy function for copying a string, the buffer that was allocated was a char buffer. 이 코드는 TCHAR로 쉽게 변경됩니다.This is easily changed to TCHAR.

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

마찬가지로, 컴파일러 오류가 발생할 경우 LPSTR(긴 문자열 포인터) 및 LPCSTR(긴 상수 문자열 포인터)를 LPTSTR(긴 TCHAR 문자열 포인터) 및 LPCTSTR(긴 상수 TCHAR 문자열 포인터)로 각각 변경했습니다.Similarly, we changed LPSTR (Long Pointer to STRing) and LPCSTR (Long Pointer to Constant STRing) to LPTSTR (Long Pointer to TCHAR STRing) and LPCTSTR (Long Pointer to Constant TCHAR STRing) respectively, when warranted by a compiler error. 각 상황을 개별적으로 검사해야 했으므로 전체 검색 및 바꾸기를 사용하여 이러한 바꾸기를 수행하지는 않았습니다.We chose not to make such replacements by using global search and replace, because each situation had to be examined individually. A 접미사가 있는 Windows 구조를 사용하는 특정 Windows 메시지를 처리하는 경우와 같이 char 버전이 필요한 경우도 있습니다.In some cases, the char version is wanted, such as when processing certain Windows messages which use Windows structures that have the A suffix. Windows API에서 접미사 A는 ASCII 또는 ANSI를 의미하고(MBCS에도 적용됨), 접미사 W는 와이드 문자 또는 UTF-16 유니코드를 의미합니다.In the Windows API, the suffix A means ASCII or ANSI (and also applies to MBCS), and the suffix W means wide characters, or UTF-16 Unicode. 이 명명 패턴은 Windows 헤더에서 사용되지만, Spy++ 코드에서 MBCS 버전에서만 이미 정의된 함수의 유니코드 버전을 추가해야 하는 경우에도 따랐습니다.This naming pattern is used in the Windows headers, but we also followed it in the Spy++ code when we had to add a Unicode version of a function that was already defined in only an MBCS version.

올바르게 확인되는 버전을 사용하기 위해 형식을 바꾸어야 하는 경우도 있었습니다(예: WNDCLASSA 대신 WNDCLASS 사용).In some cases we had to replace a type to use a version that resolves correctly (WNDCLASS instead of WNDCLASSA for example).

대부분의 경우 GetClassName(GetClassNameA 대신)과 같은 Win32 API의 제네릭 버전(매크로)을 사용해야 했습니다.In many cases we had to use the generic version (macro) of a Win32 API like GetClassName (instead of GetClassNameA). 메시지 처리기 switch 문에서 일부 메시지는 MBCS 또는 유니코드 특정이고, 두 경우 모두 MBCS 버전을 명시적으로 호출하도록 코드를 변경해야 했습니다. 일반적으로 명명된 함수를 A 및 W 특정 함수로 바꾸고 UNICODE 정의 여부에 따라 올바른 A 또는 W 이름으로 확인되는 제네릭 이름에 대한 매크로를 추가했기 때문입니다.In message handler switch statement, some messages are MBCS or Unicode specific, in those cases, we had to change the code to explicitly call the MBCS version, because we replaced the generically named functions with A and W specific functions, and added a macro for the generic name that resolves to the correct A or W name based on whether UNICODE is defined. 코드의 많은 부분에서 _UNICODE를 정의하도록 전환할 때 A가 필요한 경우에도 이제 W 버전이 선택됩니다.In many parts of the code, when we switched to define _UNICODE, the W version is now chosen even when the A version is what's wanted.

특별한 조치가 필요한 몇 개의 위치가 있습니다.There are a few places where special actions had to be taken. WideCharToMultiByte 또는 MultiByteToWideChar를 사용하려면 자세히 살펴봐야 할 수 있습니다.Any use of WideCharToMultiByte or MultiByteToWideChar might require a closer look. 다음은 WideCharToMultiByte가 사용된 한 가지 예입니다.Here's one example where WideCharToMultiByte was being used.

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

이 작업을 처리하려면 작업이 수행된 이유가 CString의 내부 버퍼, strFace에 글꼴 이름을 나타내는 와이드 문자열을 복사하기 위한 것임을 이해해야 했습니다.To address this, we had to understand that the reason this was done was to copy a wide character string representing the name of a font into the internal buffer of a CString, strFace. 이 예제는 와이드 문자 CString 문자열과 마찬가지로 멀티바이트 CString 문자열을 위한 약간 다른 코드가 필요했으므로 이 경우 #ifdef를 추가했습니다.This required slightly different code for multibyte CString strings as for wide character CString strings, so we added an #ifdef in this case.

#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  

물론, 실제로 wcscpy 대신 보다 안전한 버전인 wcscpy_s를 사용해야 합니다.Of course, instead of wcscpy we really should use wcscpy_s, the more secure version. 다음 섹션에서 이 작업을 처리합니다.The next section addresses this.

작업을 검사하기 위해 문자 집합을 멀티바이트 문자 집합 사용으로 다시 설정하고 유니코드 및 MBCS를 사용하여 코드가 컴파일되는지 확인해야 합니다.As a check on our work, we should reset the Character Set to Use Multibyte Character Set and make sure that the code still compiles using MBCS as well as Unicode. 당연히 이러한 모든 변경 후에는 다시 컴파일된 앱에서 전체 테스트 과정을 실행해야 합니다.Needless to say, a full test pass should be executed on the recompiled app after all these changes.

이 Spy++ 솔루션을 작업할 때 평균적인 C++ 개발자가 코드를 유니코드로 변환하는 데 약 2일이 걸렸습니다.In our work with this Spy++ solution, it took about two working days for an average C++ developer to convert the code to Unicode. 다시 테스트하는 시간은 여기에 포함되지 않았습니다.That did not include the retesting time.

12단계.Step 12. 보안 CRT를 사용하도록 포팅Porting to use the Secure CRT

보안 버전(_s 접미사가 있는 버전)의 CRT 함수를 사용하도록 코드를 포팅하는 것이 다음 작업입니다.Porting the code to use the secure versions (the versions with the _s suffix) of CRT functions is next. 이 경우 일반적인 전략은 함수를 _s 버전으로 바꾼 후 일반적으로 필요한 추가 버퍼 크기 매개 변수를 추가하는 것입니다.In this case, the general strategy is to replace the function with the _s version and then, usually, add the required additional buffer size parameters. 대부분의 경우 크기가 알려져 있으므로 이 작업은 간단합니다.In many cases this is straightforward since the size is known. 크기가 즉시 제공되지 않는 경우에는 CRT 함수를 사용하는 함수에 매개 변수를 더 추가하거나 대상 버퍼의 사용을 검사하고 적절한 크기 제한을 확인해야 합니다.In other cases, where the size is not immediately available, it’s necessary to add additional parameters to the function that’s using the CRT function, or perhaps examine the usage of the destination buffer and see what the appropriate size limits are.

Visual C++에서는 크기 매개 변수를 많이 추가하지 않고 쉽게 코드 보안을 설정하는 트릭을 제공하며, 템플릿 오버로드를 사용하여 수행됩니다.Visual C++ provides a trick to make it easier to get code secure without adding as many size parameters, and that is by using the template overloads. 이러한 오버로드는 템플릿이므로 C가 아니라 C++로 컴파일하는 경우에만 사용할 수 있습니다. Spyxxhk는 C 프로젝트이므로 트릭이 작동하지 않습니다.Since these overloads are templates, they are only available when compiling as C++, not as C. Spyxxhk is a C project, so the trick won't work for that. 그러나 Spyxx는 C 프로젝트가 아니므로 트릭을 사용할 수 있습니다.However, Spyxx is not and we can use the trick. 트릭은 stdafx.h와 같은 프로젝트의 각 파일에서 컴파일되는 위치에 다음과 같은 줄을 추가하는 것입니다.The trick is to add a line like this in a place where it will be compiled in every file of the project, such as in stdafx.h:

#define _CRT_SECURE_TEMPLATE_OVERLOADS 1  

이렇게 정의하면 버퍼가 배열일 때마다 원시 포인터 대신 해당 크기가 배열 형식에서 유추되고, 사용자가 제공하지 않아도 크기 매개 변수로 사용됩니다.When you define that, whenever the buffer is an array, rather than a raw pointer, its size is inferred from the array type and that is used as the size parameter, without you having to supply it. 이는 코드를 다시 작성하는 복잡성을 줄이는 데 도움이 됩니다.That helps to cut down the complexity of rewriting the code. 함수 이름을 _s 버전으로 바꾸어야 하지만 검색 및 바꾸기 작업으로 수행할 수 있는 경우가 많습니다.You still have to replace the function name with the _s version, but that can often be done by a search and replace operation.

일부 함수의 반환 값이 변경됩니다.The return values of some functions changed. 예를 들어 _itoa_s(및 _itow_s와 _itot_s 매크로)는 문자열 대신 오류 코드(errno_t)를 반환합니다.For example, _itoa_s (and _itow_s and the macro _itot_s) returns an error code (errno_t), rather than the string. 따라서 이러한 경우 _itoa_s 호출을 별도 줄로 이동하고 버퍼의 식별자로 바꾸어야 합니다.So in those cases, you have to move the call to _itoa_s onto a separate line and replace it with the buffer's identifier.

몇 가지 일반적인 사례: memcpy의 경우 memcpy_s로 전환하면 복사되는 구조의 크기가 추가되는 경우가 많습니다.Some of the common cases: for memcpy, when switching to memcpy_s, we frequently added the size of the structure being copied to. 마찬가지로, 대부분의 문자열과 버퍼의 경우 배열 또는 버퍼의 크기가 버퍼 선언에서 또는 버퍼가 원래 할당된 위치를 찾아 쉽게 확인됩니다.Similarly, for most strings and buffers, the size of the array or buffer is easily determined from the declaration of the buffer or by finding where the buffer was originally allocated. 일부 경우에서는 실제로 사용할 수 있는 버퍼 크기를 확인해야 하며, 수정 중인 함수 범위에서 해당 정보를 사용할 수 없는 경우 추가 매개 변수로 추가해야 하고 정보를 제공하도록 호출 코드를 수정해야 합니다.For some situations, you need to determine how big of a buffer is actually available, and if that information is not available in the scope of the function that you’re modifying, it should be added as an additional parameter and the calling code should be modified to provide the information.

이러한 기술을 통해 안전한 CRT 함수를 사용하도록 코드를 변환하는 데 약 반나절이 걸렸습니다.With these techniques, it took about half a day to convert the code to use the secure CRT functions. 템플릿 오버로드를 선택하지 않고 크기 매개 변수를 수동으로 추가하는 경우 두세 배의 시간이 걸릴 것입니다.If you choose not to the template overloads and to add the size parameters manually, it would probably take twice or three times more time.

13단계.Step 13. /Zc:forScope가 사용되지 않음/Zc:forScope- is deprecated

Visual C++ 6.0 이후 컴파일러는 루프에서 선언된 변수의 범위를 루프 범위로 제한하는 현재 표준을 준수합니다.Since Visual C++ 6.0, the compiler conforms to the current standard, which limits the scope of variables declared in a loop to the scope of the loop. 컴파일러 옵션 /Zc:forScope(프로젝트 속성의 루프 범위 강제 규칙)는 이를 오류로 보고할지 여부를 제어합니다.The compiler option /Zc:forScope (Force Conformance for Loop Scope in the project properties) controls whether or not this is reported as an error. 부합되도록 코드를 업데이트하고 루프 바깥쪽에 선언을 추가해야 합니다.We should update our code to be conformant, and add declarations just outside the loop. 코드 변경을 방지하기 위해 C++ 프로젝트 속성의 언어 섹션에서 해당 설정을 아니요(/Zc:forScope-)로 변경할 수 있습니다.To avoid making the code changes, you can change that setting in the Language section of the C++ project properties to No (/Zc:forScope-). 그러나 Visual C++의 이후 릴리스에서 /Zc:forScope-가 제거될 수도 있으므로 결국 표준에 맞게 코드를 변경해야 합니다.However, keep in mind that /Zc:forScope- might be removed in a future release of Visual C++, so eventually your code will need to change to conform to the standard.

이러한 문제는 비교적 쉽게 해결되지만, 코드에 따라 많은 코드에 영향을 줄 수 있습니다.These issues are relatively easy to fix, but depending on your code, it might affect a lot of code. 다음은 일반적인 문제입니다.Here's a typical issue.

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

위 코드는 오류를 생성합니다.The above code produces the error:

'n': undeclared identifier  

이 오류는 컴파일러가 더 이상 C++ 표준을 준수하지 않는 코드를 허용하는 컴파일러 옵션 사용을 중단했기 때문에 발생합니다.This occurs because the compiler has deprecated a compiler option that allowed code that no longer complies with the C++ standard. 표준에서 루프 내의 변수 선언은 해당 범위가 루프로만 제한되므로 루트 바깥쪽에 루프 카운터를 사용하는 일반적인 방식의 경우에도 다음 수정된 코드와 같이 루프 카운터를 루프 바깥쪽으로 이동해야 합니다.In the standard, declaring a variable inside a loop restricts its scope to the loop only, so the common practice of using a loop counter outside of the loop requires that the declaration of the counter also be moved outside the loop, as in the following revised code:

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

요약Summary

원래 Visual C++ 6.0 코드에서 최신 컴파일러로 Spy++를 포팅하는 데 약 1주 동안 20시간의 코딩 시간이 소요되었습니다.Porting Spy++ from the original Visual C++ 6.0 code to the latest compiler took about 20 hours of coding time over the course of about a week. Visual Studio 6.0에서 Visual Studio 2015로 8개 제품 릴리스가 직접 업그레이드되었습니다.We upgraded directly through eight releases of the product from Visual Studio 6.0 to Visual Studio 2015. 이 방법은 현재 큰 프로젝트와 작은 프로젝트의 업그레이드에 모두 권장되는 방법입니다.This is now the recommended approach for all upgrades on projects large and small.

참고 항목See Also

포팅 및 업그레이드: 예제 및 사례 연구 Porting and Upgrading: Examples and Case Studies
이전 사례 연구: COM SpyPrevious case study: COM Spy