393 lines
12 KiB
C++
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;
|
|
}
|