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.

1497 lines
40 KiB

  1. /*++
  2. Copyright (C) 1995-2001 Microsoft Corporation
  3. Module Name:
  4. PROVPERF.CPP
  5. Abstract:
  6. Defines the acutal "Put" and "Get" functions for the
  7. performance counter provider. The format of the mapping
  8. string is;
  9. machine|Object|counter[|instance]
  10. Examples;
  11. local|memory|available bytes
  12. a-davj2|LogicalDisk|Free Megabytes|C:
  13. History:
  14. a-davj 9-27-95 Created.
  15. --*/
  16. #include "precomp.h"
  17. #include <tchar.h>
  18. #include "provperf.h"
  19. #include "cvariant.h"
  20. // maximum amount of time to wait for exclusive access
  21. #define MAX_EXEC_WAIT 5000
  22. //***************************************************************************
  23. //
  24. // AddTesterDetails
  25. //
  26. // DESCRIPTION:
  27. //
  28. // This function is used add the counter type to the property and is useful
  29. // to wbem testers. Normal users dont want the overhead caused by this.
  30. //
  31. // PARAMETERS:
  32. //
  33. // pClassInt Object being refreshed
  34. // PropName Property Name
  35. // dwCtrType counter type
  36. //
  37. // RETURN VALUE:
  38. //
  39. // always 0
  40. //
  41. //***************************************************************************
  42. void AddTesterDetails(IWbemClassObject FAR * pClassInt,BSTR PropName,DWORD dwCtrType)
  43. {
  44. // Get the qualifier pointer for the property
  45. IWbemQualifierSet * pQualifier = NULL;
  46. // Get an Qualifier set interface.
  47. SCODE sc = pClassInt->GetPropertyQualifierSet(PropName,&pQualifier); // Get prop attribute
  48. if(FAILED(sc))
  49. return;
  50. WCHAR wcName[40];
  51. switch(dwCtrType)
  52. {
  53. case PERF_COUNTER_COUNTER:
  54. wcsncpy(wcName,L"PERF_COUNTER_COUNTER", 39);
  55. break;
  56. case PERF_COUNTER_TIMER:
  57. wcsncpy(wcName,L"PERF_COUNTER_TIMER", 39);
  58. break;
  59. case PERF_COUNTER_QUEUELEN_TYPE:
  60. wcsncpy(wcName,L"PERF_COUNTER_QUEUELEN_TYPE", 39);
  61. break;
  62. case PERF_COUNTER_LARGE_QUEUELEN_TYPE:
  63. wcsncpy(wcName,L"PERF_COUNTER_LARGE_QUEUELEN_TYPE", 39);
  64. break;
  65. case PERF_COUNTER_BULK_COUNT:
  66. wcsncpy(wcName,L"PERF_COUNTER_BULK_COUNT", 39);
  67. break;
  68. case PERF_COUNTER_TEXT:
  69. wcsncpy(wcName,L"PERF_COUNTER_TEXT", 39);
  70. break;
  71. case PERF_COUNTER_RAWCOUNT:
  72. wcsncpy(wcName,L"PERF_COUNTER_RAWCOUNT", 39);
  73. break;
  74. case PERF_COUNTER_LARGE_RAWCOUNT:
  75. wcsncpy(wcName,L"PERF_COUNTER_LARGE_RAWCOUNT", 39);
  76. break;
  77. case PERF_COUNTER_RAWCOUNT_HEX:
  78. wcsncpy(wcName,L"PERF_COUNTER_RAWCOUNT_HEX", 39);
  79. break;
  80. case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
  81. wcsncpy(wcName,L"PERF_COUNTER_LARGE_RAWCOUNT_HEX", 39);
  82. break;
  83. case PERF_SAMPLE_FRACTION:
  84. wcsncpy(wcName,L"PERF_SAMPLE_FRACTION", 39);
  85. break;
  86. case PERF_SAMPLE_COUNTER:
  87. wcsncpy(wcName,L"PERF_SAMPLE_COUNTER", 39);
  88. break;
  89. case PERF_COUNTER_NODATA:
  90. wcsncpy(wcName,L"PERF_COUNTER_NODATA", 39);
  91. break;
  92. case PERF_COUNTER_TIMER_INV:
  93. wcsncpy(wcName,L"PERF_COUNTER_TIMER_INV", 39);
  94. break;
  95. case PERF_SAMPLE_BASE:
  96. wcsncpy(wcName,L"PERF_SAMPLE_BASE", 39);
  97. break;
  98. case PERF_AVERAGE_TIMER:
  99. wcsncpy(wcName,L"PERF_AVERAGE_TIMER", 39);
  100. break;
  101. case PERF_AVERAGE_BASE:
  102. wcsncpy(wcName,L"PERF_AVERAGE_BASE", 39);
  103. break;
  104. case PERF_AVERAGE_BULK:
  105. wcsncpy(wcName,L"PERF_AVERAGE_BULK", 39);
  106. break;
  107. case PERF_100NSEC_TIMER:
  108. wcsncpy(wcName,L"PERF_100NSEC_TIMER", 39);
  109. break;
  110. case PERF_100NSEC_TIMER_INV:
  111. wcsncpy(wcName,L"PERF_100NSEC_TIMER_INV", 39);
  112. break;
  113. case PERF_COUNTER_MULTI_TIMER:
  114. wcsncpy(wcName,L"PERF_COUNTER_MULTI_TIMER", 39);
  115. break;
  116. case PERF_COUNTER_MULTI_TIMER_INV:
  117. wcsncpy(wcName,L"PERF_COUNTER_MULTI_TIMER_INV", 39);
  118. break;
  119. case PERF_COUNTER_MULTI_BASE:
  120. wcsncpy(wcName,L"PERF_COUNTER_MULTI_BASE", 39);
  121. break;
  122. case PERF_100NSEC_MULTI_TIMER:
  123. wcsncpy(wcName,L"PERF_100NSEC_MULTI_TIMER", 39);
  124. break;
  125. case PERF_100NSEC_MULTI_TIMER_INV:
  126. wcsncpy(wcName,L"PERF_100NSEC_MULTI_TIMER_INV", 39);
  127. break;
  128. case PERF_RAW_FRACTION:
  129. wcsncpy(wcName,L"PERF_RAW_FRACTION", 39);
  130. break;
  131. case PERF_RAW_BASE:
  132. wcsncpy(wcName,L"PERF_RAW_BASE", 39);
  133. break;
  134. case PERF_ELAPSED_TIME:
  135. wcsncpy(wcName,L"PERF_ELAPSED_TIME", 39);
  136. break;
  137. case PERF_COUNTER_HISTOGRAM_TYPE:
  138. wcsncpy(wcName,L"PERF_COUNTER_HISTOGRAM_TYPE", 39);
  139. break;
  140. case PERF_COUNTER_DELTA:
  141. wcsncpy(wcName,L"PERF_COUNTER_DELTA", 39);
  142. break;
  143. case PERF_COUNTER_LARGE_DELTA:
  144. wcsncpy(wcName,L"PERF_COUNTER_LARGE_DELTA", 39);
  145. break;
  146. default:
  147. swprintf(wcName,L"0x%x", dwCtrType);
  148. }
  149. wcName[39] = 0;
  150. CVariant var(wcName);
  151. BSTR bstr = SysAllocString(L"CounterType");
  152. if(bstr)
  153. {
  154. sc = pQualifier->Put(bstr, var.GetVarPtr(), 0);
  155. SysFreeString(bstr);
  156. }
  157. pQualifier->Release();
  158. }
  159. //***************************************************************************
  160. //
  161. // CImpPerf::CImpPerf
  162. //
  163. // DESCRIPTION:
  164. //
  165. // Constuctor.
  166. //
  167. // PARAMETERS:
  168. //
  169. //***************************************************************************
  170. CImpPerf::CImpPerf()
  171. {
  172. wcscpy(wcCLSID,L"{F00B4404-F8F1-11CE-A5B6-00AA00680C3F}");
  173. sMachine = TEXT("local");
  174. hKeyMachine = HKEY_LOCAL_MACHINE;
  175. dwLastTimeUsed = 0;
  176. hKeyPerf = HKEY_PERFORMANCE_DATA;
  177. TitleBuffer = NULL;
  178. hExec = CreateMutex(NULL, false, TEXT("WbemPerformanceDataMutex"));
  179. m_hTermEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
  180. return;
  181. }
  182. //***************************************************************************
  183. //
  184. // CImpPerf::~CImpPerf
  185. //
  186. // DESCRIPTION:
  187. //
  188. // Destructor.
  189. //
  190. //***************************************************************************
  191. CImpPerf::~CImpPerf()
  192. {
  193. bool bGotMutex = false;
  194. if(hExec)
  195. {
  196. DWORD dwRet = WaitForSingleObject(hExec,2*MAX_EXEC_WAIT);
  197. if(dwRet == WAIT_OBJECT_0 || dwRet == WAIT_ABANDONED)
  198. bGotMutex = true;
  199. }
  200. if(bGotMutex)
  201. ReleaseMutex(hExec);
  202. FreeStuff();
  203. sMachine.Empty();
  204. if(hExec)
  205. CloseHandle(hExec);
  206. if(m_hTermEvent)
  207. CloseHandle(m_hTermEvent);
  208. }
  209. //***************************************************************************
  210. //
  211. // SCODE CImpPerf::LoadData
  212. //
  213. // DESCRIPTION:
  214. //
  215. // Loads up the perf monitor data.
  216. //
  217. // PARAMETERS:
  218. //
  219. // ProvObj Object containing the property context string.
  220. // pls Where to put the data
  221. // piObject Identifies the perf mon object
  222. // piCounter Identifies the perf mon counter
  223. // **ppNew Created data block
  224. // bJustGettingInstances Flag which indicates that we are actully
  225. // looking for the instance names.
  226. //
  227. // RETURN VALUE:
  228. //
  229. // WBEM_E_INVALID_PARAMETER Bad context string
  230. // WBEM_E_OUT_OF_MEMORY low memory
  231. // otherwise error from called function
  232. //
  233. //***************************************************************************
  234. SCODE CImpPerf::LoadData(
  235. CProvObj & ProvObj,
  236. LINESTRUCT * pls,
  237. int * piObject,
  238. int * piCounter,
  239. PERF_DATA_BLOCK **ppNew,
  240. BOOL bJustGettingInstances)
  241. {
  242. SCODE sc;
  243. BOOL bChange;
  244. if( ( ProvObj.sGetToken(0) == NULL ) || ( piObject == NULL ) || ( piCounter == NULL ) )
  245. return WBEM_E_INVALID_PARAMETER; //BAD MAPPING STRING
  246. // Determine if there has been a change in the machine being
  247. // accessed. Save the current machine and get the handles if
  248. // there was a change.
  249. bChange = lstrcmpi(sMachine,ProvObj.sGetToken(0));
  250. sMachine = ProvObj.sGetToken(0);
  251. if(bChange)
  252. {
  253. sc = dwGetRegHandles(ProvObj.sGetToken(0));
  254. if(sc != S_OK)
  255. return sc;
  256. }
  257. // build up a table of the performance strings and
  258. // their corresponding indexes. This only needs to be done
  259. // when the buffer is empty or when the machine changes.
  260. if(bChange || TitleBuffer == NULL)
  261. {
  262. sc = GetPerfTitleSz ();
  263. if(sc != S_OK)
  264. return sc;
  265. }
  266. // get the indexs for the object and counter names
  267. dwLastTimeUsed = GetCurrentTime();
  268. *piObject = iGetTitleIndex(ProvObj.sGetToken(1));
  269. if(bJustGettingInstances)
  270. *piCounter = 0;
  271. else
  272. *piCounter = iGetTitleIndex(ProvObj.sGetToken(2));
  273. if(*piObject == -1 || *piCounter == -1)
  274. {
  275. return WBEM_E_INVALID_PARAMETER; // bad mapping string
  276. }
  277. // Using the index for the object, get the perf counter data
  278. // data.
  279. sc = Cache.dwGetNew(ProvObj.sGetToken(0),*piObject,(LPSTR *)ppNew,pls);
  280. return sc;
  281. }
  282. //***************************************************************************
  283. //
  284. // SCODE CImpPerf::RefreshProperty
  285. //
  286. // DESCRIPTION:
  287. //
  288. // Gets the value of a single property from the NT performance
  289. // counter data.
  290. //
  291. // PARAMETERS:
  292. //
  293. // lFlags flags. Not currently used
  294. // pClassInt Instance object
  295. // PropName Property name
  296. // ProvObj Object containing the property context string.
  297. // pPackage Caching object
  298. // pVar Points to value to set
  299. // bTesterDetails Provide extra info for testers
  300. // RETURN VALUE:
  301. //
  302. // S_OK all is well
  303. // else probably set by LoadData or FindData.
  304. //
  305. //***************************************************************************
  306. SCODE CImpPerf::RefreshProperty(
  307. IN long lFlags,
  308. IN IWbemClassObject FAR * pClassInt,
  309. IN BSTR PropName,
  310. IN CProvObj & ProvObj,
  311. OUT IN CObject * pPackage,
  312. OUT CVariant * pVar, BOOL bTesterDetails)
  313. {
  314. DWORD dwCtrType;
  315. float fRet;
  316. SCODE sc;
  317. int iObject,iCounter;
  318. PERF_DATA_BLOCK * pNew, * pOld;
  319. DWORD dwSize;
  320. LINESTRUCT ls;
  321. void * pCountData, *pIgnore;
  322. CVariant vPerf;
  323. // The perf counter provider keeps some rather expensive data and
  324. // so it doesnt support complete reentrancy.
  325. if(hExec)
  326. {
  327. DWORD dwRet;
  328. dwRet = WaitForSingleObject(hExec,MAX_EXEC_WAIT);
  329. if(dwRet != WAIT_ABANDONED && dwRet != WAIT_OBJECT_0)
  330. return WBEM_E_FAILED;
  331. }
  332. else
  333. return WBEM_E_FAILED;
  334. // Load up the data
  335. sc = LoadData(ProvObj,&ls,&iObject,&iCounter,&pNew,FALSE);
  336. if(sc != S_OK)
  337. goto Done;
  338. // Find the desired data.
  339. sc = FindData(pNew,iObject,iCounter,ProvObj,&dwSize,&pCountData,
  340. &ls,TRUE,NULL); // find data sets the error in pMo!
  341. if(sc != S_OK)
  342. goto Done;
  343. // determine what type of counter it is
  344. dwCtrType = ls.lnCounterType & 0xc00;
  345. if(dwCtrType == PERF_TYPE_COUNTER)
  346. {
  347. // This type of counter requires time average data. Get the cache to
  348. // get two buffers which are separated by a minimum amount of time
  349. sc = Cache.dwGetPair(ProvObj.sGetToken(0),iObject,
  350. (LPSTR *)&pOld,(LPSTR *)&pNew,&ls);
  351. if(sc != S_OK)
  352. goto Done;
  353. sc = FindData(pNew,iObject,iCounter,ProvObj,&dwSize,&pCountData,&ls,TRUE,NULL);
  354. if(sc != S_OK)
  355. goto Done;
  356. sc = FindData(pOld,iObject,iCounter,ProvObj,&dwSize,&pIgnore,&ls,FALSE,NULL);
  357. if(sc != S_OK)
  358. goto Done;
  359. fRet = CounterEntry(&ls);
  360. vPerf.SetData(&fRet,VT_R4);
  361. }
  362. else if(dwCtrType == PERF_TYPE_NUMBER)
  363. {
  364. // Simple counter.
  365. fRet = CounterEntry(&ls);
  366. vPerf.SetData(&fRet,VT_R4);
  367. }
  368. else if(dwCtrType == PERF_TYPE_TEXT)
  369. {
  370. // Text. Allocate enough space to hold the text and
  371. // copy the text into temp WCHAR buffer since it is not
  372. // clear from the documentation if the data in the block
  373. // is null terminated.
  374. WCHAR * pNew = (WCHAR *)CoTaskMemAlloc(dwSize+2);
  375. if(pNew == NULL)
  376. {
  377. sc = WBEM_E_OUT_OF_MEMORY;
  378. goto Done;
  379. }
  380. memset(pNew,0,dwSize+2);
  381. if(ls.lnCounterType & 0x10000)
  382. mbstowcs(pNew,(char *)pCountData,dwSize);
  383. else
  384. memcpy(pNew,pCountData,dwSize);
  385. VARIANT * pVar = vPerf.GetVarPtr();
  386. VariantClear(pVar);
  387. pVar->vt = VT_BSTR;
  388. pVar->bstrVal = SysAllocString(pNew);
  389. if(pVar->bstrVal == NULL)
  390. sc = WBEM_E_OUT_OF_MEMORY;
  391. CoTaskMemFree(pNew);
  392. if(sc != S_OK)
  393. {
  394. goto Done;
  395. }
  396. }
  397. // Convert the data into the desired form
  398. sc = vPerf.DoPut(lFlags,pClassInt,PropName,pVar);
  399. if(bTesterDetails)
  400. AddTesterDetails(pClassInt, PropName, dwCtrType);
  401. Done:
  402. if(hExec)
  403. ReleaseMutex(hExec);
  404. return sc;
  405. }
  406. //***************************************************************************
  407. //
  408. // SCODE CImpPerf::UpdateProperty
  409. //
  410. // DESCRIPTION:
  411. //
  412. // Normally this routine is used to save properties, but NT
  413. // performance counter data is Read only.
  414. //
  415. // PARAMETERS:
  416. //
  417. // lFlags N/A
  418. // pClassInt N/A
  419. // PropName N/A
  420. // ProvObj N/A
  421. // pPackage N/A
  422. // pVar N/A
  423. //
  424. // RETURN VALUE:
  425. //
  426. // E_NOTIMPL
  427. //
  428. //***************************************************************************
  429. SCODE CImpPerf::UpdateProperty(
  430. long lFlags,
  431. IWbemClassObject FAR * pClassInt,
  432. BSTR PropName,
  433. CProvObj & ProvObj,
  434. CObject * pPackage,
  435. CVariant * pVar)
  436. {
  437. return E_NOTIMPL;
  438. }
  439. //***************************************************************************
  440. //
  441. // void CImpPerf::FreeStuff
  442. //
  443. // DESCRIPTION:
  444. //
  445. // Used to free up memory that is no longer needed as well as
  446. // freeing up registry handles.
  447. //
  448. //***************************************************************************
  449. void CImpPerf::FreeStuff(void)
  450. {
  451. if(hKeyMachine != HKEY_LOCAL_MACHINE)
  452. {
  453. RegCloseKey(hKeyMachine);
  454. hKeyMachine = NULL;
  455. }
  456. if(hKeyPerf != HKEY_PERFORMANCE_DATA)
  457. {
  458. RegCloseKey(hKeyPerf);
  459. hKeyPerf = NULL;
  460. }
  461. if(TitleBuffer)
  462. {
  463. delete TitleBuffer;
  464. TitleBuffer = NULL;
  465. }
  466. m_IndexCache.Empty();
  467. return;
  468. }
  469. //***************************************************************************
  470. //
  471. // DWORD CImpPerf::GetPerfTitleSz
  472. //
  473. // DESCRIPTION:
  474. //
  475. // Retrieves the performance data title strings.
  476. // This call retrieves english version of the title strings.
  477. //
  478. // RETURN VALUE:
  479. //
  480. // 0 if OK
  481. // WBEM_E_OUT_OF_MEMORY if low memory
  482. // else set by RegOpenKeyEx
  483. //
  484. //***************************************************************************
  485. DWORD CImpPerf::GetPerfTitleSz ()
  486. {
  487. HKEY hKey1;
  488. DWORD Type;
  489. DWORD DataSize;
  490. DWORD dwR;
  491. // Free any existing stuff
  492. if(TitleBuffer)
  493. {
  494. delete TitleBuffer;
  495. TitleBuffer = NULL;
  496. }
  497. m_IndexCache.Empty();
  498. // Initialize
  499. //
  500. hKey1 = NULL;
  501. // Open the perflib key to find out the last counter's index and system version.
  502. // char cRegPath[100];
  503. // DWORD dwLoc = GetUserDefaultLCID();
  504. // dwLoc &= 0x3ff; // mask off the high word
  505. // wsprintf(cRegPath,"software\\microsoft\\windows nt\\currentversion\\perflib\\%03x",
  506. // dwLoc);
  507. // dwR = RegOpenKeyEx (hKeyMachine,
  508. // cRegPath,
  509. // 0,
  510. // KEY_READ,
  511. // &hKey1);
  512. // if (dwR != S_OK)
  513. // {
  514. dwR = RegOpenKeyEx (hKeyMachine,
  515. TEXT("software\\microsoft\\windows nt\\currentversion\\perflib\\009"),
  516. 0,
  517. KEY_READ,
  518. &hKey1);
  519. // }
  520. if (dwR != S_OK)
  521. goto done;
  522. // Find out the size of the data.
  523. //
  524. dwR = RegQueryValueEx (hKey1, TEXT("Counter"),
  525. 0, &Type, 0, &DataSize);
  526. if (dwR != S_OK)
  527. goto done;
  528. // Allocate memory
  529. //
  530. TitleBuffer = new TCHAR[DataSize];
  531. if (!TitleBuffer)
  532. {
  533. dwR = WBEM_E_OUT_OF_MEMORY;
  534. goto done;
  535. }
  536. // Query the data
  537. //
  538. dwR = RegQueryValueEx (hKey1, TEXT("Counter"),
  539. 0, &Type, (BYTE *)TitleBuffer, &DataSize);
  540. done:
  541. if(dwR == 5)
  542. dwR = WBEM_E_ACCESS_DENIED;
  543. if(hKey1 != NULL)
  544. RegCloseKey(hKey1);
  545. return dwR;
  546. }
  547. //***************************************************************************
  548. //
  549. // DWORD CImpPerf::dwGetRegHandles
  550. //
  551. // DESCRIPTION:
  552. //
  553. // Sets the handles for the local computer and the performance
  554. // information.
  555. //
  556. // PARAMETERS:
  557. //
  558. // pMachine Machine name
  559. //
  560. // RETURN VALUE:
  561. //
  562. // S_OK all is well
  563. // otherwise return is from RegConnectRegistry
  564. //***************************************************************************
  565. DWORD CImpPerf::dwGetRegHandles(
  566. const TCHAR * pMachine)
  567. {
  568. DWORD dwRet;
  569. TCHAR pTemp[256];
  570. if(pMachine == NULL)
  571. return WBEM_E_INVALID_PARAMETER;
  572. lstrcpy(pTemp,pMachine);
  573. // if the current handles are to a remote machine, then free them
  574. if(!lstrcmpi(sMachine,TEXT("local")))
  575. {
  576. if(hKeyPerf && hKeyPerf != HKEY_PERFORMANCE_DATA)
  577. RegCloseKey(hKeyPerf);
  578. if(hKeyMachine)
  579. RegCloseKey(hKeyMachine);
  580. hKeyPerf = hKeyMachine = NULL;
  581. }
  582. // Determine if the target is remote or local
  583. if(lstrcmpi(pMachine,TEXT("local")))
  584. {
  585. // Remote, connect up
  586. dwRet = RegConnectRegistry(pTemp,HKEY_PERFORMANCE_DATA,
  587. &hKeyPerf);
  588. if(dwRet != S_OK) // could not remote connect
  589. return dwRet;
  590. dwRet = RegConnectRegistry(pTemp,HKEY_LOCAL_MACHINE,
  591. &hKeyMachine);
  592. if(dwRet != S_OK)
  593. {
  594. RegCloseKey(hKeyPerf);
  595. hKeyPerf = hKeyMachine = NULL;
  596. return dwRet;
  597. }
  598. }
  599. else
  600. {
  601. hKeyMachine = HKEY_LOCAL_MACHINE;
  602. hKeyPerf = HKEY_PERFORMANCE_DATA;
  603. }
  604. return 0;
  605. }
  606. //***************************************************************************
  607. //
  608. // int CImpPerf::iGetTitleIndex
  609. //
  610. // DESCRIPTION:
  611. //
  612. // Looks for the name in the buffer containing the names and
  613. // returns the index. The buffer is a series of strings with a double
  614. // null at the end. Each counter or object is represented by a pair of
  615. // strings with the first having the number and the second having the
  616. // text. This code goes through the pairs, storing the number string and
  617. // checking the text vs the input. If a match, then the number is returned.
  618. //
  619. // PARAMETERS:
  620. //
  621. // pSearch String to be found in buffer
  622. //
  623. // RETURN VALUE:
  624. //
  625. // integer that goes with the string. -1 if not found
  626. //
  627. //***************************************************************************
  628. int CImpPerf::iGetTitleIndex(
  629. const TCHAR * pSearch)
  630. {
  631. DWORD Index;
  632. int Len;
  633. TCHAR * pIndex;
  634. if(pSearch == NULL)
  635. return -1;
  636. Index = m_IndexCache.Find(pSearch);
  637. if(Index != -1)
  638. return Index;
  639. int iRet = -1;
  640. LPTSTR szTitle = TitleBuffer;
  641. while (Len = lstrlen (szTitle))
  642. {
  643. pIndex = szTitle; // save pointer to number
  644. szTitle = szTitle + Len +1; // skip to name
  645. if (pSearch != NULL && !lstrcmpi(pSearch,szTitle))
  646. { // found string!
  647. Index = _ttoi (pIndex); // get number
  648. m_IndexCache.Add(szTitle, Index);
  649. if(iRet == -1)
  650. iRet = Index;
  651. }
  652. szTitle = szTitle + lstrlen (szTitle) +1; // skip to next number
  653. }
  654. return iRet;
  655. }
  656. //***************************************************************************
  657. //
  658. // SCODE CImpPerf::FindData
  659. //
  660. // DESCRIPTION:
  661. //
  662. // Finds the counter in the data block. Note that the steps are quite
  663. // involved and an understanding of the structure of performance data
  664. // is probably required. See chap 66 of the Win32 Programmers Ref.
  665. //
  666. //
  667. // PARAMETERS:
  668. //
  669. // pData Data block to be searched
  670. // iObj Int which identifies the object
  671. // iCount Int which identifies the counter
  672. // ProvObj Object containing the parsed context string
  673. // pdwSize Size of data
  674. // **ppRetData points to data
  675. // pls Line structure
  676. // bNew If true, indicates that we are searching the newest
  677. // sample of data.
  678. // pInfo If set, points to an collection object which
  679. // contains a list of instance names. By being set
  680. // the function doesnt look for actual data, instead
  681. // it is used just to get the instance names.
  682. //
  683. // RETURN VALUE:
  684. //
  685. // S_OK all is well
  686. // WBEM_E_FAILED couldnt find the data in the block
  687. //
  688. //***************************************************************************
  689. SCODE CImpPerf::FindData(
  690. IN PERF_DATA_BLOCK * pData,
  691. IN int iObj,
  692. IN int iCount,
  693. IN CProvObj & ProvObj,
  694. OUT DWORD * pdwSize,
  695. OUT void **ppRetData,
  696. OUT PLINESTRUCT pls,
  697. IN BOOL bNew,
  698. OUT CEnumPerfInfo * pInfo)
  699. {
  700. try
  701. {
  702. int iIndex;
  703. BOOL bEqual;
  704. DWORD dwSize = 0;
  705. DWORD dwType,dwTypeBase = 0;
  706. *ppRetData = NULL;
  707. void * pVoid = NULL, * pVoidBase = NULL;
  708. PPERF_OBJECT_TYPE pObj = NULL;
  709. PPERF_COUNTER_DEFINITION pCount = NULL;
  710. PPERF_COUNTER_DEFINITION pCountBase= NULL;
  711. PPERF_INSTANCE_DEFINITION pInst = NULL;
  712. // Some objects, such as disks, have what are called instances and in
  713. // that case the provider string will have an extra token with the
  714. // instance name in it.
  715. WCHAR wInstName[MAX_PATH];
  716. wInstName[0] = 0;
  717. WCHAR * pwInstName = wInstName;
  718. long lDuplicateNum = 0;
  719. // If there is an instance name, convert it to WCHAR. Also, the
  720. // instance name may be of the for "[123]chars" and in this case the
  721. // didits between "[]" are converted to a number and the actual name
  722. // starts after the ']'.
  723. if(ProvObj.iGetNumTokens() > 3)
  724. {
  725. if(lstrlen(ProvObj.sGetToken(3)) > MAX_PATH -1)
  726. return WBEM_E_FAILED;
  727. #ifdef UNICODE
  728. wcscpy(wInstName, ProvObj.sGetToken(3));
  729. #else
  730. mbstowcs(wInstName, ProvObj.sGetToken(3), MAX_PATH-1);
  731. #endif
  732. if(wInstName[0] == L'[')
  733. {
  734. lDuplicateNum = _wtol(&wInstName[1]);
  735. for(pwInstName = &wInstName[1]; *pwInstName && *pwInstName != L']';
  736. pwInstName++); // INTENTIONAL SEMI!
  737. if(*pwInstName == L']')
  738. pwInstName++;
  739. }
  740. }
  741. else
  742. {
  743. // if there is not an instance name and the argument for enumeration is null, then we have a
  744. // bad path
  745. if(pInfo == NULL)
  746. return WBEM_E_INVALID_OBJECT_PATH;
  747. }
  748. // Go through the list of objects and find the one
  749. // that matches iObj
  750. pObj = (PPERF_OBJECT_TYPE)((PBYTE)pData + pData->HeaderLength);
  751. for(iIndex = 0; iIndex < (int)pData->NumObjectTypes; iIndex++)
  752. {
  753. if((int)pObj->ObjectNameTitleIndex == iObj)
  754. break; // found it!
  755. pObj = (PPERF_OBJECT_TYPE)((PBYTE)pObj + pObj->TotalByteLength);
  756. }
  757. if(iIndex == (int)pData->NumObjectTypes)
  758. return WBEM_E_FAILED; // never found object in the block
  759. // Object was found, set the object type data
  760. if(bNew)
  761. {
  762. pls->ObjPerfFreq = *(LONGLONG UNALIGNED *)(&pObj->PerfFreq);
  763. pls->ObjCounterTimeNew = *(LONGLONG UNALIGNED *)(&pObj->PerfTime);
  764. }
  765. else
  766. pls->ObjCounterTimeOld = *(LONGLONG UNALIGNED *)(&pObj->PerfTime);
  767. // Go through the list of counters for the object and find the one that
  768. // matches iCount. Note that some counter names may be have more than
  769. // one id. Therefore, try the other ids if the intial one doesnt work.
  770. bool bFound = false;
  771. bool bEndOfList = false;
  772. int lTry = 0; // how may times we have tried
  773. do
  774. {
  775. pCount = (PPERF_COUNTER_DEFINITION)((PBYTE)pObj + pObj->HeaderLength);
  776. for(iIndex = 0; iIndex < (int)pObj->NumCounters; iIndex++)
  777. {
  778. if((int)pCount->CounterNameTitleIndex == iCount || pInfo)
  779. {
  780. bFound = true;
  781. break; // found it!
  782. }
  783. pCount = (PPERF_COUNTER_DEFINITION)((PBYTE)pCount + pCount->ByteLength);
  784. }
  785. if(bFound == false)
  786. {
  787. lTry++;
  788. iCount = m_IndexCache.Find(ProvObj.sGetToken(2), lTry);
  789. if(iCount == -1)
  790. bEndOfList = true;
  791. }
  792. } while (bFound == false && bEndOfList == false);
  793. if(bFound == false)
  794. {
  795. return WBEM_E_FAILED; // never found object in the block
  796. }
  797. // The counter was found, save the counter information
  798. // If the counter is not the last one in the object, then the
  799. // next one might be the base which is used for certain calculations
  800. dwType = pCount->CounterType;
  801. pls->lnCounterType = pCount->CounterType;
  802. if(iIndex < (int)pObj->NumCounters - 1)
  803. {
  804. // might be the base
  805. pCountBase = (PPERF_COUNTER_DEFINITION)((PBYTE)pCount +
  806. pCount->ByteLength);
  807. dwTypeBase = pCountBase->CounterType;
  808. }
  809. // Get a pointer to the start of the perf counter block
  810. // There are two cases: If there are no instances, then
  811. // the data starts after the last counter descriptor.
  812. // If there are instances, each instance has it's own block.
  813. pVoid = NULL;
  814. if(pObj->NumInstances == -1)
  815. {
  816. // The object is a singleton
  817. if(pInfo) // If we are enumerating instances
  818. {
  819. pInfo->AddEntry(L"@");
  820. return S_OK;
  821. }
  822. // easy case, get offset into data, add offset
  823. // for particular counter.
  824. pVoid = (PBYTE)pObj + pObj->DefinitionLength
  825. + pCount->CounterOffset;
  826. if(pCountBase)
  827. pVoidBase = (PBYTE)pObj + pObj->DefinitionLength
  828. + pCountBase->CounterOffset;
  829. }
  830. else if(pObj->NumInstances > 0)
  831. {
  832. WCHAR wNum[12];
  833. // hard case, got a list of instaces, start off
  834. // by getting a pointer to the first one.
  835. long lNumDupsSoFar = 0;
  836. pInst= (PPERF_INSTANCE_DEFINITION)((PBYTE)pObj + pObj->DefinitionLength);
  837. for(iIndex = 0; iIndex < (int)pObj->NumInstances; iIndex++)
  838. {
  839. // Each instance has a unicode name, get it and
  840. // compare it against the name passed in the
  841. // provider string.
  842. PPERF_COUNTER_BLOCK pCtrBlk;
  843. WCHAR * pwName;
  844. if(pInst->UniqueID == PERF_NO_UNIQUE_ID)
  845. pwName = (WCHAR *)((PBYTE)pInst + pInst->NameOffset);
  846. else
  847. {
  848. _ltow (pInst->UniqueID, wNum, 10);
  849. pwName = wNum;
  850. }
  851. if(pInfo)
  852. {
  853. // We we are mearly getting the instance names, just add the
  854. // instance name to the list. If the instance name is a
  855. // duplicate, prepend "[num]" to the name.
  856. if(wcslen(pwName) > 240)
  857. continue; // should never happen but just in case!
  858. int iRet = pInfo->GetNumDuplicates(pwName);
  859. if(iRet > 0)
  860. {
  861. swprintf(wInstName,L"[%ld]", iRet);
  862. wcscat(wInstName, pwName);
  863. }
  864. else
  865. wcscpy(wInstName, pwName);
  866. pInfo->AddEntry(wInstName);
  867. }
  868. else
  869. {
  870. // for now the code assumes that the first instance
  871. // will be retrieved if the instance is not specified
  872. if(wcslen(pwInstName) == 0)
  873. bEqual = TRUE;
  874. else
  875. {
  876. bEqual = !_wcsicmp(pwName ,pwInstName);
  877. if(lDuplicateNum > lNumDupsSoFar && bEqual)
  878. {
  879. bEqual = FALSE;
  880. lNumDupsSoFar++;
  881. }
  882. }
  883. if(bEqual)
  884. {
  885. // we found the instance !!!! Data is found
  886. // in data block following instance offset
  887. // appropriatly for this counter.
  888. pVoid = (PBYTE)pInst + pInst->ByteLength +
  889. pCount->CounterOffset;
  890. if(pCountBase)
  891. pVoidBase = (PBYTE)pInst + pInst->ByteLength +
  892. pCountBase->CounterOffset;
  893. break;
  894. }
  895. }
  896. // not found yet, next instance is after this
  897. // instance + this instance's counter data
  898. pCtrBlk = (PPERF_COUNTER_BLOCK)((PBYTE)pInst +
  899. pInst->ByteLength);
  900. pInst = (PPERF_INSTANCE_DEFINITION)((PBYTE)pInst +
  901. pInst->ByteLength + pCtrBlk->ByteLength);
  902. }
  903. }
  904. // Bail out if data was never found or if we were just looking for instances
  905. if(pInfo)
  906. return pInfo->GetStatus();
  907. if(pVoid == NULL)
  908. {
  909. return WBEM_E_FAILED; // never found object in the block
  910. }
  911. // Move the counter data and possibly the base data into the structure
  912. // Note that text is handled via the ppRetData pointer and is not
  913. // done here.
  914. DWORD dwSizeField = dwType & 0x300;
  915. void * pDest = (bNew) ? &pls->lnaCounterValue[0] : &pls->lnaOldCounterValue[0];
  916. if(dwSizeField == PERF_SIZE_DWORD)
  917. {
  918. memset(pDest,0,sizeof(LONGLONG)); // zero out unused portions
  919. dwSize = sizeof(DWORD);
  920. memcpy(pDest,pVoid,dwSize);
  921. }
  922. else if(dwSizeField == PERF_SIZE_LARGE)
  923. {
  924. dwSize = sizeof(LONGLONG);
  925. memcpy(pDest,pVoid,dwSize);
  926. }
  927. else if(dwSizeField == PERF_SIZE_VARIABLE_LEN)
  928. dwSize = pCount->CounterSize; // this sets it for text
  929. else
  930. {
  931. return WBEM_E_FAILED; // never found object in the block
  932. }
  933. // possibly do the base now.
  934. dwSizeField = dwTypeBase & 0x300;
  935. pDest = (bNew) ? &pls->lnaCounterValue[1] : &pls->lnaOldCounterValue[1];
  936. if(dwSizeField == PERF_SIZE_DWORD && pVoidBase)
  937. {
  938. memset(pDest,0,sizeof(LONGLONG));
  939. memcpy(pDest,pVoidBase,sizeof(DWORD));
  940. }
  941. else if(dwSizeField == PERF_SIZE_LARGE && pVoidBase)
  942. memcpy(pDest,pVoidBase,sizeof(LONGLONG));
  943. *ppRetData = pVoid; // Set to return data
  944. *pdwSize = dwSize;
  945. return S_OK;
  946. }
  947. catch(...)
  948. {
  949. return WBEM_E_FAILED;
  950. }
  951. }
  952. //***************************************************************************
  953. //
  954. // SCODE CImpPerf::MakeEnum
  955. //
  956. // DESCRIPTION:
  957. //
  958. // Creates a CEnumPerfInfo object which can be used for enumeration
  959. //
  960. // PARAMETERS:
  961. //
  962. // pClass Pointer to the class object.
  963. // ProvObj Object containing the property context string.
  964. // ppInfo Set to point to an collection object which has
  965. // the keynames of the instances.
  966. //
  967. // RETURN VALUE:
  968. //
  969. // S_OK all is well,
  970. // else set by LoadData or FindData
  971. //
  972. //***************************************************************************
  973. SCODE CImpPerf::MakeEnum(
  974. IN IWbemClassObject * pClass,
  975. IN CProvObj & ProvObj,
  976. OUT CEnumInfo ** ppInfo)
  977. {
  978. SCODE sc;
  979. int iObject,iCounter;
  980. PERF_DATA_BLOCK * pNew;
  981. DWORD dwSize;
  982. LINESTRUCT ls;
  983. void * pCountData;
  984. CVariant vPerf;
  985. CEnumPerfInfo * pInfo = NULL;
  986. *ppInfo = NULL;
  987. // The perf counter provider keeps some rather expensive data and
  988. // so it doesnt support complete reentrancy.
  989. if(hExec)
  990. {
  991. DWORD dwRet;
  992. dwRet = WaitForSingleObject(hExec,MAX_EXEC_WAIT);
  993. if(dwRet != WAIT_ABANDONED && dwRet != WAIT_OBJECT_0)
  994. return WBEM_E_FAILED;
  995. }
  996. else
  997. return WBEM_E_FAILED;
  998. // Load up the data
  999. sc = LoadData(ProvObj,&ls,&iObject,&iCounter,&pNew,TRUE);
  1000. if(sc != S_OK)
  1001. goto DoneMakeEnum;
  1002. // Create a new CEnumPerfInfo object. Its entries will be filled
  1003. // in by Find Data.
  1004. pInfo = new CEnumPerfInfo();
  1005. if(pInfo == NULL)
  1006. {
  1007. sc = WBEM_E_OUT_OF_MEMORY;
  1008. goto DoneMakeEnum;
  1009. }
  1010. sc = FindData(pNew,iObject,iCounter,ProvObj,&dwSize,&pCountData,
  1011. &ls,TRUE,pInfo);
  1012. if(sc != S_OK)
  1013. delete pInfo;
  1014. DoneMakeEnum:
  1015. if(sc == S_OK)
  1016. *ppInfo = pInfo;
  1017. if(hExec)
  1018. ReleaseMutex(hExec);
  1019. return sc;
  1020. }
  1021. //***************************************************************************
  1022. //
  1023. // SCODE CImpPerf::GetKey
  1024. //
  1025. // DESCRIPTION:
  1026. //
  1027. // Gets the key name of an entry in the enumeration list.
  1028. //
  1029. // PARAMETERS:
  1030. //
  1031. // pInfo Collection list
  1032. // iIndex Index in the collection
  1033. // ppKey Set to the string. MUST BE FREED with "delete"
  1034. //
  1035. // RETURN VALUE:
  1036. //
  1037. // S_OK if all is well
  1038. // WBEM_E_INVALID_PARAMETER bad index
  1039. // WBEM_E_OUT_OF_MEMORY
  1040. //***************************************************************************
  1041. SCODE CImpPerf::GetKey(
  1042. IN CEnumInfo * pInfo,
  1043. IN int iIndex,
  1044. OUT LPWSTR * ppKey)
  1045. {
  1046. CEnumPerfInfo * pPerfInfo = (CEnumPerfInfo *)pInfo;
  1047. LPWSTR pEntry = pPerfInfo->GetEntry(iIndex);
  1048. if(pEntry == NULL)
  1049. return WBEM_E_INVALID_PARAMETER;
  1050. *ppKey = new WCHAR[wcslen(pEntry)+1];
  1051. if(*ppKey == NULL)
  1052. return WBEM_E_OUT_OF_MEMORY;
  1053. wcscpy(*ppKey,pEntry);
  1054. return S_OK;
  1055. }
  1056. //***************************************************************************
  1057. //
  1058. // SCODE CImpPerf::MergeStrings
  1059. //
  1060. // DESCRIPTION:
  1061. //
  1062. // Combines the Class Context, Key, and Property Context strings.
  1063. //
  1064. // PARAMETERS:
  1065. //
  1066. // ppOut Output string. MUST BE FREED VIA "delete"
  1067. // pClassContext Class context
  1068. // pKey Key property value
  1069. // pPropContext Property context
  1070. //
  1071. // RETURN VALUE:
  1072. //
  1073. // S_OK if all is well
  1074. // WBEM_E_INVALID_PARAMETER context string
  1075. // WBEM_E_OUT_OF_MEMORY
  1076. //
  1077. //***************************************************************************
  1078. SCODE CImpPerf::MergeStrings(
  1079. OUT LPWSTR * ppOut,
  1080. IN LPWSTR pClassContext,
  1081. IN LPWSTR pKey,
  1082. IN LPWSTR pPropContext)
  1083. {
  1084. // Allocate space for output
  1085. int iLen = 3;
  1086. if(pClassContext)
  1087. iLen += wcslen(pClassContext);
  1088. if(pKey)
  1089. iLen += wcslen(pKey);
  1090. if(pPropContext)
  1091. iLen += wcslen(pPropContext);
  1092. else
  1093. return WBEM_E_INVALID_PARAMETER; // should always have this!
  1094. *ppOut = new WCHAR[iLen];
  1095. if(*ppOut == NULL)
  1096. return WBEM_E_OUT_OF_MEMORY;
  1097. //todo todo, remove this demo specical
  1098. if(pPropContext[0] == L'@')
  1099. {
  1100. wcscpy(*ppOut,pPropContext+1);
  1101. return S_OK;
  1102. }
  1103. //todo todo, remove this demo specical
  1104. // simplecase is that everything is in the property context. That would
  1105. // be the case when the provider is being used as a simple dynamic
  1106. // property provider
  1107. if(pClassContext == NULL || pKey == NULL)
  1108. {
  1109. wcscpy(*ppOut,pPropContext);
  1110. return S_OK;
  1111. }
  1112. // Copy the class context, property, and finally the key
  1113. wcscpy(*ppOut,pClassContext);
  1114. wcscat(*ppOut,L"|");
  1115. wcscat(*ppOut,pPropContext);
  1116. wcscat(*ppOut,L"|");
  1117. wcscat(*ppOut,pKey);
  1118. return S_OK;
  1119. }
  1120. //***************************************************************************
  1121. //
  1122. // CEnumPerfInfo::CEnumPerfInfo
  1123. //
  1124. // DESCRIPTION:
  1125. //
  1126. // Constructor.
  1127. //
  1128. //***************************************************************************
  1129. CEnumPerfInfo::CEnumPerfInfo()
  1130. {
  1131. m_iNumUniChar = 0;
  1132. m_iNumEntries = 0;
  1133. m_pBuffer = NULL;
  1134. m_status = S_OK;
  1135. }
  1136. //***************************************************************************
  1137. //
  1138. // CEnumPerfInfo::~CEnumPerfInfo
  1139. //
  1140. // DESCRIPTION:
  1141. //
  1142. // Destructor.
  1143. //
  1144. //***************************************************************************
  1145. CEnumPerfInfo::~CEnumPerfInfo()
  1146. {
  1147. if(m_pBuffer)
  1148. delete m_pBuffer;
  1149. }
  1150. //***************************************************************************
  1151. //
  1152. // void CEnumPerfInfo::AddEntry
  1153. //
  1154. // DESCRIPTION:
  1155. //
  1156. // Adds an entry to the enumeration list.
  1157. //
  1158. // PARAMETERS:
  1159. //
  1160. // pNew String to add to collection.
  1161. //
  1162. //***************************************************************************
  1163. void CEnumPerfInfo::AddEntry(
  1164. LPWSTR pNew)
  1165. {
  1166. if(m_status != S_OK)
  1167. return; // already had memory problems.
  1168. int iNewSize = wcslen(pNew) + 1 + m_iNumUniChar;
  1169. LPWSTR pNewBuff = new WCHAR[iNewSize];
  1170. if(pNewBuff == NULL)
  1171. {
  1172. m_status = WBEM_E_OUT_OF_MEMORY;
  1173. return;
  1174. }
  1175. wcscpy(&pNewBuff[m_iNumUniChar],pNew);
  1176. if(m_pBuffer)
  1177. {
  1178. memcpy(pNewBuff,m_pBuffer,m_iNumUniChar*2);
  1179. delete m_pBuffer;
  1180. }
  1181. m_iNumEntries++;
  1182. m_iNumUniChar = iNewSize;
  1183. m_pBuffer = pNewBuff;
  1184. }
  1185. //***************************************************************************
  1186. //
  1187. // int CEnumPerfInfo::GetNumDuplicates
  1188. //
  1189. // DESCRIPTION:
  1190. //
  1191. // Checks the list to find duplicate entries.
  1192. //
  1193. // PARAMETERS:
  1194. //
  1195. // pwcTest string to test for duplicates
  1196. //
  1197. // RETURN VALUE:
  1198. //
  1199. // number of matching strings in the collection.
  1200. //
  1201. //***************************************************************************
  1202. int CEnumPerfInfo::GetNumDuplicates(
  1203. LPWSTR pwcTest)
  1204. {
  1205. int iRet = 0;
  1206. int iCnt;
  1207. LPWSTR pVal = m_pBuffer;
  1208. for(iCnt = 0; iCnt < m_iNumEntries; iCnt++)
  1209. {
  1210. WCHAR * pwcText = pVal;
  1211. // If the string is of the form "[number]text", skip the "[number]"
  1212. // part.
  1213. if(*pVal == L'[')
  1214. {
  1215. for(pwcText = pVal+1; *pwcText && *pwcText != L']';pwcText++);
  1216. if(*pwcText == L']')
  1217. pVal = pwcText+1;
  1218. }
  1219. if(!_wcsicmp(pwcTest, pVal))
  1220. iRet++;
  1221. pVal += wcslen(pVal) + 1;
  1222. }
  1223. return iRet;
  1224. }
  1225. //***************************************************************************
  1226. //
  1227. // LPWSTR CEnumPerfInfo::GetEntry
  1228. //
  1229. // DESCRIPTION:
  1230. //
  1231. // Gets a list entry.
  1232. //
  1233. // PARAMETERS:
  1234. //
  1235. // iIndex collection index
  1236. //
  1237. // RETURN VALUE:
  1238. //
  1239. // pointer to string in index. Should NOT be freed.
  1240. // NULL if bad index
  1241. //
  1242. //***************************************************************************
  1243. LPWSTR CEnumPerfInfo::GetEntry(
  1244. IN int iIndex)
  1245. {
  1246. // fist check for bad conditions
  1247. if(m_status != S_OK || iIndex < 0 || iIndex >= m_iNumEntries)
  1248. return NULL;
  1249. int iCnt;
  1250. LPWSTR pRet = m_pBuffer;
  1251. for(iCnt = 0; iCnt < iIndex; iCnt++)
  1252. pRet += wcslen(pRet) + 1;
  1253. return pRet;
  1254. }
  1255. //***************************************************************************
  1256. //
  1257. // CImpPerfProp::CImpPerfProp
  1258. //
  1259. // DESCRIPTION:
  1260. //
  1261. // Constructor.
  1262. //
  1263. //***************************************************************************
  1264. CImpPerfProp::CImpPerfProp()
  1265. {
  1266. m_pImpDynProv = new CImpPerf();
  1267. }
  1268. //***************************************************************************
  1269. //
  1270. // CImpPerfProp::~CImpPerfProp
  1271. //
  1272. // DESCRIPTION:
  1273. //
  1274. // Destructor.
  1275. //
  1276. //***************************************************************************
  1277. CImpPerfProp::~CImpPerfProp()
  1278. {
  1279. if(m_pImpDynProv)
  1280. delete m_pImpDynProv;
  1281. }