迁移指南: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.

COMSpyCOMSpy

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. 为了解有关此错误的详细信息,我们可以参考与 Visual C++ 2010 项目升级相关的博客文章,该升级涉及从 vcbuild 到 msbuild 这一重大更改。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  

/EDITANDCONTINUE 有用时,/SAFESEH 编译器选项在调试模式中将不起作用,因此此处的修复方式是仅在“调试”配置中禁用 /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. 若要在属性对话框中执行此操作,则要打开产生此错误的项目的属性对话框,首先将“配置”设置为“调试”(实际为“调试 Unicode”),然后在“链接器高级”部分中将“映像具有安全异常处理程序”属性重置为“否”(/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.

BEGIN_PROPERTY_MAP(CComSpy)  
     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)  
     PROP_PAGE(CLSID_StockFontPage)  
END_PROPERTY_MAP()  

将相应更改 ccomspy.h 中的代码,并根据需要添加 COM 类型。We change the code in ccomspy.h accordingly, adding COM types as appropriate.

BEGIN_PROPERTY_MAP(CComSpy)  
     PROP_ENTRY_TYPE_EX( "LogFile", DISPID_LOGFILE, CLSID_ComSpyPropPage, IID_IComSpy, VT_BSTR)  
     PROP_ENTRY_TYPE_EX( "ShowGridLines", DISPID_GRIDLINES, CLSID_ComSpyPropPage, IID_IComSpy, VT_BOOL)  
     PROP_ENTRY_TYPE_EX( "Audit", DISPID_AUDIT, CLSID_ComSpyPropPage, IID_IComSpy, VT_BOOL)  
     PROP_ENTRY_TYPE_EX( "ColWidth", DISPID_COLWIDTH, CLSID_ComSpyPropPage, IID_IComSpy, VT_UINT)  
     PROP_PAGE(CLSID_StockFontPage)  
END_PROPERTY_MAP()  

下面将处理最后几个警告,它们也是由更严格的编译器符合性检查引起的: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++)  
    CoTaskMemFree(pKeys[i]);  

问题是“i”声明为 UINT 而“lCount”声明为 long,因此出现有符号/无符号不匹配。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 获取值,将 lCount 的类型更改 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++)  
    CoTaskMemFree(pKeys[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. 问题出现在 CWindowImpl 的析构函数中,该函数是 CSpyCon 对象的基类,也是应用程序的主要 COM 组件。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"));  
          ATLASSERT(FALSE);  
     }  
     #endif //_DEBUG  
}  

hWnd 在 WindowProc 函数中通常设置为零,但未如此设置,原因是为关闭窗口的 Windows 消息 (WM_SYSCOMMAND) 调用了自定义处理程序而非默认的 WindowProc。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 设置为零。The custom handler was not setting the hWnd to zero. MFC 的 CWnd 类中的类似代码显示当正在销毁某个窗口时,调用 OnNcDestroy,并且在 MFC 中文档建议在重写 CWnd::OnNcDestroy 时,应调用基础 NcDestroy 以确保发生正确的清理操作,包括分离窗口处理程序和窗口(即将 hWnd 设置为零)。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 窗体项目,将一个按钮从工具箱拖动到此窗体,并双击它进入 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();  
    dog1.Wag();  
}  

这将顺畅运行,不出现任何问题,且 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