Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
12 KiB

  1. //=============================================================================
  2. // Code used to manage threaded WMI refreshes.
  3. //=============================================================================
  4. #include "stdafx.h"
  5. #include "refreshthread.h"
  6. #include "wmilive.h"
  7. //-----------------------------------------------------------------------------
  8. // The constructor - create the events to manage the refresh thread.
  9. //-----------------------------------------------------------------------------
  10. CRefreshThread::CRefreshThread(HWND hwnd) :
  11. m_fCancel(FALSE), m_fQuit(FALSE), m_fRecursive(FALSE), m_fForceRefresh(FALSE), m_pcategory(NULL),
  12. m_hThread(NULL), m_dwThreadID(0), m_hwnd(hwnd), m_hrWMI(E_FAIL)
  13. {
  14. // Generate a system wide unique name for the events (in case there are multiple
  15. // instances of MSInfo running). If we can't generate a GUID for this, use the tick count.
  16. CString strEvent(_T(""));
  17. GUID guid;
  18. if (SUCCEEDED(::CoCreateGuid(&guid)))
  19. {
  20. LPOLESTR lpGUID;
  21. if (SUCCEEDED(StringFromCLSID(guid, &lpGUID)))
  22. {
  23. strEvent = lpGUID;
  24. CoTaskMemFree(lpGUID);
  25. }
  26. }
  27. if (strEvent.IsEmpty())
  28. strEvent.Format(_T("%08x"), ::GetTickCount());
  29. m_eventDone = CreateEvent(NULL, TRUE, TRUE, CString(_T("MSInfoDone")) + strEvent);
  30. m_eventStart = CreateEvent(NULL, TRUE, FALSE, CString(_T("MSInfoStart")) + strEvent);
  31. ::InitializeCriticalSection(&m_criticalsection);
  32. ::InitializeCriticalSection(&m_csCategoryRefreshing);
  33. }
  34. //-----------------------------------------------------------------------------
  35. // The destructor should stop a refresh and get rid of the events.
  36. //-----------------------------------------------------------------------------
  37. CRefreshThread::~CRefreshThread()
  38. {
  39. KillRefresh();
  40. DeleteCriticalSection(&m_criticalsection);
  41. DeleteCriticalSection(&m_csCategoryRefreshing);
  42. CloseHandle(m_eventDone);
  43. CloseHandle(m_eventStart);
  44. }
  45. //-----------------------------------------------------------------------------
  46. // Start the refresh thread for the specified category.
  47. //-----------------------------------------------------------------------------
  48. DWORD WINAPI ThreadRefresh(void * pArg);
  49. void CRefreshThread::StartRefresh(CMSInfoLiveCategory * pCategory, BOOL fRecursive, BOOL fForceRefresh)
  50. {
  51. CancelRefresh();
  52. m_fRecursive = fRecursive;
  53. m_fForceRefresh = fForceRefresh;
  54. m_pcategory = pCategory;
  55. m_fCancel = FALSE;
  56. m_nCategoriesRefreshed = 0;
  57. if (m_hThread == NULL)
  58. {
  59. ::ResetEvent(m_eventDone);
  60. ::ResetEvent(m_eventStart);
  61. m_hThread = ::CreateThread(NULL, 0, ThreadRefresh, (LPVOID) this, 0, &m_dwThreadID);
  62. }
  63. else
  64. {
  65. ::ResetEvent(m_eventDone);
  66. ::SetEvent(m_eventStart);
  67. }
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Cancel the refresh in progress.
  71. //-----------------------------------------------------------------------------
  72. void CRefreshThread::CancelRefresh()
  73. {
  74. m_fCancel = TRUE;
  75. WaitForRefresh();
  76. }
  77. //-----------------------------------------------------------------------------
  78. // Kill the refresh thread.
  79. //-----------------------------------------------------------------------------
  80. BOOL gfEndingSession = FALSE;
  81. void CRefreshThread::KillRefresh()
  82. {
  83. // If we're exiting normally, allow 30 seconds to finish WMI business, if
  84. // the Windows session is ending, allow 5 seconds.
  85. DWORD dwTimeout = (gfEndingSession) ? 5000 : 30000;
  86. // Cancel the refresh, passing in the shorter timeout.
  87. m_fCancel = TRUE;
  88. if (IsRefreshing())
  89. ::WaitForSingleObject(m_eventDone, dwTimeout);
  90. // Tell the thread to quit, wait the timeout to see if it does so before
  91. // terminating it.
  92. m_fQuit = TRUE;
  93. m_fCancel = TRUE;
  94. ::SetEvent(m_eventStart);
  95. if (WAIT_TIMEOUT == ::WaitForSingleObject(m_hThread, dwTimeout))
  96. ::TerminateThread(m_hThread, 0);
  97. ::CloseHandle(m_hThread);
  98. m_hThread = NULL;
  99. }
  100. //-----------------------------------------------------------------------------
  101. // Is there currently a refresh going on?
  102. //-----------------------------------------------------------------------------
  103. BOOL CRefreshThread::IsRefreshing()
  104. {
  105. return (WAIT_TIMEOUT == ::WaitForSingleObject(m_eventDone, 0));
  106. }
  107. //-----------------------------------------------------------------------------
  108. // Wait for the current refresh to finish.
  109. //-----------------------------------------------------------------------------
  110. BOOL CRefreshThread::WaitForRefresh()
  111. {
  112. if (IsRefreshing())
  113. return (WAIT_TIMEOUT != ::WaitForSingleObject(m_eventDone, 600000));
  114. return TRUE;
  115. }
  116. //-----------------------------------------------------------------------------
  117. // Check the WMI connection to the named computer. Useful for remoting.
  118. //-----------------------------------------------------------------------------
  119. HRESULT CRefreshThread::CheckWMIConnection()
  120. {
  121. HWND hwndTemp = m_hwnd;
  122. m_pcategory = NULL;
  123. m_hwnd = NULL;
  124. if (m_hThread == NULL)
  125. {
  126. ::ResetEvent(m_eventDone);
  127. ::ResetEvent(m_eventStart);
  128. m_hThread = ::CreateThread(NULL, 0, ThreadRefresh, (LPVOID) this, 0, &m_dwThreadID);
  129. }
  130. WaitForRefresh();
  131. m_hwnd = hwndTemp;
  132. return m_hrWMI;
  133. }
  134. //-----------------------------------------------------------------------------
  135. // This code runs in the worker thread which does the WMI queries. When it
  136. // starts, it creates the WMI objects it will use. It then loops, doing
  137. // the refreshes, until it's told to quit.
  138. //
  139. // TBD - need to know when to get rid of cached data.
  140. //-----------------------------------------------------------------------------
  141. DWORD WINAPI ThreadRefresh(void * pArg)
  142. {
  143. CRefreshThread * pParent = (CRefreshThread *) pArg;
  144. if (pParent == NULL)
  145. return 0;
  146. CoInitialize(NULL);
  147. // TBD
  148. CWMILiveHelper * pWMI = new CWMILiveHelper();
  149. HRESULT hrWMI = E_FAIL;
  150. if (pWMI)
  151. hrWMI = pWMI->Create(pParent->m_strMachine);
  152. pParent->m_hrWMI = hrWMI;
  153. CMapPtrToPtr mapRefreshFuncToData;
  154. CPtrList lstCategoriesToRefresh;
  155. CMSInfoLiveCategory * pLiveCategory;
  156. CMSInfoLiveCategory * pChild;
  157. HRESULT hr;
  158. CString strCaption;
  159. // Loop until it's indicated we should quit.
  160. while (!pParent->m_fQuit)
  161. {
  162. // If there's a category pointer, then refresh the data for that category.
  163. if (pParent->m_pcategory)
  164. {
  165. ASSERT(lstCategoriesToRefresh.IsEmpty());
  166. // We use a list of categories to refresh (this allows us to do recursive refreshes).
  167. // If the refresh isn't recursive, only one category will be put in the list.
  168. lstCategoriesToRefresh.AddHead((void *) pParent->m_pcategory);
  169. while (!lstCategoriesToRefresh.IsEmpty())
  170. {
  171. pLiveCategory = (CMSInfoLiveCategory *) lstCategoriesToRefresh.RemoveHead();
  172. if (pLiveCategory == NULL)
  173. continue;
  174. // Update the progress information on a multi-category refresh operation.
  175. // This includes the number of categories refreshed and the name of the
  176. // currently refreshing category (which is guarded by a critical section).
  177. pLiveCategory->GetNames(&strCaption, NULL);
  178. ::EnterCriticalSection(&pParent->m_csCategoryRefreshing);
  179. pParent->m_nCategoriesRefreshed += 1;
  180. pParent->m_strCategoryRefreshing = strCaption;
  181. ::LeaveCriticalSection(&pParent->m_csCategoryRefreshing);
  182. if (pLiveCategory->m_iColCount && pLiveCategory->m_pRefreshFunction)
  183. {
  184. // Refresh the data.
  185. pLiveCategory->m_hrError = S_OK;
  186. if (FAILED(hrWMI))
  187. {
  188. pLiveCategory->m_hrError = hrWMI;
  189. pLiveCategory->m_dwLastRefresh = ::GetTickCount();
  190. }
  191. else if (pLiveCategory->m_pRefreshFunction)
  192. {
  193. // Allocate the array of pointer lists which will contain the results
  194. // of this refresh. Each pointer in the list will refer to a CMSIValue.
  195. CPtrList * aptrList = new CPtrList[pLiveCategory->m_iColCount];
  196. if (aptrList)
  197. {
  198. // Retrieve any refresh function specific storage that may have been created.
  199. void * pRefreshData;
  200. if (!mapRefreshFuncToData.Lookup((void *)pLiveCategory->m_pRefreshFunction, pRefreshData))
  201. pRefreshData = NULL;
  202. // Call the refresh function for this category, with the refresh index.
  203. hr = pLiveCategory->m_pRefreshFunction(pWMI,
  204. pLiveCategory->m_dwRefreshIndex,
  205. &pParent->m_fCancel,
  206. aptrList,
  207. pLiveCategory->m_iColCount,
  208. &pRefreshData);
  209. pLiveCategory->m_hrError = hr;
  210. // If the refresh function allocated some storage, save it.
  211. if (pRefreshData)
  212. mapRefreshFuncToData.SetAt((void *)pLiveCategory->m_pRefreshFunction, pRefreshData);
  213. // If a long refresh time is needed for testing, uncomment the following:
  214. //
  215. // ::Sleep(5000 /* milliseconds */);
  216. if (!pParent->m_fCancel && SUCCEEDED(pLiveCategory->m_hrError))
  217. {
  218. // Get the number of rows of data.
  219. int iRowCount = (int)aptrList[0].GetCount();
  220. #ifdef _DEBUG
  221. for (int i = 0; i < pLiveCategory->m_iColCount; i++)
  222. ASSERT(iRowCount == aptrList[i].GetCount());
  223. #endif
  224. // Update the category's current data. This has to be done in a
  225. // critical section, since the main thread accesses this data.
  226. pParent->EnterCriticalSection();
  227. pLiveCategory->DeleteContent();
  228. if (iRowCount)
  229. pLiveCategory->AllocateContent(iRowCount);
  230. for (int j = 0; j < pLiveCategory->m_iColCount; j++)
  231. for (int i = 0; i < pLiveCategory->m_iRowCount; i++)
  232. if (!aptrList[j].IsEmpty())
  233. {
  234. CMSIValue * pValue = (CMSIValue *) aptrList[j].RemoveHead();
  235. pLiveCategory->SetData(i, j, pValue->m_strValue, pValue->m_dwValue);
  236. // Set the advanced flag for either the first column, or
  237. // for any column which is advanced (any cell in a row
  238. // being advanced makes the whole row advanced).
  239. if (j == 0 || pValue->m_fAdvanced)
  240. pLiveCategory->SetAdvancedFlag(i, pValue->m_fAdvanced);
  241. delete pValue;
  242. }
  243. pParent->LeaveCriticalSection();
  244. // Record the time this refresh was done.
  245. pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount();
  246. }
  247. else
  248. {
  249. // The refresh was cancelled or had an error - delete the new data. If the
  250. // refresh had an error, record the time the refresh was attempted.
  251. if (FAILED(pLiveCategory->m_hrError))
  252. pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount();
  253. }
  254. for (int iCol = 0; iCol < pLiveCategory->m_iColCount; iCol++)
  255. while (!aptrList[iCol].IsEmpty()) // shouldn't be true unless refresh cancelled
  256. delete (CMSIValue *) aptrList[iCol].RemoveHead();
  257. delete [] aptrList;
  258. }
  259. }
  260. }
  261. else
  262. {
  263. pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount();
  264. }
  265. // If this is a recursive refresh, then we should add all of the children of this
  266. // category to the list of categories to be refreshed.
  267. if (pParent->m_fRecursive)
  268. {
  269. pChild = (CMSInfoLiveCategory *) pLiveCategory->GetFirstChild();
  270. while (pChild)
  271. {
  272. lstCategoriesToRefresh.AddTail((void *) pChild);
  273. pChild = (CMSInfoLiveCategory *) pChild->GetNextSibling();
  274. }
  275. }
  276. } // while
  277. }
  278. else if (pParent->m_pcategory)
  279. {
  280. // Record the time this refresh was done.
  281. pParent->m_pcategory->m_dwLastRefresh = ::GetTickCount();
  282. }
  283. // Signal the parent window that there's new data ready to be displayed.
  284. // Do this even if cancelled, so old data will be shown.
  285. if (pParent->m_hwnd && !pParent->m_fCancel)
  286. ::PostMessage(pParent->m_hwnd, WM_MSINFODATAREADY, 0, (LPARAM)pParent->m_pcategory);
  287. ::SetEvent(pParent->m_eventDone);
  288. // Go to sleep until it's time to return to work.
  289. ::WaitForSingleObject(pParent->m_eventStart, INFINITE);
  290. ::ResetEvent(pParent->m_eventStart);
  291. ::ResetEvent(pParent->m_eventDone);
  292. }
  293. // Deallocate an cached stuff saved by the refresh functions.
  294. RefreshFunction pFunc;
  295. void * pCache;
  296. for (POSITION pos = mapRefreshFuncToData.GetStartPosition(); pos;)
  297. {
  298. mapRefreshFuncToData.GetNextAssoc(pos, (void * &)pFunc, pCache);
  299. if (pFunc)
  300. pFunc(NULL, 0, NULL, NULL, 0, &pCache);
  301. }
  302. mapRefreshFuncToData.RemoveAll();
  303. if (pWMI)
  304. delete pWMI;
  305. CoUninitialize();
  306. return 0;
  307. }