![]() |
Desktop Development »
Shell and IE programming »
IE / Explorer plug-ins
Intermediate
A Desktop Performance MonitorBy Chad BuscheHow to implement an Explorer Desktop Band that uses the Microsoft’s Performance Data Helper interface to display current performance data about activity such as memory, disk, and processor usage. |
VC6Win2K, WinXP, ATL, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
![]()

The Windows performance monitor is a great tool for determining what is happening with the performance of a computer. I have always found the ability to monitor every aspect of a computer extremely useful and very powerful but wished that Microsoft�s performance monitor would dock to the task bar so I could continuously monitor my system without having to bring the window to the front. One day I decided to write it. This article will demonstrate how to implement an Explorer Desktop Band that uses the Microsoft�s Performance Data Helper interface to display current performance data about activity such as memory, disk, and processor usage.
Requires Internet Explorer 4.0 or greater and one of the following operating systems:
Windows NT 4.0
Windows 2000
Windows XP (Home and Pro)
To install the desktop performance monitor band you need to run regsvr32.exe on the IETools.dll. The IETools.dll can be downloaded or built from the source provided as part of this article. See links above.
Regsvr32.exe IETools.dll
Once the band object has been successfully registered you can add the toolbar to the taskbar by right clicking the task bar. Go to the Toolbars sub-menu and select the �Performance Monitor� option.
To remove the performance monitor unselect the �Performance Monitor� option on the toolbar sub-menu and run the following command on the IETools.dll.
Regsvr32 /U IETools.dll
Reboot the computer and then the IETools.dll can be deleted.
A Desktop band is simply a COM object that implements certain interfaces and registers itself as belonging to specific component category. There are three interfaces every desktop band needs to implement:
The IDesktopBand interface provides the desktop bands container
with information about the band object. IObjectWiteSite interface
enables the communication between Desktop and container.
IPersistStream is uses to store state so an object can be saved and
loaded.
Additionally the desktop band must also register itself as belonging to the
CATID_DeskBand component category. This lets explorer know that
your object can be hosted in the taskbar and to add it to the list of available
toolbars in the taskbar�s context menu.
The COM object is created by running the ATL COM application wizard creating new COM server dll. Once that is done you derive you new band objects from the three interfaces mentioned above.
class ATL_NO_VTABLE CPerfBar : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CPerfBar, &CLSID_PerfBar>, public IDispatchImpl<IPerfBar, &IID_IPerfBar,&LIBID_IETOOLSLib>, public IObjectWithSite, public IPersistStream, public IDeskBand, public IContextMenu, public CWindowImpl<CPerfBar>
Next add the interfaces to ATL�s COM map
BEGIN_COM_MAP(CPerfBar)
COM_INTERFACE_ENTRY ( IPerfBar )
COM_INTERFACE_ENTRY ( IDispatch )
COM_INTERFACE_ENTRY ( IObjectWithSite )
COM_INTERFACE_ENTRY ( IDeskBand )
COM_INTERFACE_ENTRY ( IPersist )
COM_INTERFACE_ENTRY ( IPersistStream )
COM_INTERFACE_ENTRY ( IDockingWindow )
COM_INTERFACE_ENTRY ( IOleWindow )
COM_INTERFACE_ENTRY ( IContextMenu )
END_COM_MAP()
Then we need to let explorer know about our band object by registering it in the component category.
BEGIN_CATEGORY_MAP( CPerfBar )
IMPLEMENTED_CATEGORY(CATID_DeskBand)
END_CATEGORY_MAP()
To draw our performance meter the first thing we need to do is create a
window to draw on. ATL has a nice HWND wrapper class named
CWindowImpl that makes it very easy for an object to handle and
respond to Windows messages. To use it we first derive our band object from
CWindowImpl.
class ATL_NO_VTABLE CPerfBar : � public CWindowImpl<CPerfBar>
To handle window message we must create a message map and message handlers for every message that will be handled. These are added to our desktop band class.
BEGIN_MSG_MAP( CPerfBar )
MESSAGE_HANDLER( WM_CREATE, OnCreate )
MESSAGE_HANDLER( WM_DESTROY, OnGoodBye )
MESSAGE_HANDLER( WM_PAINT, OnPaint )
MESSAGE_HANDLER( WM_ERASEBKGND, OnEraseBg )
END_MSG_MAP()
LRESULT OnPaint ( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
LRESULT OnEraseBg( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
LRESULT OnCreate ( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
LRESULT OnGoodBye( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
Now we can implement our drawing routine. The performance data is stored as a percentage from 0 to 100 in a STL deque. The deque was chosen over a STL vector because it provides much faster insertions in the front.
typedef deque<FLOAT> PerfValueQ; typedef PerfValueQ::iterator PerfValueQIterator ; PerfValueQ m_qPerfValues;
The OnPaint handler creates a memory device context to avoid
flickering during the drawing process. The meter is then drawn on to the memory
device context and finally the memory device context is BitBlt on to the screen.
LRESULT CPerfBar::OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
bHandled )
{
PAINTSTRUCT ps = {0};
RECT rect = {0};
HDC hdcMem = NULL;
HBITMAP hbmMem = NULL;
HBITMAP hbmOld = NULL;
BeginPaint( &ps );
GetClientRect( &rect );
hdcMem = CreateCompatibleDC( ps.hdc );
hbmMem = CreateCompatibleBitmap( ps.hdc, rect.right - rect.left,
rect.bottom- rect.top);
hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);
DrawBarMeter( hdcMem );
BitBlt( ps.hdc, rect.left, rect.top, rect.right-rect.left,
rect.bottom-rect.top, hdcMem, 0, 0, SRCCOPY);
SelectObject( hdcMem, hbmOld );
DeleteObject( hbmMem );
DeleteDC( hdcMem );
EndPaint( &ps);
return 0;
}
VOID CPerfBar::DrawBarMeter( HDC hdc )
{
INT barHeight = 0;
RECT rect = {0};
HPEN hOldPen = NULL;
PerfValueQIterator QIterator = m_qPerfValues.begin();
GetClientRect( &rect );
FillRect( hdc, &rect, m_backBrush );
hOldPen = (HPEN)SelectObject( hdc, m_forePen );
for ( ; rect.right >= rect.left; rect.right-- )
{
if ( QIterator != m_qPerfValues.end() )
{
barHeight = (INT)( (*QIterator) * ( rect.bottom - rect.top ) );
QIterator++;
}
else
barHeight = 0;
barHeight = barHeight < 2 ? 2 : barHeight;
MoveToEx( hdc, rect.right, rect.bottom, NULL );
LineTo ( hdc, rect.right, rect.bottom - barHeight );
}
m_qPerfValues.erase( QIterator, m_qPerfValues.end() );
SelectObject( hdc, hOldPen );
}
Persistence in band objects is used for more than just saving the state of a band when shutting down Windows and reloading the state when you start back up. Every time the band object is undocked from the taskbar or desktop it is persisted to a stream and destroyed. Once it has been relocated to the new location a brand new instance of the band object is created and its state is loaded from the persisted stream.
Persistence in band objects is implemented through the
IPersistStream interface. The IPersistStream interface
provides methods for saving and loading objects in a stream. Initially the
stream requests the maximum size in bytes that is needed for the stream to save
the object. The GetSizeMax method is called to request this
information.
STDMETHODIMP CPerfBar::GetSizeMax( ULARGE_INTEGER* pcbSize )
{
if ( pcbSize == NULL )
return E_INVALIDARG;
ULISet32( *pcbSize, sizeof( m_clrFore ) +
sizeof( m_clrBack ) +
sizeof( m_ThreadData.dwRefreshRate ) +
sizeof( m_ThreadData.szCounterPath ) );
return S_OK;
}
The object is then persisted by a call to the save method.
STDMETHODIMP CPerfBar::Save( LPSTREAM pStream,
BOOL bClearDirty )
{
HRESULT hr = S_OK;
if ( FAILED( pStream->Write( &m_clrFore,
sizeof(m_clrFore), NULL ) ) ||
FAILED( pStream->Write( &m_clrBack,
sizeof(m_clrBack), NULL ) ) ||
FAILED( pStream->Write( &m_ThreadData.dwRefreshRate,
sizeof(m_ThreadData.dwRefreshRate), NULL ) ) ||
FAILED( pStream->Write( m_ThreadData.szCounterPath,
sizeof(m_ThreadData.szCounterPath), NULL ) ) )
{
hr = STG_E_CANTSAVE;
}
else
{
if ( bClearDirty )
m_bDirty = FALSE;
}
return hr;
}
The band object will then be destroyed while the user picks a new location to dock the band. Once the new location has been chosen a new band object is created and the state is reloaded.
STDMETHODIMP CPerfBar::Load( LPSTREAM pStream )
{
HRESULT hr = S_OK;
if ( FAILED( pStream->Read( &m_clrFore,
sizeof(m_clrFore), NULL ) ) ||
FAILED( pStream->Read( &m_clrBack,
sizeof(m_clrBack), NULL ) ) ||
FAILED( pStream->Read( &m_ThreadData.dwRefreshRate,
sizeof(m_ThreadData.dwRefreshRate), NULL ) ) ||
FAILED( pStream->Read( m_ThreadData.szCounterPath,
sizeof(m_ThreadData.szCounterPath), NULL ) ) )
{
hr = E_FAIL;
}
return hr;
}

A nice feature is explorer allows band objects to add menu items to its context menu. This allows the performance monitor band object to add a command for opening up the options dialog so the user can change the properties of the band object.
This is implemented through the IContextMenu
interface which a band object must implement to add items to the context menus.
When the context menu is displayed explorer passes the band object a
HMENU to add menu items to.
#define MENU_ITEMS_ADDED 2 STDMETHODIMP CPerfBar::QueryContextMenu( HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags ) { HRESULT hr = S_OK; if( CMF_DEFAULTONLY & uFlags ) hr = MAKE_HRESULT( SEVERITY_SUCCESS, 0, 0 ); else { TCHAR lptstrMenuString[MAX_STRINGTABLE] = {0}; LoadString( _Module.m_hInstResource, IDS_MI_CONFIGURE, lptstrMenuString, MAX_STRINGTABLE ); // Add a seperator InsertMenu( hMenu, indexMenu, MF_SEPARATOR | MF_BYPOSITION, idCmdFirst + IDM_SEPERATOR, 0 ); // Add the new menu item InsertMenu( hMenu, indexMenu, MF_STRING | MF_BYPOSITION, idCmdFirst + IDM_CONFIGURE, lptstrMenuString ); hr = MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, MENU_ITEMS_ADDED ); } return hr; }
The return value to the QueryContextMenu function is a
successful HRESULT with the number of items added to the context
menu place in the status code section.
The context menu is then displayed to the user. Band objects are notified if
the user selects a custom menu item by the InvokeCommand method.
The performance monitor band launches a dialog to allow the user to change the
properties of the band.
STDMETHODIMP CPerfBar::InvokeCommand( LPCMINVOKECOMMANDINFO pici )
{
HRESULT hr = S_OK;
if ( HIWORD( pici->lpVerb ) != 0 )
hr = E_INVALIDARG;
else
{
switch ( LOWORD( pici->lpVerb ) )
{
case IDM_CONFIGURE:
if ( m_dlgOptions.IsWindow() == FALSE )
{
m_dlgOptions.SetCounter ( m_ThreadData.szCounterPath );
m_dlgOptions.SetBackgroundColor ( m_clrBack );
m_dlgOptions.SetForegroundColor ( m_clrFore );
m_dlgOptions.SetRefreshRate( m_ThreadData.dwRefreshRate );
m_dlgOptions.SetDialogParent( m_hWnd );
m_dlgOptions.Create ( m_hWnd );
m_dlgOptions.CenterWindow( GetDesktopWindow() );
m_dlgOptions.ShowWindow( SW_SHOW );
}
else
m_dlgOptions.SetFocus();
hr = S_OK;
break;
default:
hr = E_INVALIDARG;
}
}
return hr;
}
The Performance Data Helper (PDH) library is an API provided by Microsoft to retrieve real-time performance information. This library is only supported on the NT platforms so the performance monitor band will only function on the NT platform as well. The API for monitoring a source is fairly simple and Microsoft provides a way to add your own sources. To begin monitoring a source you need to open a query and create a counter.
class CPerfMon { public: CPerfMon( ); virtual ~CPerfMon(); BOOL Start( LPTSTR lpstrCounter ); VOID Stop(); LONG GetValue(); private: HQUERY m_hQuery; HCOUNTER m_hCounter; }; BOOL CPerfMon::Start( LPTSTR lpstrCounter ) { PDH_STATUS pdhStatus = ERROR_SUCCESS; Stop(); pdhStatus = PdhOpenQuery( NULL, 0, &m_hQuery ) ; if ( pdhStatus == ERROR_SUCCESS ) { pdhStatus = PdhValidatePath( lpstrCounter ); if ( pdhStatus == ERROR_SUCCESS ) { pdhStatus = PdhAddCounter( m_hQuery, lpstrCounter, 0, &m_hCounter ) ; } } ASSERT( pdhStatus == ERROR_SUCCESS ); if ( pdhStatus != ERROR_SUCCESS ) Stop(); return pdhStatus == ERROR_SUCCESS; }
The counter requires a path to a source which looks something like �\Processor(_Total)\% Processor Time�.
Once a performance counter has been successfully created, the current value
can be retrieve by a call to the PdhGetFormattedCounterValue
method.
LONG CPerfMon::GetValue()
{
PDH_STATUS pdhStatus = ERROR_SUCCESS;
LONG nRetVal = 0;
PDH_FMT_COUNTERVALUE pdhCounterValue;
pdhStatus = PdhCollectQueryData( m_hQuery );
if ( pdhStatus == ERROR_SUCCESS )
{
pdhStatus = PdhGetFormattedCounterValue( m_hCounter,
PDH_FMT_LONG,
NULL,
&pdhCounterValue );
if ( pdhStatus == ERROR_SUCCESS )
nRetVal = pdhCounterValue.longValue;
}
return nRetVal;
}
Cleanup of the allocated counters and queries is simple and can be accomplished by removing the counter and closing the query.
VOID CPerfMon::Stop()
{
if ( m_hCounter )
PdhRemoveCounter( m_hCounter );
if ( m_hQuery )
PdhCloseQuery ( m_hQuery );
m_hQuery = NULL;
m_hCounter = NULL;
}
The performance monitoring for the band object is handled by a separate thread that posts custom windows messages to the main window. The separate thread is used to break the monitoring of the system from the windows message loop. The main thread procedure takes a structure that contains all the information the thread needs to monitor the system and an extra Boolean value for performing synchronization between the thread and the main window.
typedef struct { HWND hWndNotify; // Window to post messages to LONG bContinue; // Set to false to cause the thread to stop // monitoring DWORD dwRefreshRate; // How often we get a new performance value TCHAR szCounterPath[ MAX_COUNTER_PATH ]; // what we are monitoring } PerfMonThreadData, *LPPERFMONTHREADDATA;
The thread loops so long as bContinue is TRUE.
While the thread loops, it performs the following functions:
VOID PerfMonThreadProc( LPVOID lParam )
{
LPPERFMONTHREADDATA lpData = (LPPERFMONTHREADDATA) lParam;
INT nTime = 0;
CPerfMon PerfMon;
if ( PerfMon.Start( lpData->szCounterPath ) &&
IsWindow( lpData->hWndNotify ) )
{
while ( InterlockedExchange( &lpData->bContinue, TRUE ) )
{
PostMessage(lpData->hWndNotify, WM_ADDPERFVALUE, 0,
PerfMon.GetValue());
nTime = ( nTime == 0 ) ? lpData->dwRefreshRate :
lpData->dwRefreshRate -
(GetTickCount() - nTime);
if ( nTime > 0 )
{
Sleep( nTime );
}
nTime = GetTickCount();
}
}
PerfMon.Stop();
}
The main window simply waits for a message from the monitoring thread and handles them as it would any other window message. The new performance value is stuffed in the dequeue and the main window is invalidated.
LRESULT CPerfBar::OnNewPerformanceValue( UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled )
{
m_qPerfValues.push_front( ((FLOAT)lParam / 100.0f ) );
Invalidate();
return 0;
}
Please report all defects to me by email: mailto:chad_busche@hotmail.com?subject=Desktop Performance Monitor
Please also feel free to submit suggestions and comments. Enjoy!
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 31 Oct 2002 Editor: Chris Maunder |
Copyright 2002 by Chad Busche Everything else Copyright © CodeProject, 1999-2009 Web15 | Advertise on the Code Project |