移植のガイド: COM Spy
このトピックは、以前のバージョンの Visual Studio C++ プロジェクトを Visual Studio の最新バージョンにアップグレードするプロセスについて示す一連の記事の 2 番目です。 このトピックのコード例は、前回 Visual Studio 2005 でコンパイルされています。
COMSpy
COMSpy は、コンピューター上のサービス コンポーネントのアクティビティを監視してログに記録するプログラムです。 サービス コンポーネントは、システム上で実行し、同じネットワーク上のコンピューターで使用できる COM+ コンポーネントです。 Windows コントロール パネルで、コンポーネント サービスの機能別に管理されています。
ステップ 1. プロジェクト ファイルを変換する
プロジェクト ファイルは簡単に変換できます。変換すると、移行レポートが生成されます。 対処が必要な可能性がある問題について通知するエントリが、レポートにいくつかあります。 報告される 1 つの問題を示します (このトピックでは、完全パスを削除するなど、エラー メッセージが読みやすさのために省略されることがあります)。
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).
プロジェクトのアップグレードのよくある問題の 1 つとして、[プロジェクトのプロパティ] ダイアログ ボックスのリンカーの OutputFile の設定をレビューしなければならないことがあります。 Visual Studio 2010 より前のプロジェクトでは、OutputFile が標準以外の値に設定されている場合、自動変換ウィザードで問題が発生することがあります。 このケースでは、出力ファイルのパスが非標準のフォルダー XP32_DEBUG に設定されていました。 このエラーの詳細について調べるため、Visual Studio 2010 プロジェクトのアップグレードに関連するブログの投稿を参照しました。これは、比較的大きな変更である vcbuild から msbuild への変更に関連するアップグレードです。 この情報によると、新しいプロジェクトを作成するときの出力ファイルの設定の既定値は $(OutDir)$(TargetName)$(TargetExt)
ですが、これは変換されたプロジェクトでうまくいくことが確認できないため、変換時に設定されていません。 ただし、OutputFile でその設定にして、機能するか確認してみましょう。 機能すれば、続行できます。 非標準の出力フォルダーを使用する特段の理由がなければ、標準の場所を使用することをお勧めします。 このケースでは、移植とアップグレード プロセス中に、出力の場所を非標準のままにすることを選択しました。$(OutDir)
は、デバッグ構成で XP32_DEBUG フォルダーに解決され、リリース構成では ReleaseU フォルダーに解決されます。
ステップ 2. ビルドできる状態にする
移植されたプロジェクトをビルドすると、多数のエラーと警告が発生します。
次のコンパイラ エラーが原因で、ComSpyCtl
はコンパイルを完了しません。
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
メソッドを参照します。
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());
}
問題は、古いバージョンのコンパイラであれば受理されていた変換が有効でなくなったことです。 C++ 標準に準拠するために、以前許可されていたいくつかのコードが許可されなくなりました。 この場合、const ポインターを要求する関数に非定数のポインターを渡すことは安全ではありません。 解決策は、CComSpy
クラスで IPersistStreamInit_Save
の宣言を検索し、3 番目のパラメーターに const 修飾子を追加することです。
HRESULT CComSpy::IPersistStreamInit_Save(LPSTREAM pStm, BOOL /* fClearDirty */, const ATL_PROPMAP_ENTRY* pMap)
また、IPersistStreamInit_Load
を同じように変更します。
HRESULT IPersistStreamInit_Load(LPSTREAM pStm, const ATL_PROPMAP_ENTRY* pMap);
次のエラーは、登録に関するものです。
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.
このビルド後の登録コマンドはもう必要ありません。 代わりに、カスタム ビルド コマンドを削除して、リンカー設定で出力を登録するよう指定します。
警告に対処する
プロジェクトで次のリンカー警告が生成されます。
warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/SAFESEH' specification
/SAFESEH
コンパイラ オプションはデバッグ モード (/EDITANDCONTINUE
が有効なとき) では役に立たないので、ここでは、デバッグの構成でのみ、/SAFESEH
を無効にします。 [プロパティ] ダイアログでこれを行うには、このエラーを生成するプロジェクトの [プロパティ] ダイアログを開き、まず [構成] を [デバッグ] に設定して (実際は Unicode のデバッグ)、[リンカー高度クラス] セクションで、[安全な例外ハンドラーを含むイメージ] プロパティを [いいえ] に設定 (/SAFESEH:NO
) します。
コンパイラからはほかにも、PROP_ENTRY_EX
が非推奨とされていることが警告されています。 これは安全でなく、推奨される代替手段は 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 型を適宜追加します。
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()
最後の警告に来ていますが、これはより厳密なコンパイラの一致チェックも原因になっています。
\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 は次のコードに由来します。
for (i=0;i<lCount;i++)
CoTaskMemFree(pKeys[i]);
問題は、i
が UINT
として宣言され、lCount
が long
として宣言されているため、符号付き/符号なしの不一致があることです。 lCount
の型を UINT
に変更する方法には不都合があります。long
型を使用し、ユーザーのコード内にはない IMtsEventInfo::get_Count
から値を取得しているためです。 したがって、コードにキャストを追加します。 C スタイルのキャストは、このような数値のキャストに対して機能しますが、static_cast
が推奨されるスタイルです。
for (i=0;i<static_cast<UINT>(lCount);i++)
CoTaskMemFree(pKeys[i]);
これらの警告は、同じ名前を持つパラメーターがある関数で変数が宣言されており、混乱しやすいコードになる可能性がある場合に発生します。 ローカル変数の名前を変更することで、これを修正します。
手順 3. テストとデバッグ
まずさまざまなメニューやコマンドを使用して実行し、それからアプリケーションを終了することでアプリケーションをテストしました。 指摘された唯一の問題は、アプリを閉じる時に使用したデバッグのアサーションでした。 問題は、アプリケーションのメインの COM コンポーネントである CSpyCon
オブジェクトの基底クラスの CWindowImpl
のデストラクターに表示されました。 アサーション エラーは、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
関数で 0 に設定されますが、既定の WindowProc
の代わりに、ウィンドウを閉じる Windows メッセージ (WM_SYSCOMMAND) でカスタム ハンドラーが呼び出されたため、そのような処理が発生しませんでした。 カスタム ハンドラーは hWnd
をゼロに設定していませんでした。 MFC の CWnd
クラスの同様のコードを見ると、ウィンドウが破棄されるときに OnNcDestroy
が呼び出されています。MFC では、CWnd::OnNcDestroy
をオーバーライドするときには基本 NcDestroy
を呼び出し、ウィンドウのハンドルをウィンドウから分離することを含めた適切なクリーンアップ操作が必ず発生するようにする、つまり、hWnd
をゼロに設定するようドキュメントでアドバイスされています。 このアサーションは、同じアサーション コードが古いバージョンの atlwin.h に存在するため、元のバージョンのサンプルでもトリガーされている可能性があります。
アプリの機能をテストするために、ATL プロジェクト テンプレートを使用したサービス コンポーネントを作成し、ATL プロジェクト ウィザードで COM+ のサポートを追加することを選択しました。 サービス コンポーネントの作業をこれまで行ったことがなくても、作成は難しくなく、ほかのアプリが使用するシステムやネットワークでも登録して利用できます。 COM Spy アプリは、診断を目的としてサービス コンポーネントのアクティビティを監視するよう設計されています。
次に、クラスを追加して ATL オブジェクトを選択し、オブジェクト名を Dog
と指定しました。 そして、dog.h および dog.cpp に実装を追加しました。
STDMETHODIMP CDog::Wag(LONG* lDuration)
{
// TODO: Add your implementation code here
*lDuration = 100l;
return S_OK;
}
次に、ビルドして登録し (Visual Studio を管理者として実行する必要があります)、Windows コントロール パネルのサービス コンポーネント アプリケーションを使用してアクティブ化しました。 C# Windows フォーム プロジェクトを作成し、ツールボックスからフォームにボタンをドラッグして、クリック イベント ハンドラーをダブルクリックしました。 次のコードを追加し、Dog
コンポーネントのインスタンスを作成しました。
private void button1_Click(object sender, EventArgs e)
{
ATLProjectLib.Dog dog1 = new ATLProjectLib.Dog();
dog1.Wag();
}
これは問題なく実行できました。COM Spy が起動して実行され、Dog
コンポーネントを監視するよう構成されると、アクティビティを示す大量のデータが表示されます。