|
|
//=============================================================================
// Code used to manage threaded WMI refreshes.
//=============================================================================
#include "stdafx.h"
#include "refreshthread.h"
#include "wmilive.h"
//-----------------------------------------------------------------------------
// The constructor - create the events to manage the refresh thread.
//-----------------------------------------------------------------------------
CRefreshThread::CRefreshThread(HWND hwnd) : m_fCancel(FALSE), m_fQuit(FALSE), m_fRecursive(FALSE), m_fForceRefresh(FALSE), m_pcategory(NULL), m_hThread(NULL), m_dwThreadID(0), m_hwnd(hwnd), m_hrWMI(E_FAIL) { // Generate a system wide unique name for the events (in case there are multiple
// instances of MSInfo running). If we can't generate a GUID for this, use the tick count.
CString strEvent(_T("")); GUID guid;
if (SUCCEEDED(::CoCreateGuid(&guid))) { LPOLESTR lpGUID;
if (SUCCEEDED(StringFromCLSID(guid, &lpGUID))) { strEvent = lpGUID; CoTaskMemFree(lpGUID); } }
if (strEvent.IsEmpty()) strEvent.Format(_T("%08x"), ::GetTickCount());
m_eventDone = CreateEvent(NULL, TRUE, TRUE, CString(_T("MSInfoDone")) + strEvent); m_eventStart = CreateEvent(NULL, TRUE, FALSE, CString(_T("MSInfoStart")) + strEvent);
::InitializeCriticalSection(&m_criticalsection); ::InitializeCriticalSection(&m_csCategoryRefreshing); }
//-----------------------------------------------------------------------------
// The destructor should stop a refresh and get rid of the events.
//-----------------------------------------------------------------------------
CRefreshThread::~CRefreshThread() { KillRefresh(); DeleteCriticalSection(&m_criticalsection); DeleteCriticalSection(&m_csCategoryRefreshing); CloseHandle(m_eventDone); CloseHandle(m_eventStart); }
//-----------------------------------------------------------------------------
// Start the refresh thread for the specified category.
//-----------------------------------------------------------------------------
DWORD WINAPI ThreadRefresh(void * pArg); void CRefreshThread::StartRefresh(CMSInfoLiveCategory * pCategory, BOOL fRecursive, BOOL fForceRefresh) { CancelRefresh();
m_fRecursive = fRecursive; m_fForceRefresh = fForceRefresh; m_pcategory = pCategory; m_fCancel = FALSE; m_nCategoriesRefreshed = 0;
if (m_hThread == NULL) { ::ResetEvent(m_eventDone); ::ResetEvent(m_eventStart); m_hThread = ::CreateThread(NULL, 0, ThreadRefresh, (LPVOID) this, 0, &m_dwThreadID); } else { ::ResetEvent(m_eventDone); ::SetEvent(m_eventStart); } }
//-----------------------------------------------------------------------------
// Cancel the refresh in progress.
//-----------------------------------------------------------------------------
void CRefreshThread::CancelRefresh() { m_fCancel = TRUE; WaitForRefresh(); }
//-----------------------------------------------------------------------------
// Kill the refresh thread.
//-----------------------------------------------------------------------------
BOOL gfEndingSession = FALSE; void CRefreshThread::KillRefresh() { // If we're exiting normally, allow 30 seconds to finish WMI business, if
// the Windows session is ending, allow 5 seconds.
DWORD dwTimeout = (gfEndingSession) ? 5000 : 30000;
// Cancel the refresh, passing in the shorter timeout.
m_fCancel = TRUE; if (IsRefreshing()) ::WaitForSingleObject(m_eventDone, dwTimeout);
// Tell the thread to quit, wait the timeout to see if it does so before
// terminating it.
m_fQuit = TRUE; m_fCancel = TRUE; ::SetEvent(m_eventStart); if (WAIT_TIMEOUT == ::WaitForSingleObject(m_hThread, dwTimeout)) ::TerminateThread(m_hThread, 0);
::CloseHandle(m_hThread); m_hThread = NULL; }
//-----------------------------------------------------------------------------
// Is there currently a refresh going on?
//-----------------------------------------------------------------------------
BOOL CRefreshThread::IsRefreshing() { return (WAIT_TIMEOUT == ::WaitForSingleObject(m_eventDone, 0)); }
//-----------------------------------------------------------------------------
// Wait for the current refresh to finish.
//-----------------------------------------------------------------------------
BOOL CRefreshThread::WaitForRefresh() { if (IsRefreshing()) return (WAIT_TIMEOUT != ::WaitForSingleObject(m_eventDone, 600000));
return TRUE; }
//-----------------------------------------------------------------------------
// Check the WMI connection to the named computer. Useful for remoting.
//-----------------------------------------------------------------------------
HRESULT CRefreshThread::CheckWMIConnection() { HWND hwndTemp = m_hwnd;
m_pcategory = NULL; m_hwnd = NULL;
if (m_hThread == NULL) { ::ResetEvent(m_eventDone); ::ResetEvent(m_eventStart); m_hThread = ::CreateThread(NULL, 0, ThreadRefresh, (LPVOID) this, 0, &m_dwThreadID); }
WaitForRefresh(); m_hwnd = hwndTemp; return m_hrWMI; }
//-----------------------------------------------------------------------------
// This code runs in the worker thread which does the WMI queries. When it
// starts, it creates the WMI objects it will use. It then loops, doing
// the refreshes, until it's told to quit.
//
// TBD - need to know when to get rid of cached data.
//-----------------------------------------------------------------------------
DWORD WINAPI ThreadRefresh(void * pArg) { CRefreshThread * pParent = (CRefreshThread *) pArg; if (pParent == NULL) return 0;
CoInitialize(NULL);
// TBD
CWMILiveHelper * pWMI = new CWMILiveHelper(); HRESULT hrWMI = E_FAIL; if (pWMI) hrWMI = pWMI->Create(pParent->m_strMachine); pParent->m_hrWMI = hrWMI;
CMapPtrToPtr mapRefreshFuncToData; CPtrList lstCategoriesToRefresh; CMSInfoLiveCategory * pLiveCategory; CMSInfoLiveCategory * pChild; HRESULT hr; CString strCaption;
// Loop until it's indicated we should quit.
while (!pParent->m_fQuit) { // If there's a category pointer, then refresh the data for that category.
if (pParent->m_pcategory) { ASSERT(lstCategoriesToRefresh.IsEmpty()); // We use a list of categories to refresh (this allows us to do recursive refreshes).
// If the refresh isn't recursive, only one category will be put in the list.
lstCategoriesToRefresh.AddHead((void *) pParent->m_pcategory); while (!lstCategoriesToRefresh.IsEmpty()) { pLiveCategory = (CMSInfoLiveCategory *) lstCategoriesToRefresh.RemoveHead(); if (pLiveCategory == NULL) continue;
// Update the progress information on a multi-category refresh operation.
// This includes the number of categories refreshed and the name of the
// currently refreshing category (which is guarded by a critical section).
pLiveCategory->GetNames(&strCaption, NULL); ::EnterCriticalSection(&pParent->m_csCategoryRefreshing); pParent->m_nCategoriesRefreshed += 1; pParent->m_strCategoryRefreshing = strCaption; ::LeaveCriticalSection(&pParent->m_csCategoryRefreshing);
if (pLiveCategory->m_iColCount && pLiveCategory->m_pRefreshFunction) { // Refresh the data.
pLiveCategory->m_hrError = S_OK;
if (FAILED(hrWMI)) { pLiveCategory->m_hrError = hrWMI; pLiveCategory->m_dwLastRefresh = ::GetTickCount(); } else if (pLiveCategory->m_pRefreshFunction) { // Allocate the array of pointer lists which will contain the results
// of this refresh. Each pointer in the list will refer to a CMSIValue.
CPtrList * aptrList = new CPtrList[pLiveCategory->m_iColCount]; if (aptrList) { // Retrieve any refresh function specific storage that may have been created.
void * pRefreshData; if (!mapRefreshFuncToData.Lookup((void *)pLiveCategory->m_pRefreshFunction, pRefreshData)) pRefreshData = NULL;
// Call the refresh function for this category, with the refresh index.
hr = pLiveCategory->m_pRefreshFunction(pWMI, pLiveCategory->m_dwRefreshIndex, &pParent->m_fCancel, aptrList, pLiveCategory->m_iColCount, &pRefreshData); pLiveCategory->m_hrError = hr;
// If the refresh function allocated some storage, save it.
if (pRefreshData) mapRefreshFuncToData.SetAt((void *)pLiveCategory->m_pRefreshFunction, pRefreshData);
// If a long refresh time is needed for testing, uncomment the following:
//
// ::Sleep(5000 /* milliseconds */);
if (!pParent->m_fCancel && SUCCEEDED(pLiveCategory->m_hrError)) { // Get the number of rows of data.
int iRowCount = (int)aptrList[0].GetCount();
#ifdef _DEBUG
for (int i = 0; i < pLiveCategory->m_iColCount; i++) ASSERT(iRowCount == aptrList[i].GetCount()); #endif
// Update the category's current data. This has to be done in a
// critical section, since the main thread accesses this data.
pParent->EnterCriticalSection();
pLiveCategory->DeleteContent(); if (iRowCount) pLiveCategory->AllocateContent(iRowCount);
for (int j = 0; j < pLiveCategory->m_iColCount; j++) for (int i = 0; i < pLiveCategory->m_iRowCount; i++) if (!aptrList[j].IsEmpty()) { CMSIValue * pValue = (CMSIValue *) aptrList[j].RemoveHead(); pLiveCategory->SetData(i, j, pValue->m_strValue, pValue->m_dwValue); // Set the advanced flag for either the first column, or
// for any column which is advanced (any cell in a row
// being advanced makes the whole row advanced).
if (j == 0 || pValue->m_fAdvanced) pLiveCategory->SetAdvancedFlag(i, pValue->m_fAdvanced);
delete pValue; }
pParent->LeaveCriticalSection();
// Record the time this refresh was done.
pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount(); } else { // The refresh was cancelled or had an error - delete the new data. If the
// refresh had an error, record the time the refresh was attempted.
if (FAILED(pLiveCategory->m_hrError)) pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount(); }
for (int iCol = 0; iCol < pLiveCategory->m_iColCount; iCol++) while (!aptrList[iCol].IsEmpty()) // shouldn't be true unless refresh cancelled
delete (CMSIValue *) aptrList[iCol].RemoveHead(); delete [] aptrList; } } } else { pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount(); }
// If this is a recursive refresh, then we should add all of the children of this
// category to the list of categories to be refreshed.
if (pParent->m_fRecursive) { pChild = (CMSInfoLiveCategory *) pLiveCategory->GetFirstChild(); while (pChild) { lstCategoriesToRefresh.AddTail((void *) pChild); pChild = (CMSInfoLiveCategory *) pChild->GetNextSibling(); } } } // while
} else if (pParent->m_pcategory) { // Record the time this refresh was done.
pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount(); }
// Signal the parent window that there's new data ready to be displayed.
// Do this even if cancelled, so old data will be shown.
if (pParent->m_hwnd && !pParent->m_fCancel) ::PostMessage(pParent->m_hwnd, WM_MSINFODATAREADY, 0, (LPARAM)pParent->m_pcategory);
::SetEvent(pParent->m_eventDone);
// Go to sleep until it's time to return to work.
::WaitForSingleObject(pParent->m_eventStart, INFINITE); ::ResetEvent(pParent->m_eventStart); ::ResetEvent(pParent->m_eventDone); }
// Deallocate an cached stuff saved by the refresh functions.
RefreshFunction pFunc; void * pCache;
for (POSITION pos = mapRefreshFuncToData.GetStartPosition(); pos;) { mapRefreshFuncToData.GetNextAssoc(pos, (void * &)pFunc, pCache); if (pFunc) pFunc(NULL, 0, NULL, NULL, 0, &pCache); } mapRefreshFuncToData.RemoveAll();
if (pWMI) delete pWMI; CoUninitialize(); return 0; }
|