Guida al porting: COM SpyPorting Guide: COM Spy

Questo argomento è il secondo di una serie di articoli che illustrano il processo di aggiornamento di progetti Visual C++ creati con versioni precedenti alla versione più recente di 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. Il codice di esempio usato in questo argomento è stato compilato per l'ultima volta con Visual Studio 2005.The example code in this topic was last compiled with Visual Studio 2005.

COM SpyCOMSpy

COM Spy è un programma che monitora e registra l'attività dei componenti serviti su un computer.COMSpy is a program that monitors and logs the activity of serviced components on a machine. I componenti serviti sono componenti COM+ che vengono eseguiti in un sistema e possono essere usati dai computer nella stessa rete.Serviced components are COM+ components that run on a system and can be used by computers on the same network. Vengono gestiti dalla funzionalità di Servizi componenti nel Pannello di controllo di Windows.They're managed by the Component Services functionality in the Windows Control Panel.

Passaggio 1.Step 1. Conversione del file di progettoConverting the project file.

Il file di progetto viene convertito facilmente e produce un report di migrazioneThe project file converts easily and produces a migration report. contenente alcune voci che informano sui possibili problemi da affrontare.There are a few entries in the report that let us know about issues we might need to deal with. Ecco uno dei problemi segnalati (notare che in questo argomento i messaggi di errore vengono talvolta abbreviati per migliorare la leggibilità, ad esempio rimuovendo i percorsi completi):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).  

Uno dei problemi frequenti riscontrati durante l'aggiornamento di progetti è che può essere necessario esaminare l'impostazione di OutputFile del linker nella finestra di dialogo delle proprietà del progetto.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. Per i progetti creati con versioni precedenti a Visual Studio 2010, OutputFile è un'impostazione che, se impostata su un valore non standard, crea problemi con la procedura guidata di conversione automatica.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. In questo caso i percorsi dei file di output sono stati impostati su una cartella non standard, ovvero XP32_DEBUG.In this case, the paths for the output files were set to a nonstandard folder, XP32_DEBUG. Altre informazioni su questo errore sono reperibili in un post di blog relativo all'aggiornamento del progetto Visual C++ 2010, l'aggiornamento che include l'importante passaggio da vcbuild a 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. In base a queste informazioni, il valore predefinito per l'impostazione OutputFile quando si crea un nuovo progetto è $(OutDir)$(TargetName)$(TargetExt), che però non viene impostato durante la conversione perché nei progetti convertiti non è possibile verificare che tutto sia corretto.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. Provare comunque a specificare questo valore per OutputFile e verificare se funziona.However, let's try putting that in for OutputFile and see if it works. In caso affermativo, sarà possibile procedere.It does, so we can move on. Se non esistono motivi specifici per usare una cartella di output non standard, è consigliabile usare il percorso standard.If there is no particular reason for using a nonstandard output folder, we recommend using the standard location. In questo caso, è stato scelto di mantenere il percorso di output non standard durante il processo di porting e di aggiornamento. $(OutDir) risolve la cartella XP32_DEBUG nella configurazione di Debug e la cartella ReleaseU per la configurazione di tipo Versione.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.

Passaggio 2.Step 2. Preparazione della compilazioneGetting it to build

Durante la compilazione del progetto di cui è stato eseguito il porting si verifica un certo numero di errori e di avvisi.Building the ported project, a number of errors and warnings occur.

ComSpyCtl non viene compilato a causa di questo errore del compilatore: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  

L'errore fa riferimento al metodo Save nella classe IPersistStreamInitImpl in atlcom.h.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());  
}  

Il problema è che una conversione accettata in una versione precedente del compilatore non è più valida.The problem is that a conversion that an older version of the compiler accepted is no longer valid. Per garantire la conformità allo standard C++, una parte del codice consentito in precedenza non è più consentita.In order to conform with the C++ standard, some code that previously was allowed is no longer allowed. In questo caso, è preferibile non passare un puntatore di tipo non const a una funzione che prevede un puntatore di tipo const.In this case, it's not safe to pass a non-const pointer to a function that expects a const pointer. La soluzione consiste nell'individuare la dichiarazione di IPersistStreamInit_Save nella classe CComSpy e aggiungere il modificatore const al terzo parametro.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)  

È inoltre necessario apportare una modifica simile a IPersistStreamInit_Load.And a similar change to IPersistStreamInit_Load.

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

L'errore successivo riguarda la registrazione.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.  

Questo comando di registrazione post-compilazione non è più necessario.We don't need this post-build registration command anymore. È invece sufficiente rimuovere il comando di compilazione personalizzato e specificare di registrare l'output nelle impostazioni del linker.Instead, we simply remove the custom build command, and specify in the Linker settings to register the output.

Gestione degli avvisiDealing with warnings

Il progetto genera l'avviso seguente del linker.The project produces the following linker warning.

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

L'opzione del compilatore /SAFESEH non è utile in modalità debug, mentre lo è /EDITANDCONTINUE, di conseguenza in questo caso la correzione consiste nel disattivare /SAFESEH solo per le configurazioni di tipo Debug.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. A questo scopo, è stata aperta la finestra di dialogo delle proprietà relativa al progetto che ha generato l'errore ed è stata impostata innanzitutto la configurazione su Debug (ovvero su Debug Unicode), quindi nella sezione delle impostazioni avanzate del linker, è stata reimpostata la proprietà Immagine con gestori eccezioni sicuri su No (/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).

Il compilatore avverte che PROP_ENTRY_EX è deprecato,The compiler warns us that PROP_ENTRY_EX is deprecated. di conseguenza non è sicuro e deve essere sostituito con 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()  

Il codice in ccomspy.h è stato modificato di conseguenza, aggiungendo se necessario i tipi 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()  

Ed ecco finalmente gli ultimi avvisi, che sono anche quelli causati da controlli di conformità più stringenti del compilatore: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'  

L'avviso C4018 proviene dal codice seguente:Warning C4018 comes from this code:

for (i=0;i<lCount;i++)  
    CoTaskMemFree(pKeys[i]);  

Il problema è che i è dichiarato come UINT e lCount è dichiarato come long, da cui deriva l'errata corrispondenza tra signed e unsigned.The problem is that i is declared as UINT and lCount is declared as long, hence the signed/unsigned mismatch. Modificare il tipo di lCount in UINT sarebbe scomodo perché questo ottiene il relativo valore da IMtsEventInfo::get_Count, che usa il tipo long e non è incluso nel codice utente.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. A questo punto viene aggiunto un cast al codice.So we add a cast to the code. In un cast numerico come questo è corretto anche un cast di tipo C, ma il tipo consigliato è 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]);  

Questi avvisi si verificano in casi in cui una variabile è stata dichiarata in una funzione che contiene un parametro con lo stesso nome e questo causa codice potenzialmente poco chiaro.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. Per risolvere il problema, sono stati modificati i nomi delle variabili locali.We fixed that by changing the names of the local variables.

Passaggio 3.Step 3. Test e debugTesting and debugging

Per testare l'app, sono stati prima esaminati tutti i vari menu e comandi e quindi è stata chiusa l'applicazione.We tested the app first by running through the various menus and commands, and then closing the application. L'unico problema riscontrato è stata un'asserzione di debug alla chiusura dell'app.The only issue noted was a debug assertion upon closing down the app. Il problema si è presentato nel distruttore di CWindowImpl, una classe di base dell'oggetto CSpyCon, ovvero il componente COM principale dell'applicazione.The problem appeared in the destructor for CWindowImpl, a base class of the CSpyCon object, the application's main COM component. L'errore di asserzione si è verificato nel codice seguente in 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 è in genere impostato su zero nella funzione WindowProc, ma questo non si è verificato perché invece della funzione WindowProc predefinita è stato chiamato un gestore personalizzato per il messaggio Windows (WM_SYSCOMMAND) che chiude la finestraThe 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. e tale gestore non ha impostato hWnd su zero.The custom handler was not setting the hWnd to zero. Dando un'occhiata al codice simile nella classe CWnd di MFC è possibile notare che durante l'eliminazione di una finestra viene chiamato OnNcDestroy e, in MFC, la documentazione consiglia che durante l'override di CWnd::OnNcDestroy, è necessario chiamare la versione di base di NcDestroy per garantire l'esecuzione delle operazioni di pulizia corrette, tra cui la separazione dell'handle dalla finestra, vale a dire l'impostazione di hWnd su zero.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. È possibile che questa asserzione sia stata attivata anche nella versione originale dell'esempio, dal momento che lo stesso codice dell'asserzione era presente nella versione precedente di 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.

Per testare la funzionalità dell'app, è stato creato un componente servito che usa il modello di progetto ATL e si è scelto di aggiungere il supporto COM+ nella Creazione guidata progetto ATL.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. Anche se non si è mai lavorato prima con componenti serviti, non è difficile crearne uno e registrarne uno nel sistema o nella rete per renderlo disponibile per altre app.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. L'app COM Spy viene usata per monitorare l'attività di componenti servizi per finalità diagnostiche.The COM Spy app is designed to monitor the activity of serviced components as a diagnostic aid.

È stata quindi aggiunta una classe, è stato scelto Oggetto ATL ed è stato specificato Dog come nome dell'oggetto,Then we added a class, chose ATL Object, and specified the object name as Dog. aggiungendo l'implementazione in dog.h e 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;  
}  

In seguito l'oggetto è stato compilato e registrato (eseguendo Visual Studio come amministratore) e infine attivato nel Pannello di controllo di Windows tramite l'applicazione Componente servito.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. È stato creato un progetto Windows Forms C#, è stato trascinato un pulsante nel form dalla casella degli strumenti ed è stato fatto doppio clic su di esso per un gestore eventi clic.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. È stato infine aggiunto il codice seguente per creare un'istanza del componente 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();  
}  

Il codice è stato eseguito senza problemi e, con COM Spy in esecuzione e configurato per il monitoraggio del componente Dog, sono apparsi numerosi dati relativi all'attività.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.

Vedere ancheSee Also

Porting e aggiornamento: esempi e case study Porting and Upgrading: Examples and Case Studies
Esempio successivo: Spy++ Next Example: Spy++
Esempio precedente: MFC ScribblePrevious Example: MFC Scribble