windows-nt/Source/XPSP1/NT/admin/pchealth/sysinfo/control/refreshthread.cpp
2020-09-26 16:20:57 +08:00

393 lines
12 KiB
C++

//=============================================================================
// 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;
}