포팅 가이드: COM SpyPorting Guide: COM Spy

이 항목은 이전 Visual C++ 프로젝트를 최신 버전의 Visual Studio로 업그레이드하는 프로세스를 보여 주는 일련의 문서 중 두 번째입니다.This topic is the second in a series of articles that demonstrates the process of upgrading older Visual C++ projects to the latest version of Visual Studio. 이 항목의 예제 코드는 Visual Studio 2005를 사용하여 마지막으로 컴파일되었습니다.The example code in this topic was last compiled with Visual Studio 2005.


COMSpy는 컴퓨터에서 서비스 구성 요소의 활동을 모니터링 및 기록하는 프로그램입니다.COMSpy is a program that monitors and logs the activity of serviced components on a machine. 서비스 구성 요소는 시스템에서 실행되며 동일한 네트워크의 컴퓨터에서 사용할 수 있는 COM+ 구성 요소입니다.Serviced components are COM+ components that run on a system and can be used by computers on the same network. Windows 제어판의 구성 요소 서비스 기능으로 관리됩니다.They're managed by the Component Services functionality in the Windows Control Panel.

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

프로젝트 파일은 쉽게 변환되며 마이그레이션 보고서를 생성합니다.The project file converts easily and produces a migration report. 보고서에는 처리해야 할 수도 있는 문제를 알려주는 몇 개의 항목이 있습니다.There are a few entries in the report that let us know about issues we might need to deal with. 다음은 보고되는 문제 중 하나입니다(이 항목 전체에서 오류 메시지는 때때로 전체 경로를 제거하는 등 읽기 쉽도록 축약됨).Here's one issue that is reported (note that throughout this topic, error messages are sometimes shortened for readability, for example to remove the full paths):

ComSpyAudit\ComSpyAudit.vcproj: MSB8012: $(TargetPath) ('C:\Users\UserName\Desktop\spy\spy\ComSpyAudit\.\XP32_DEBUG\ComSpyAudit.dll') does not match the Librarian's OutputFile property value '.\XP32_DEBUG\ComSpyAudit.dll' ('C:\Users\UserName\Desktop\spy\spy\XP32_DEBUG\ComSpyAudit.dll') in project configuration 'Unicode Debug|Win32'. This may cause your project to build incorrectly. To correct this, please make sure that $(TargetPath) property value matches the value specified in %(Lib.OutputFile).  

프로젝트를 업그레이드할 때 자주 발생하는 문제 중 하나는 프로젝트 속성 대화 상자의 링커 OutputFile 설정을 검토해야 할 수도 있다는 것입니다.One of the frequent problems in upgrading projects is that the Linker OutputFile setting in the project properties dialog box might need to be reviewed. Visual Studio 2010 이전 프로젝트에서 OutputFile은 비표준 값으로 설정할 경우 자동 변환 마법사에서 문제가 발생하는 설정 중 하나입니다.For projects prior to Visual Studio 2010, the OutputFile is one setting that the automatic conversion wizard has trouble with, if it's set to a non-standard value. 이 경우 출력 파일의 경로가 비표준 폴더인 XP32_DEBUG로 설정되었습니다.In this case, the paths for the output files were set to a nonstandard folder, XP32_DEBUG. 이 오류에 대해 자세히 알아보기 위해, 중요한 변경 내용인 vcbuild에서 msbuild로의 변경을 포함하는 업그레이드인 Visual C++ 2010 프로젝트 업그레이드와 관련된 블로그 게시물을 참조했습니다.To find out more about this error, we consulted a blog post related to the Visual C++ 2010 project upgrade, which was the upgrade that involved the change from vcbuild to msbuild, a significant change. 이 정보에 따라 새 프로젝트를 만들 때 OutputFile 설정의 기본값은 $(OutDir)$(TargetName)$(TargetExt)이지만, 변환된 프로젝트에서 모두 올바른지 확인할 수 없기 때문에 변환 중에는 설정되지 않았습니다.According to this information, the default value for the Output File setting when you create a new project is $(OutDir)$(TargetName)$(TargetExt), but this isn't set during conversion since it's not possible for converted projects to verify that everything is correct. 그러나 OutputFile에 대해 이 값을 설정하고 작동하는지 살펴보겠습니다.However, let's try putting that in for OutputFile and see if it works. 작동하므로 계속 진행할 수 있습니다.It does, so we can move on. 비표준 출력 폴더를 사용할 특별한 이유가 없는 경우 표준 위치를 사용하는 것이 좋습니다.If there is no particular reason for using a nonstandard output folder, we recommend using the standard location. 이 경우 포팅 및 업그레이드 프로세스 중에 출력 위치를 비표준 폴더로 그대로 두었습니다. $(OutDir)은 디버그 구성의 XP32_DEBUG 폴더 및 릴리스 구성의 ReleaseU 폴더로 확인됩니다.In this case, we chose to leave the output location as the non-standard during the porting and upgrading process; $(OutDir) resolves to the XP32_DEBUG folder in the Debug configuration and the ReleaseU folder for the Release configuration.

2단계.Step 2. 빌드Getting it to build

포팅된 프로젝트를 빌드하면 다양한 오류와 경고가 발생합니다.Building the ported project, a number of errors and warnings occur.

다음 컴파일러 오류로 인해 ComSpyCtl이 컴파일되지 않습니다.ComSpyCtl doesn't compile though due to this compiler error:

atlcom.h(611): error C2664: 'HRESULT CComSpy::IPersistStreamInit_Save(LPSTREAM,BOOL,ATL::ATL_PROPMAP_ENTRY *)': cannot convert argument 3 from 'const ATL::ATL_PROPMAP_ENTRY *' to 'ATL::ATL_PROPMAP_ENTRY *'atlcom.h(611): note: Conversion loses qualifiersatlcom.h(608): note: while compiling class template member function 'HRESULT ATL::IPersistStreamInitImpl<CComSpy>::Save(LPSTREAM,BOOL)'\spy\spy\comspyctl\ccomspy.h(28): note: see reference to class template instantiation 'ATL::IPersistStreamInitImpl<CComSpy>' being compiled  

이 오류는 atlcom.h에서 IPersistStreamInitImpl 클래스의 Save 메서드를 참조합니다.The error references the Save method on the IPersistStreamInitImpl class in atlcom.h.

STDMETHOD(Save)(_Inout_ LPSTREAM pStm, _In_ BOOL fClearDirty)  
     T* pT = static_cast<T*>(this);  
     ATLTRACE(atlTraceCOM, 2, _T("IPersistStreamInitImpl::Save\n"));  
     return pT->IPersistStreamInit_Save(pStm, fClearDirty, T::GetPropertyMap());  

문제는 이전 버전의 컴파일러에서 허용된 변환이 더 이상 유효하지 않은 것입니다.The problem is that a conversion that an older version of the compiler accepted is no longer valid. C++ 표준을 준수하기 위해 이전에 허용된 일부 코드가 더 이상 허용되지 않습니다.In order to conform with the C++ standard, some code that previously was allowed is no longer allowed. 이 경우 const 포인터가 필요한 함수에 const가 아닌 포인터를 전달하는 것은 안전하지 않습니다.In this case, it's not safe to pass a non-const pointer to a function that expects a const pointer. 해결 방법은 CComSpy 클래스에서 IPersistStreamInit_Save 선언을 찾아 세 번째 매개 변수에 const 한정자를 추가하는 것입니다.The solution is to find the declaration of IPersistStreamInit_Save on the CComSpy class and add the const modifier to the third parameter.

HRESULT CComSpy::IPersistStreamInit_Save(LPSTREAM pStm, BOOL /* fClearDirty */, const ATL_PROPMAP_ENTRY* pMap)  

IPersistStreamInit_Load도 유사한 방식으로 변경합니다.And a similar change to IPersistStreamInit_Load.

HRESULT IPersistStreamInit_Load(LPSTREAM pStm, const ATL_PROPMAP_ENTRY* pMap);  

다음 오류는 등록을 다룹니다.The next error deals with registration.

error MSB3073: The command "regsvr32 /s /c "C:\Users\username\Desktop\spy\spy\ComSpyCtl\.\XP32_DEBUG\ComSpyCtl.lib"error MSB3073: echo regsvr32 exec. time > ".\XP32_DEBUG\regsvr32.trg"error MSB3073:error MSB3073: :VCEnd" exited with code 3.  

이 빌드 후 등록 명령은 더 이상 필요하지 않습니다.We don't need this post-build registration command anymore. 대신, 사용자 지정 빌드 명령을 제거하고 링커 설정에서 출력을 등록하도록 지정합니다.Instead, we simply remove the custom build command, and specify in the Linker settings to register the output.

경고 처리Dealing with warnings

프로젝트에서 다음과 같은 링커 경고를 생성합니다.The project produces the following linker warning.

warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/SAFESEH' specification  

/SAFESEH 컴파일러 옵션은 디버그 모드에서 유용하지 않습니다. 디버그 모드에서는 /EDITANDCONTINUE가 유용하므로 여기서 해결 방법은 디버그 구성에 대해서만 /SAFESEH를 사용하지 않도록 설정하는 것입니다.The /SAFESEH compiler option is not useful in debug mode, which is when /EDITANDCONTINUE is useful, so the fix here is to disable /SAFESEH for Debug configurations only. 속성 대화 상자에서 이 작업을 수행하려면 이 오류를 생성하는 프로젝트에 대한 속성 대화 상자를 열고 먼저 구성을 디버그(실제로는 디버그 유니코드)로 설정한 다음 링커 고급 섹션에서 이미지에 안전한 예외 처리기가 있음 속성을 아니요(/SAFESEH:NO)로 다시 설정합니다.To do this in the property dialog, we open the property dialog for the project that produces this error, and we first set the Configuration to Debug (actually Debug Unicode), and then in the Linker Advanced section, reset the Image Has Safe Exception Handlers property to No (/SAFESEH:NO).

컴파일러에서 PROP_ENTRY_EX가 사용되지 않는다고 경고합니다.The compiler warns us that PROP_ENTRY_EX is deprecated. 보안되지 않으므로 PROP_ENTRY_TYPE_EX를 대신 사용하는 것이 좋습니다.It's not secure and the recommended substitute is PROP_ENTRY_TYPE_EX.

     PROP_ENTRY_EX( "LogFile", DISPID_LOGFILE, CLSID_ComSpyPropPage, IID_IComSpy)  
     PROP_ENTRY_EX( "ShowGridLines", DISPID_GRIDLINES, CLSID_ComSpyPropPage, IID_IComSpy)  
     PROP_ENTRY_EX( "Audit", DISPID_AUDIT, CLSID_ComSpyPropPage, IID_IComSpy)  
     PROP_ENTRY_EX( "ColWidth", DISPID_COLWIDTH, CLSID_ComSpyPropPage, IID_IComSpy)  

그에 따라 ccomspy.h의 코드를 변경하고 COM 형식을 적절하게 추가합니다.We change the code in ccomspy.h accordingly, adding COM types as appropriate.


이제 보다 엄격한 컴파일러 규칙 검사에 의해서도 발생하는 마지막 몇 개의 경고를 살펴보겠습니다.We're getting down to the last few warnings, which are also caused by more strict compiler conformance checks:

\spy\comspyctl\usersub.h(70): warning C4457: declaration of 'var' hides function parameter\spy\comspyctl\usersub.h(48): note: see declaration of 'var'\spy\comspyctl\usersub.h(94): warning C4018: '<': signed/unsigned mismatch  ComSpy.cpp\spy\comspyctl\comspy.cpp(186): warning C4457: declaration of 'bHandled' hides function parameter\spy\spy\comspyctl\comspy.cpp(177): note: see declaration of 'bHandled'  

경고 C4018은 다음 코드에서 발생합니다.Warning C4018 comes from this code:

for (i=0;i<lCount;i++)  

문제는 i가 UINT로 선언되고 lCount가 long으로 선언되어 signed 또는 unsigned가 일치하지 않는 것입니다.The problem is that i is declared as UINT and lCount is declared as long, hence the signed/unsigned mismatch. lCount는 long 형식을 사용하고 사용자 코드에 없는 IMtsEventInfo::get_Count에서 해당 값을 가져오기 때문에 형식을 UINT로 변경하는 것이 불편합니다.It would be inconvenient to change the type of lCount to UINT, since it gets its value from IMtsEventInfo::get_Count, which uses the type long, and is not in user code. 따라서 코드에 캐스트를 추가합니다.So we add a cast to the code. C 스타일 캐스트도 이러한 숫자 캐스트에 사용할 수 있지만 권장 스타일은 static_cast입니다.A C-style cast would do for a numerical cast such as this, but static_cast is the recommended style.

for (i=0;i<static_cast<UINT>(lCount);i++)  

이러한 경고는 동일한 이름의 매개 변수가 있는 함수에서 변수가 선언되어 잠재적으로 코드가 모호해질 수 있는 경우입니다.Those warnings are cases where a variable was declared in a function that has a parameter with the same name, leading to potentially confusing code. 지역 변수의 이름을 변경하여 이 문제를 해결했습니다.We fixed that by changing the names of the local variables.

3단계.Step 3. 테스트 및 디버깅Testing and debugging

먼저 다양한 메뉴 및 명령을 실행한 후 응용 프로그램을 닫아 앱을 테스트했습니다.We tested the app first by running through the various menus and commands, and then closing the application. 발견된 문제는 앱을 닫을 때 발생하는 디버그 어설션뿐이었습니다.The only issue noted was a debug assertion upon closing down the app. 이 문제는 응용 프로그램의 기본 COM 구성 요소인 CSpyCon 개체의 기본 클래스인 CWindowImpl의 소멸자에서 나타났습니다.The problem appeared in the destructor for CWindowImpl, a base class of the CSpyCon object, the application's main COM component. atlwin.h의 다음 코드에서 어설션 오류가 발생했습니다.The assertion failure occurred in the following code in atlwin.h.

virtual ~CWindowImplRoot()  
     #ifdef _DEBUG  
     if(m_hWnd != NULL)// should be cleared in WindowProc  
          ATLTRACE(atlTraceWindowing, 0, _T("ERROR - Object deleted before window was destroyed\n"));  
     #endif //_DEBUG  

hWnd는 일반적으로 WindowProc 함수에서 0으로 설정되지만 창을 닫는 Windows 메시지(WM_SYSCOMMAND)에 대해 기본 WindowProc 대신 사용자 지정 처리기가 호출되었기 때문에 0으로 설정되지 않았습니다.The hWnd is normally set to zero in the WindowProc function, but that didn't happen because instead of the default WindowProc, a custom handler is called for the Windows message (WM_SYSCOMMAND) that closes the window. 사용자 지정 처리기에서 hWnd를 0으로 설정하지 않았습니다.The custom handler was not setting the hWnd to zero. MFC의 CWnd 클래스에서 유사한 코드를 살펴보면 창을 삭제할 때 OnNcDestroy가 호출되고, MFC의 설명서에서 CWnd::OnNcDestroy를 재정의할 때 창에서 창 핸들을 분리하는 작업, 즉 hWnd를 0으로 설정하는 작업을 포함하여 올바른 정리 작업이 수행되도록 기본 NcDestroy를 호출해야 한다고 조언합니다.A look at similar code in MFC's CWnd class, shows that when a window is being destroyed, OnNcDestroy is called, and in MFC, documentation advises that when overriding CWnd::OnNcDestroy, the base NcDestroy should be called to make sure that the right clean-up operations occur, including separating the window handle from the window, or in other words, setting the hWnd to zero. 동일한 어설션 코드가 이전 버전의 atlwin.h에 있었기 때문에 원래 버전의 샘플에서도 이 어설션이 트리거되었을 수 있습니다.This assert might have been triggered in the original version of the sample as well, since the same assertion code was present in the old version of atlwin.h.

앱의 기능을 테스트하기 위해 ATL 프로젝트 템플릿을 사용하여 서비스 구성 요소를 만들고 ATL 프로젝트 마법사에서 COM+ 지원을 추가하도록 선택했습니다.To test the functionality of the app, we created a Serviced Component using the ATL project template, chose to add COM+ support in the ATL project wizard. 이전에 서비스 구성 요소로 작업한 적이 없는 경우에도 어렵지 않게 새로 만들고 다른 앱이 사용할 수 있도록 시스템 또는 네트워크에 등록 및 제공할 수 있습니다.If you haven’t worked with serviced components before, it’s not difficult to create one and get one registered and available on the system or network for other apps to use. COM Spy 앱은 진단 보조 기능으로 서비스 구성 요소의 활동을 모니터링하도록 설계되었습니다.The COM Spy app is designed to monitor the activity of serviced components as a diagnostic aid.

클래스를 추가하고, ATL 개체를 선택한 후 Dog라는 개체 이름을 지정했습니다.Then we added a class, chose ATL Object, and specified the object name as Dog. 그런 다음 dog.h 및 dog.cpp에서 구현을 추가했습니다.Then in dog.h and dog.cpp, we added the implementation.

STDMETHODIMP CDog::Wag(LONG* lDuration)  
    // TODO: Add your implementation code here  
    *lDuration = 100l;  
    return S_OK;  

빌드 및 등록하고(관리자 권한으로 Visual Studio를 실행하려면 필요함), Windows 제어판의 서비스 구성 요소 응용 프로그램을 사용하여 활성화했습니다.Next, we built and registered it (you’ll need to run Visual Studio as Administrator), and activated it using the Serviced Component application in the Windows Control Panel. C# Windows Forms 프로젝트를 만들고 도구 상자에서 폼으로 단추를 끌어다 놓은 다음 click 이벤트 처리기에 대해 단추를 두 번 클릭했습니다.We created a C# Windows Forms project, dragged a button to the form from the toolbox, and double-clicked that to a click event handler. 다음 코드를 추가하여 Dog 구성 요소를 인스턴스화했습니다.We added the following code to instantiate the Dog component.

private void button1_Click(object sender, EventArgs e)  
    ATLProjectLib.Dog dog1 = new ATLProjectLib.Dog();  

이 코드는 문제없이 실행되었으며, COM Spy가 작동하여 실행되고 Dog 구성 요소를 모니터링하도록 구성된 경우 활동을 보여 주는 많은 데이터가 나타납니다.This ran without problems, and with COM Spy up and running and configured to monitor the Dog component, lots of data appears showing the activity.

참고 항목See Also

포팅 및 업그레이드: 예제 및 사례 연구 Porting and Upgrading: Examples and Case Studies
다음 예제: Spy++ Next Example: Spy++
이전 예제: MFC ScribblePrevious Example: MFC Scribble