Leaked source code of windows server 2003
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.

1774 lines
44 KiB

  1. /*
  2. Copyright (c) 1998-1999 Microsoft Corporation
  3. */
  4. #include "stdafx.h"
  5. #include "atlconv.h"
  6. #include "termmgr.h"
  7. #include "meterf.h"
  8. #include "medpump.h"
  9. #include <stdio.h>
  10. #include <limits.h>
  11. #include <tchar.h>
  12. //
  13. // the default value for maximum number of filters serviced by a single
  14. // pump. can also be configurable through registry.
  15. //
  16. #define DEFAULT_MAX_FILTER_PER_PUMP (20)
  17. //
  18. // registry location where max number of filters per pump is configured
  19. //
  20. #define MST_REGISTRY_PATH _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Telephony\\MST")
  21. #define MAX_FILTERS_PER_PUMP_KEY _T("MaximumFiltersPerPump")
  22. DWORD WINAPI WriteMediaPumpThreadStart(void *pvPump)
  23. {
  24. return ((CMediaPump *)pvPump)->PumpMainLoop();
  25. }
  26. CMediaPump::CMediaPump(
  27. )
  28. : m_hThread(NULL),
  29. m_hRegisterBeginSemaphore(NULL),
  30. m_hRegisterEndSemaphore(NULL)
  31. {
  32. HRESULT hr;
  33. LOG((MSP_TRACE, "CMediaPump::CMediaPump - enter"));
  34. // create semaphores for registration signaling and completion -
  35. // m_hRegisterBeginSemaphore is signaled before the Register call tries to acquire
  36. // the critical section and m_hRegisterEndSemaphore is signaled when the Register
  37. // call completes
  38. // NOTE the names must be NULL, otherwise they will conflict when multiple pump threads
  39. // are created
  40. TCHAR *ptszSemaphoreName = NULL;
  41. #if DBG
  42. //
  43. // in debug build, use named semaphores.
  44. //
  45. TCHAR tszSemaphoreNameString[MAX_PATH];
  46. _stprintf(tszSemaphoreNameString, _T("RegisterBeginSemaphore_pid[0x%lx]_MediaPump[%p]_"), GetCurrentProcessId(), this);
  47. LOG((MSP_TRACE, "CMediaPump::CMediaPump - creating semaphore[%S]", tszSemaphoreNameString));
  48. ptszSemaphoreName = &tszSemaphoreNameString[0];
  49. #endif
  50. //
  51. // create the beginning semaphore
  52. //
  53. m_hRegisterBeginSemaphore = CreateSemaphore(NULL, 0, LONG_MAX, ptszSemaphoreName);
  54. if ( NULL == m_hRegisterBeginSemaphore ) goto cleanup;
  55. #if DBG
  56. //
  57. // construct a name for registration end semaphore
  58. //
  59. _stprintf(tszSemaphoreNameString,
  60. _T("RegisterEndSemaphore_pid[0x%lx]_MediaPump[%p]"),
  61. GetCurrentProcessId(), this);
  62. LOG((MSP_TRACE, "CMediaPump::CMediaPump - creating semaphore[%S]", tszSemaphoreNameString));
  63. ptszSemaphoreName = &tszSemaphoreNameString[0];
  64. #endif
  65. //
  66. // create the end semaphore
  67. //
  68. m_hRegisterEndSemaphore = CreateSemaphore(NULL, 0, LONG_MAX, ptszSemaphoreName);
  69. if ( NULL == m_hRegisterEndSemaphore ) goto cleanup;
  70. // insert the register event and the filter info to the arrays
  71. // NOTE: we have to close and null the event in case of failure
  72. // so that we can just check the register event handle value in
  73. // Register to check if initialization was successful
  74. hr = m_EventArray.Add(m_hRegisterBeginSemaphore);
  75. if ( FAILED(hr) ) goto cleanup;
  76. // add a corresponding NULL filter info entry
  77. hr = m_FilterInfoArray.Add(NULL);
  78. if ( FAILED(hr) )
  79. {
  80. m_EventArray.Remove(m_EventArray.GetSize()-1);
  81. goto cleanup;
  82. }
  83. LOG((MSP_TRACE, "CMediaPump::CMediaPump - exit"));
  84. return;
  85. cleanup:
  86. if ( NULL != m_hRegisterBeginSemaphore )
  87. {
  88. CloseHandle(m_hRegisterBeginSemaphore);
  89. m_hRegisterBeginSemaphore = NULL;
  90. }
  91. if ( NULL != m_hRegisterEndSemaphore )
  92. {
  93. CloseHandle(m_hRegisterEndSemaphore);
  94. m_hRegisterEndSemaphore = NULL;
  95. }
  96. LOG((MSP_TRACE, "CMediaPump::CMediaPump - cleanup exit"));
  97. }
  98. CMediaPump::~CMediaPump(void)
  99. {
  100. LOG((MSP_TRACE, "CMediaPump::~CMediaPump - enter"));
  101. if ( NULL != m_hThread )
  102. {
  103. // when decommit is called on all the write terminals, the thread
  104. // will return. this will signal the thread handle
  105. WaitForSingleObject(m_hThread, INFINITE);
  106. CloseHandle(m_hThread);
  107. m_hThread = NULL;
  108. }
  109. if ( NULL != m_hRegisterBeginSemaphore )
  110. {
  111. CloseHandle(m_hRegisterBeginSemaphore);
  112. m_hRegisterBeginSemaphore = NULL;
  113. }
  114. if ( NULL != m_hRegisterEndSemaphore )
  115. {
  116. CloseHandle(m_hRegisterEndSemaphore);
  117. m_hRegisterEndSemaphore = NULL;
  118. }
  119. LOG((MSP_TRACE, "CMediaPump::~CMediaPump - exit"));
  120. }
  121. HRESULT
  122. CMediaPump::CreateThreadPump(void)
  123. {
  124. LOG((MSP_TRACE, "CMediaPump::CreateThreadPump - enter"));
  125. // release resources for any previous thread
  126. if (NULL != m_hThread)
  127. {
  128. // when decommit is called on all the write terminals, the thread
  129. // will return. this will signal the thread handle
  130. // NOTE: this wait cannot cause a deadlock as we know that the
  131. // number of entries in the array had dropped to 0 and only thread
  132. // that can remove entries is the pump. this means that the pump thread
  133. // must have detected that and returned
  134. WaitForSingleObject(m_hThread, INFINITE);
  135. CloseHandle(m_hThread);
  136. m_hThread = NULL;
  137. }
  138. // create new thread
  139. m_hThread = CreateThread(
  140. NULL, 0, WriteMediaPumpThreadStart,
  141. (void *)this, 0, NULL
  142. );
  143. if (NULL == m_hThread)
  144. {
  145. DWORD WinErrorCode = GetLastError();
  146. LOG((MSP_TRACE,
  147. "CMediaPump::CreateThreadPump - failed to create thread. LastError = 0x%lx",
  148. WinErrorCode));
  149. return HRESULT_FROM_ERROR_CODE(WinErrorCode);
  150. }
  151. LOG((MSP_TRACE, "CMediaPump::CreateThreadPump - finish"));
  152. return S_OK;
  153. }
  154. // add this filter to its wait array
  155. HRESULT
  156. CMediaPump::Register(
  157. IN CMediaTerminalFilter *pFilter,
  158. IN HANDLE hWaitEvent
  159. )
  160. {
  161. LOG((MSP_TRACE, "CMediaPump::Register - enter"));
  162. TM_ASSERT(NULL != pFilter);
  163. TM_ASSERT(NULL != hWaitEvent);
  164. BAIL_IF_NULL(pFilter, E_INVALIDARG);
  165. BAIL_IF_NULL(hWaitEvent, E_INVALIDARG);
  166. // check if the register event
  167. if ( NULL == m_hRegisterBeginSemaphore )
  168. {
  169. LOG((MSP_ERROR,
  170. "CMediaPump::Register - m_hRegisterBeginSemaphore is NULL"));
  171. return E_FAIL;
  172. }
  173. LONG lDebug;
  174. // signal the register event, the pump thread will come out of the
  175. // critical section and wait on m_hRegisterEndSemaphore
  176. if ( !ReleaseSemaphore(m_hRegisterBeginSemaphore, 1, &lDebug) )
  177. {
  178. DWORD WinErrorCode = GetLastError();
  179. LOG((MSP_ERROR,
  180. "CMediaPump::Register - failed to release m_hRegisterBeginSemaphore. LastError = 0x%lx",
  181. WinErrorCode));
  182. return HRESULT_FROM_ERROR_CODE(WinErrorCode);
  183. }
  184. LOG((MSP_TRACE, "CMediaPump::Register - released begin semaphore - old count was %d", lDebug));
  185. // when this SignalRegisterEnd instance is destroyed, it'll signal
  186. // the end of registration and unblock the thread pump
  187. //
  188. // NOTE this releases the semaphore on DESTRUCTION of the class instance
  189. RELEASE_SEMAPHORE_ON_DEST SignalRegisterEnd(m_hRegisterEndSemaphore);
  190. HRESULT hr;
  191. PUMP_LOCK LocalLock(&m_CritSec);
  192. TM_ASSERT(m_EventArray.GetSize() == m_FilterInfoArray.GetSize());
  193. // its possible that there is a duplicate in the array
  194. // scenario - decommit signals the wait event but commit-register
  195. // calls enter the critical section before the pump
  196. DWORD Index;
  197. if ( m_EventArray.Find(hWaitEvent, Index) )
  198. {
  199. LOG((MSP_TRACE, "CMediaPump::Register - event already registered"));
  200. return S_OK;
  201. }
  202. // check if we have reached the maximum allowed filters
  203. // if we overflow, we must return a very specific error code here
  204. // (See CMediaPumpPool)
  205. if ( m_EventArray.GetSize() >= MAX_FILTERS )
  206. {
  207. LOG((MSP_ERROR, "CMediaPump::Register - reached max number of filters for this[%p] pump", this));
  208. return TAPI_E_ALLOCATED;
  209. }
  210. // create CFilterInfo for holding the call parameters
  211. CFilterInfo *pFilterInfo = new CFilterInfo(pFilter, hWaitEvent);
  212. if (NULL == pFilterInfo)
  213. {
  214. LOG((MSP_ERROR, "CMediaPump::Register - failed to allocate CFilterInfo"));
  215. return E_OUTOFMEMORY;
  216. }
  217. //
  218. // addref so the filterinfo structure we place into the array has refcount
  219. // of one
  220. //
  221. pFilterInfo->AddRef();
  222. // insert wait event into the array
  223. hr = m_EventArray.Add(hWaitEvent);
  224. if ( FAILED(hr) )
  225. {
  226. pFilterInfo->Release();
  227. LOG((MSP_ERROR, "CMediaPump::Register - m_EventArray.Add failed hr = %lx", hr));
  228. return hr;
  229. }
  230. // add a corresponding filter info entry
  231. hr = m_FilterInfoArray.Add(pFilterInfo);
  232. if ( FAILED(hr) )
  233. {
  234. m_EventArray.Remove(m_EventArray.GetSize()-1);
  235. pFilterInfo->Release();
  236. LOG((MSP_ERROR, "CMediaPump::Register - m_FilterInfoArray.Add failed hr = %lx", hr));
  237. return hr;
  238. }
  239. // if this is the first entry into the array (beside the
  240. // m_hRegisterBeginSemaphore, we need to create a thread pump
  241. if (m_EventArray.GetSize() == 2)
  242. {
  243. hr = CreateThreadPump();
  244. if ( FAILED(hr) )
  245. {
  246. RemoveFilter(m_EventArray.GetSize()-1);
  247. LOG((MSP_ERROR, "CMediaPump::Register - CreateThreadPump failed hr = %lx", hr));
  248. return hr;
  249. }
  250. }
  251. // ignore error code. if we have been decommitted in between, it will
  252. // signal us through the wait event
  253. pFilter->SignalRegisteredAtPump();
  254. TM_ASSERT(m_EventArray.GetSize() == m_FilterInfoArray.GetSize());
  255. LOG((MSP_TRACE, "CMediaPump::Register - exit"));
  256. return S_OK;
  257. }
  258. // remove this filter from wait array
  259. HRESULT
  260. CMediaPump::UnRegister(
  261. IN HANDLE hWaitEvent
  262. )
  263. {
  264. LOG((MSP_TRACE, "CMediaPump::Unregister[%p] - enter. Event[%p]",
  265. this, hWaitEvent));
  266. //
  267. // if we did not get a valid event handle, debug
  268. //
  269. TM_ASSERT(NULL != hWaitEvent);
  270. BAIL_IF_NULL(hWaitEvent, E_INVALIDARG);
  271. //
  272. // if we don't have register event, the pump is not properly initialized
  273. //
  274. if ( NULL == m_hRegisterBeginSemaphore )
  275. {
  276. LOG((MSP_ERROR,
  277. "CMediaPump::Unregister[%p] - m_hRegisterBeginSemaphore is nUll. "
  278. "pump is not initialized. returning E_FAIL - hWaitEvent=[%p]",
  279. this, hWaitEvent));
  280. return E_FAIL;
  281. }
  282. LONG lDebugSemaphoreCount = 0;
  283. // signal the register event, the pump thread will come out of the
  284. // critical section and wait on m_hRegisterEndSemaphore
  285. if ( !ReleaseSemaphore(m_hRegisterBeginSemaphore, 1, &lDebugSemaphoreCount) )
  286. {
  287. DWORD WinErrorCode = GetLastError();
  288. LOG((MSP_ERROR,
  289. "CMediaPump::Unregister - ReleaseSemaphore failed with LastError 0x%lx",
  290. WinErrorCode));
  291. return HRESULT_FROM_ERROR_CODE(WinErrorCode);
  292. }
  293. LOG((MSP_TRACE, "CMediaPump::UnRegister - released begin semaphore - old count was %d", lDebugSemaphoreCount));
  294. // when this SignalRegisterEnd instance is destroyed, it'll signal
  295. // the end of registration and unblock the thread pump
  296. //
  297. // NOTE this releases the semaphore on DESTRUCTION of the class instance
  298. RELEASE_SEMAPHORE_ON_DEST SignalRegisterEnd(m_hRegisterEndSemaphore);
  299. PUMP_LOCK LocalLock(&m_CritSec);
  300. TM_ASSERT(m_EventArray.GetSize() == m_FilterInfoArray.GetSize());
  301. //
  302. // has this event been registered before
  303. //
  304. DWORD Index;
  305. if ( !m_EventArray.Find(hWaitEvent, Index) )
  306. {
  307. LOG((MSP_TRACE,
  308. "CMediaPump::UnRegister - event is not ours. returning E_FAIL. not an error."));
  309. return E_FAIL;
  310. }
  311. //
  312. // found the filter that matches the event. remove the filter.
  313. //
  314. RemoveFilter(Index);
  315. TM_ASSERT(m_EventArray.GetSize() == m_FilterInfoArray.GetSize());
  316. LOG((MSP_TRACE, "CMediaPump::Unregister - finish."));
  317. return S_OK;
  318. }
  319. void
  320. CMediaPump::RemoveFilter(
  321. IN DWORD Index
  322. )
  323. {
  324. PUMP_LOCK LocalLock(&m_CritSec);
  325. //
  326. // event array and filterinfo arrays must always be consistent
  327. //
  328. TM_ASSERT(m_EventArray.GetSize() == m_FilterInfoArray.GetSize());
  329. //
  330. // find the filter that needs to be removed -- it must exist
  331. //
  332. CFilterInfo *pFilterInfo = m_FilterInfoArray.Get(Index);
  333. if (NULL == pFilterInfo)
  334. {
  335. LOG((MSP_ERROR,
  336. "CMediaPump::RemoveFilter - filter %ld not found in filter array",
  337. Index));
  338. TM_ASSERT(FALSE);
  339. return;
  340. }
  341. //
  342. // remove event and filter from the corresponding arrays
  343. //
  344. m_EventArray.Remove(Index);
  345. m_FilterInfoArray.Remove(Index);
  346. //
  347. // remove filter from timer q and destroy it
  348. //
  349. LOG((MSP_TRACE,
  350. "CMediaPump::RemoveFilter - removing filter[%ld] filterinfo[%p] from timerq",
  351. Index,
  352. pFilterInfo
  353. ));
  354. m_TimerQueue.Remove(pFilterInfo);
  355. pFilterInfo->Release();
  356. TM_ASSERT(m_EventArray.GetSize() == m_FilterInfoArray.GetSize());
  357. }
  358. void
  359. CMediaPump::RemoveFilter(
  360. IN CFilterInfo *pFilterInfo
  361. )
  362. {
  363. //
  364. // find and remove should be atomic and need to be done in a lock
  365. //
  366. PUMP_LOCK LocalLock(&m_CritSec);
  367. //
  368. // find the array index
  369. //
  370. DWORD Index = 0;
  371. if ( !m_FilterInfoArray.Find(pFilterInfo, Index) )
  372. {
  373. LOG((MSP_ERROR,
  374. "CMediaPump::RemoveFilter - filter[%p] is not in the filterinfo array",
  375. pFilterInfo));
  376. return;
  377. }
  378. //
  379. // index found, remove the filter
  380. //
  381. RemoveFilter(Index);
  382. }
  383. void
  384. CMediaPump::ServiceFilter(
  385. IN CFilterInfo *pFilterInfo
  386. )
  387. {
  388. if (NULL == pFilterInfo)
  389. {
  390. LOG((MSP_ERROR,
  391. "CMediaPump::ServiceFilter - pFilterInfo is NULL"));
  392. TM_ASSERT(FALSE);
  393. return;
  394. }
  395. if (NULL == pFilterInfo->m_pFilter)
  396. {
  397. LOG((MSP_ERROR,
  398. "CMediaPump::ServiceFilter - pFilterInfo->m_pFilter is NULL"));
  399. TM_ASSERT(FALSE);
  400. return;
  401. }
  402. DWORD dwTimeBeforeGetFilledBuffer = timeGetTime();
  403. IMediaSample *pMediaSample;
  404. DWORD NextTimeout;
  405. CMediaTerminalFilter *pFilter = pFilterInfo->m_pFilter;
  406. HRESULT hr = pFilter->GetFilledBuffer(
  407. pMediaSample, NextTimeout
  408. );
  409. if ( SUCCEEDED(hr) )
  410. {
  411. // if S_FALSE, nothing needs to be done
  412. // its returned when we were signaled but there is no sample in
  413. // the filter pool now.
  414. // just continue to wait on event, no timeout needs to be scheduled
  415. if ( S_FALSE == hr )
  416. {
  417. return;
  418. }
  419. // if GetFilledBuffer could not get an output buffer from the sample
  420. // queue, then there is no sample to deliver just yet, but we do
  421. // need to schedule the next timeout.
  422. if ( VFW_S_NO_MORE_ITEMS != hr )
  423. {
  424. LOG((MSP_TRACE, "CMediaPump::ServiceFilter - calling Receive on downstream filter"));
  425. //
  426. // ask filter to process this sample. if everything goes ok, the
  427. // filter will pass the sample to a downstream connected pin
  428. //
  429. HRESULT hrReceived = pFilter->ProcessSample(pMediaSample);
  430. LOG((MSP_TRACE, "CMediaPump::ServiceFilter - returned from Receive on downstream filter"));
  431. if ( pFilter->m_bUsingMyAllocator )
  432. {
  433. CSample *pSample = ((CMediaSampleTM *)pMediaSample)->m_pSample;
  434. pSample->m_bReceived = true;
  435. if (hrReceived != S_OK)
  436. {
  437. LOG((MSP_TRACE,
  438. "CMediaPump::ServiceFilter - downstream filter's ProcessSample returned 0x%08x. "
  439. "Aborting I/O operation",
  440. hrReceived));
  441. pSample->m_MediaSampleIoStatus = E_ABORT;
  442. }
  443. }
  444. pMediaSample->Release();
  445. //
  446. // Account for how long it took to get the buffer and call Receive
  447. // on the sample.
  448. //
  449. DWORD dwTimeAfterReceive = timeGetTime();
  450. DWORD dwServiceDuration;
  451. if ( dwTimeAfterReceive >= dwTimeBeforeGetFilledBuffer )
  452. {
  453. dwServiceDuration =
  454. dwTimeAfterReceive - dwTimeBeforeGetFilledBuffer;
  455. }
  456. else
  457. {
  458. dwServiceDuration = ( (DWORD) -1 )
  459. - dwTimeBeforeGetFilledBuffer + dwTimeAfterReceive;
  460. }
  461. //
  462. // Give it an extra 1 ms, just so we err on the side of caution.
  463. // This won't cause us to fill up the buffers anytime soon.
  464. //
  465. dwServiceDuration++;
  466. //
  467. // Adjust the timeout.
  468. //
  469. if ( dwServiceDuration >= NextTimeout )
  470. {
  471. NextTimeout = 0;
  472. }
  473. else
  474. {
  475. NextTimeout -= dwServiceDuration;
  476. }
  477. LOG((MSP_TRACE, "CMediaPump::ServiceFilter - "
  478. "timeout adjusted by %d ms; resulting timeout is %d ms",
  479. dwServiceDuration, NextTimeout));
  480. }
  481. // if there is a valid timeout, schedule next timeout.
  482. // otherwise, we'll get only signaled if a new sample is added or
  483. // the filter is decommitted
  484. //
  485. // need to do this in a lock
  486. //
  487. PUMP_LOCK LocalLock(&m_CritSec);
  488. //
  489. // if the filter has not yet been unregistered, schedule next timeout
  490. //
  491. DWORD Index = 0;
  492. if ( m_FilterInfoArray.Find(pFilterInfo, Index) )
  493. {
  494. //
  495. // filter is still registered, schedule next timeout
  496. //
  497. pFilterInfo->ScheduleNextTimeout(m_TimerQueue, NextTimeout);
  498. }
  499. else
  500. {
  501. //
  502. // filter was unregistered while we held critical section. this is
  503. // ok, but we should no longer schedule the filter, since it will
  504. // be deleted when this call returns and the filter is released.
  505. //
  506. LOG((MSP_TRACE,
  507. "CMediaPump::ServiceFilter - filter[%p] is not in the filterinfo array",
  508. pFilterInfo));
  509. }
  510. }
  511. else
  512. {
  513. TM_ASSERT(FAILED(hr));
  514. RemoveFilter(pFilterInfo);
  515. }
  516. return;
  517. }
  518. void
  519. CMediaPump::DestroyFilterInfoArray(
  520. )
  521. {
  522. for(DWORD i=1; i < m_FilterInfoArray.GetSize(); i++)
  523. {
  524. CFilterInfo *pFilterInfo = m_FilterInfoArray.Get(i);
  525. TM_ASSERT(NULL != pFilterInfo);
  526. delete pFilterInfo;
  527. m_FilterInfoArray.Remove(i);
  528. }
  529. }
  530. // waits for filter events to be activated. also waits
  531. // for registration calls and timer events
  532. HRESULT
  533. CMediaPump::PumpMainLoop(
  534. )
  535. {
  536. HRESULT hr;
  537. // wait in a loop for the filter events to be set or for the timer
  538. // events to be fired
  539. DWORD TimeToWait = INFINITE;
  540. DWORD ErrorCode;
  541. BOOL InRegisterCall = FALSE;
  542. SetThreadPriority(
  543. GetCurrentThread(),
  544. THREAD_PRIORITY_TIME_CRITICAL
  545. );
  546. do
  547. {
  548. // if a register call is in progress, wait for the call to signal
  549. // us before proceeding to acquire the critical section
  550. if ( InRegisterCall )
  551. {
  552. LOG((MSP_TRACE, "CMediaPump::PumpMainLoop - waiting for end semaphore"));
  553. InRegisterCall = FALSE;
  554. DWORD EndErrorCode = WaitForSingleObject(
  555. m_hRegisterEndSemaphore,
  556. INFINITE
  557. );
  558. if ( WAIT_OBJECT_0 != EndErrorCode )
  559. {
  560. LOG((MSP_ERROR,
  561. "CMediaPump::PumpMainLoop - failed waiting for m_hRegisterEndSemaphore"));
  562. return E_UNEXPECTED;
  563. }
  564. //
  565. // lock before accessing array
  566. //
  567. m_CritSec.Lock();
  568. //
  569. // see if the last filter was unregistered... if so, exit the thread.
  570. //
  571. if ( (1 == m_EventArray.GetSize()) )
  572. {
  573. m_CritSec.Unlock();
  574. LOG((MSP_TRACE,
  575. "CMediaPump::PumpMainLoop - a filter was unregistered. "
  576. "no more filters. exiting thread"));
  577. return S_OK;
  578. }
  579. //
  580. // if did not exit, keeping the lock
  581. //
  582. LOG((MSP_TRACE, "CMediaPump::PumpMainLoop - finished waiting for end semaphore"));
  583. }
  584. else
  585. {
  586. //
  587. // grab pump lock before starting to wait for events
  588. //
  589. m_CritSec.Lock();
  590. }
  591. //
  592. // we should have a lock at this point
  593. //
  594. TM_ASSERT(m_EventArray.GetSize() > 0);
  595. TM_ASSERT(m_EventArray.GetSize() == \
  596. m_FilterInfoArray.GetSize());
  597. //
  598. // calculate time until the thread should wake up
  599. //
  600. TimeToWait = m_TimerQueue.GetTimeToTimeout();
  601. LOG((MSP_TRACE,
  602. "CMediaPump::PumpMainLoop - starting waiting for array. timeout %lu",
  603. TimeToWait));
  604. //
  605. // wait to be signaled or until a timeout
  606. //
  607. ErrorCode = WaitForMultipleObjects(
  608. m_EventArray.GetSize(),
  609. m_EventArray.GetData(),
  610. FALSE, // don't wait for all
  611. TimeToWait
  612. );
  613. C_ASSERT(WAIT_OBJECT_0 == 0);
  614. if (WAIT_TIMEOUT == ErrorCode)
  615. {
  616. //
  617. // filter timeout
  618. //
  619. TM_ASSERT(INFINITE != TimeToWait);
  620. TM_ASSERT(!m_TimerQueue.IsEmpty());
  621. LOG((MSP_TRACE, "CMediaPump::PumpMainLoop - timeout"));
  622. CFilterInfo *pFilterInfo = m_TimerQueue.RemoveFirst();
  623. if (NULL == pFilterInfo)
  624. {
  625. LOG((MSP_ERROR,
  626. "CMediaPump::PumpMainLoop - m_TimerQueue.RemoveFirst returned NULL"));
  627. TM_ASSERT(FALSE);
  628. }
  629. else
  630. {
  631. pFilterInfo->AddRef();
  632. //
  633. // release the lock while in ServiceFilter to avoid deadlock with
  634. // CMediaPump__UnRegister
  635. //
  636. m_CritSec.Unlock();
  637. ServiceFilter(pFilterInfo);
  638. pFilterInfo->Release();
  639. m_CritSec.Lock();
  640. }
  641. }
  642. else if ( ErrorCode < (WAIT_OBJECT_0 + m_EventArray.GetSize()) )
  643. {
  644. LOG((MSP_TRACE, "CMediaPump::PumpMainLoop - signaled"));
  645. DWORD nFilterInfoIndex = ErrorCode - WAIT_OBJECT_0;
  646. if (0 == nFilterInfoIndex)
  647. {
  648. //
  649. // m_hRegisterBeginSemaphore was signaled
  650. //
  651. InRegisterCall = TRUE;
  652. }
  653. else
  654. {
  655. //
  656. // one of the filters was signaled
  657. //
  658. CFilterInfo *pFilterInfo = m_FilterInfoArray.Get(nFilterInfoIndex);
  659. if (NULL == pFilterInfo)
  660. {
  661. LOG((MSP_ERROR,
  662. "CMediaPump::PumpMainLoop - pFilterInfo at index %ld is NULL",
  663. nFilterInfoIndex));
  664. TM_ASSERT(FALSE);
  665. }
  666. else
  667. {
  668. pFilterInfo->AddRef();
  669. //
  670. // unlock while in ServiceFilter. we don't want to service
  671. // filter in a lock to avoid deadlocks.
  672. //
  673. m_CritSec.Unlock();
  674. ServiceFilter(pFilterInfo);
  675. pFilterInfo->Release();
  676. m_CritSec.Lock();
  677. }
  678. }
  679. }
  680. else if ( (WAIT_ABANDONED_0 <= ErrorCode) &&
  681. (ErrorCode < (WAIT_ABANDONED_0 + m_EventArray.GetSize()) ) )
  682. {
  683. DWORD nFilterIndex = ErrorCode - WAIT_OBJECT_0;
  684. LOG((MSP_TRACE,
  685. "CMediaPump::PumpMainLoop - event 0x%lx abandoned. removing filter",
  686. nFilterIndex));
  687. // remove item from the arrays
  688. RemoveFilter(nFilterIndex);
  689. }
  690. else
  691. {
  692. //
  693. // something bad happened
  694. //
  695. DestroyFilterInfoArray();
  696. m_CritSec.Unlock();
  697. DWORD WinErrorCode = GetLastError();
  698. LOG((MSP_ERROR,
  699. "CMediaPump::PumpMainLoop - error %ld... exiting",
  700. WinErrorCode));
  701. return HRESULT_FROM_ERROR_CODE(WinErrorCode);
  702. }
  703. //
  704. // this check is performed here so that we detect the empty
  705. // array and return, thus signaling the thread handle
  706. //
  707. // the case when InRegisterCall is on is not handled here -- we will
  708. // check for the number of filters left after we have waited for the
  709. // end event
  710. //
  711. if ( (1 == m_EventArray.GetSize()) && !InRegisterCall)
  712. {
  713. LOG((MSP_TRACE,
  714. "CMediaPump::PumpMainLoop - no more filters in the array. exiting thread"));
  715. m_CritSec.Unlock();
  716. return S_OK;
  717. }
  718. m_CritSec.Unlock();
  719. }
  720. while(1);
  721. }
  722. int CMediaPump::CountFilters()
  723. {
  724. LOG((MSP_TRACE, "CMediaPump::CountFilters[%p] - enter", this));
  725. //
  726. // one of the events is the registration event -- we need to account for
  727. // it -- hence the -1
  728. //
  729. //
  730. // note: it is ok to do this without locking media pump.
  731. //
  732. // the getsize operation is purely get, and it is ok if the value is
  733. // sometimes misread -- this would lead to the new filter being distributed
  734. // not in the most optimal fashion on extremely rare occasions. this slight
  735. // abnormality in distribution will be corrected later as new filters are
  736. // coming in.
  737. //
  738. // on the other hand, locking media pump to get filter count will lead to
  739. // "deadlocks" (when main pump loop is sleeping, and nothing happens to
  740. // wake it up), and it is not trivial to get around this deadlock condition
  741. // without affecting performance. not locking the pump is a simple and very
  742. // inexpensive way to accomplish the objective, with an acceptable
  743. // trade-off.
  744. //
  745. int nFilters = m_EventArray.GetSize() - 1;
  746. LOG((MSP_TRACE, "CMediaPump::CountFilters - exit. [%d] filters", nFilters));
  747. return nFilters;
  748. }
  749. //////////////////////////////////////////////////////////////////////////////
  750. //////////////////////////////////////////////////////////////////////////////
  751. //
  752. // ZoltanS: non-optimal, but relatively painless way to get around scalability
  753. // limitation of 63 filters per pump thread. This class presents the same
  754. // external interface as the single thread pump, but creates as many pump
  755. // threads as are needed to serve the filters that are in use.
  756. //
  757. //////////////////////////////////////////////////////////////////////////////
  758. //////////////////////////////////////////////////////////////////////////////
  759. CMediaPumpPool::CMediaPumpPool()
  760. {
  761. LOG((MSP_TRACE, "CMediaPumpPool::CMediaPumpPool - enter"));
  762. //
  763. // setting the default value. registry setting, if present, overwrites this
  764. //
  765. m_dwMaxNumberOfFilterPerPump = DEFAULT_MAX_FILTER_PER_PUMP;
  766. LOG((MSP_TRACE, "CMediaPumpPool::CMediaPumpPool - exit"));
  767. }
  768. //////////////////////////////////////////////////////////////////////////////
  769. //
  770. // Destructor: This destroys the individual pumps.
  771. //
  772. CMediaPumpPool::~CMediaPumpPool(void)
  773. {
  774. LOG((MSP_TRACE, "CMediaPumpPool::~CMediaPumpPool - enter"));
  775. CLock lock(m_CritSection);
  776. //
  777. // Shut down and delete each CMediaPump; the array itself is
  778. // cleaned up in its destructor.
  779. //
  780. int iSize = m_aPumps.GetSize();
  781. for (int i = 0; i < iSize; i++ )
  782. {
  783. delete m_aPumps[i];
  784. }
  785. LOG((MSP_TRACE, "CMediaPumpPool::~CMediaPumpPool - exit"));
  786. }
  787. //////////////////////////////////////////////////////////////////////////////
  788. //
  789. // CMediaPumpPool::CreatePumps
  790. //
  791. // this function creates the media pumps, the number of which is passed as an
  792. // argument
  793. //
  794. HRESULT CMediaPumpPool::CreatePumps(int nPumpsToCreate)
  795. {
  796. LOG((MSP_TRACE, "CMediaPumpPool::CreatePumps - enter. nPumpsToCreate = [%d]", nPumpsToCreate));
  797. for (int i = 0; i < nPumpsToCreate; i++)
  798. {
  799. //
  800. // attempt to create a media pump
  801. //
  802. CMediaPump * pNewPump = new CMediaPump;
  803. if ( pNewPump == NULL )
  804. {
  805. LOG((MSP_ERROR, "CMediaPumpPool::CreatePumps - "
  806. "cannot create new media pump - "
  807. "exit E_OUTOFMEMORY"));
  808. //
  809. // delete all the pumps we have created in this call
  810. //
  811. for (int j = i - 1; j >= 0; j--)
  812. {
  813. delete m_aPumps[j];
  814. m_aPumps.RemoveAt(j);
  815. }
  816. return E_OUTOFMEMORY;
  817. }
  818. //
  819. // attempt to add the new media pump to the array of media pumps.
  820. //
  821. if ( ! m_aPumps.Add(pNewPump) )
  822. {
  823. LOG((MSP_ERROR, "CMediaPumpPool::CreatePumps - cannot add new media pump to array - exit E_OUTOFMEMORY"));
  824. //
  825. // delete all the pumps we have created in this call
  826. //
  827. delete pNewPump;
  828. for (int j = i - 1; j >= 0; j--)
  829. {
  830. delete m_aPumps[j];
  831. m_aPumps.RemoveAt(j);
  832. }
  833. return E_OUTOFMEMORY;
  834. }
  835. }
  836. LOG((MSP_TRACE, "CMediaPumpPool::CreatePumps - finished."));
  837. return S_OK;
  838. }
  839. ///////////////////////////////////////////////////////////////////////////////
  840. //
  841. // CMediaPumpPool::ReadRegistryValuesIfNeeded
  842. //
  843. // this function reads the registry setting for max number of filters per pump
  844. // and in case of success, keeps the new value.
  845. //
  846. // this function is not thread safe. the caller must guarantee thread safety
  847. //
  848. HRESULT CMediaPumpPool::ReadRegistryValuesIfNeeded()
  849. {
  850. //
  851. // we don't want to access registry more than once. so we have this static
  852. // flag that helps us limit registry access
  853. //
  854. static bRegistryChecked = FALSE;
  855. if (TRUE == bRegistryChecked)
  856. {
  857. //
  858. // checked registry before. no need to do (or log) anything here
  859. //
  860. return S_OK;
  861. }
  862. //
  863. // we don't want to log until we know we will try to read registry
  864. //
  865. LOG((MSP_TRACE, "CMediaPumpPool::ReadRegistryValuesIfNeeded - enter"));
  866. //
  867. // whether we will succeed or fail, do not check the registry again
  868. //
  869. bRegistryChecked = TRUE;
  870. //
  871. // Open the registry key
  872. //
  873. HKEY hKey = 0;
  874. LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  875. MST_REGISTRY_PATH,
  876. 0,
  877. KEY_READ,
  878. &hKey);
  879. //
  880. // did we manage to open the key?
  881. //
  882. if( ERROR_SUCCESS != lResult )
  883. {
  884. LOG((MSP_WARN, "CPTUtil::ReadRegistryValuesIfNeeded - "
  885. "RegOpenKeyEx failed, returns E_FAIL"));
  886. return E_FAIL;
  887. }
  888. //
  889. // read the value
  890. //
  891. DWORD dwMaxFiltersPerPump = 0;
  892. DWORD dwDataSize = sizeof(DWORD);
  893. lResult = RegQueryValueEx(
  894. hKey,
  895. MAX_FILTERS_PER_PUMP_KEY,
  896. NULL,
  897. NULL,
  898. (LPBYTE) &dwMaxFiltersPerPump,
  899. &dwDataSize
  900. );
  901. //
  902. // don't need the key anymore
  903. //
  904. RegCloseKey(hKey);
  905. hKey = NULL;
  906. //
  907. // any luck reading the value?
  908. //
  909. if( ERROR_SUCCESS != lResult )
  910. {
  911. LOG((MSP_WARN, "CPTUtil::ReadRegistryValuesIfNeeded - RegQueryValueEx failed, return E_FAIL"));
  912. return E_FAIL;
  913. }
  914. //
  915. // got the value, keeping it.
  916. //
  917. m_dwMaxNumberOfFilterPerPump = dwMaxFiltersPerPump;
  918. LOG((MSP_TRACE,
  919. "CMediaPumpPool::ReadRegistryValuesIfNeeded - exit. MaxNumberOfFilterPerPump = %lx",
  920. m_dwMaxNumberOfFilterPerPump));
  921. return S_OK;
  922. }
  923. //////////////////////////////////////////////////////////////////////////////
  924. //
  925. // CMediaPumpPool::GetOptimalNumberOfPumps
  926. //
  927. // this function returns the number of pumps required to process all the
  928. // filters that are currently being handled, plus the filter that is about to
  929. // be registered
  930. //
  931. HRESULT CMediaPumpPool::GetOptimalNumberOfPumps(int *pnPumpsNeeded)
  932. {
  933. LOG((MSP_TRACE, "CMediaPumpPool::GetOptimalNumberOfPumps - enter"));
  934. //
  935. // if the argument is bad, it's a bug
  936. //
  937. if (IsBadWritePtr(pnPumpsNeeded, sizeof(int)))
  938. {
  939. LOG((MSP_ERROR,
  940. "CMediaPumpPool::GetOptimalNumberOfPumps - pnPumpsNeeded[%p] is bad",
  941. pnPumpsNeeded));
  942. TM_ASSERT(FALSE);
  943. return E_POINTER;
  944. }
  945. //
  946. // calculate the total number of service filters
  947. //
  948. int nTotalExistingPumps = m_aPumps.GetSize();
  949. //
  950. // start with one filter (to adjust for the filter we are adding)
  951. //
  952. int nTotalFilters = 1;
  953. for (int i = 0; i < nTotalExistingPumps; i++)
  954. {
  955. //
  956. // note that the number of filters we get could be slightly higher then
  957. // the real number, since the filters can be removed without involving
  958. // pump pool (and thus getting its critical section). this is ok --
  959. // the worst that will happen is that we will sometimes have more pumps
  960. // then we really need.
  961. //
  962. nTotalFilters += m_aPumps[i]->CountFilters();
  963. }
  964. //
  965. // calculated how many filters are being serviced
  966. //
  967. //
  968. // what is the max number of filters a pump can service
  969. //
  970. DWORD dwMaxNumberOfFilterPerPump = GetMaxNumberOfFiltersPerPump();
  971. //
  972. // find the number of pumps needed to service all our filters
  973. //
  974. *pnPumpsNeeded = nTotalFilters / dwMaxNumberOfFilterPerPump;
  975. //
  976. // if the number of filters is not evenly divisible by the max number of
  977. // filters serviced by a pump, we need to round up.
  978. //
  979. if ( 0 != (nTotalFilters % dwMaxNumberOfFilterPerPump) )
  980. {
  981. //
  982. // uneven divide, roundup adjustment is necessary
  983. //
  984. *pnPumpsNeeded += 1;
  985. }
  986. //
  987. // we have calculated the number of pumps needed to process all our filters
  988. //
  989. LOG((MSP_TRACE,
  990. "CMediaPumpPool::GetOptimalNumberOfPumps - exit. [%d] filters should be serviced by [%d] pump(s)",
  991. nTotalFilters, *pnPumpsNeeded));
  992. return S_OK;
  993. }
  994. /////////////////////////////////////////////////////////////////////////////
  995. //
  996. // CMediaPumpPool::PickThePumpToUse
  997. //
  998. // this method chooses the pump that should be used to service the new filter
  999. // the pump is picked based on the load and the number of pumps needed to
  1000. // service all pf the current filters
  1001. //
  1002. HRESULT CMediaPumpPool::PickThePumpToUse(int *pPumpToUse)
  1003. {
  1004. LOG((MSP_TRACE, "CMediaPumpPool::PickThePumpToUse - enter"));
  1005. //
  1006. // if the argument is bad, it's a bug
  1007. //
  1008. if (IsBadWritePtr(pPumpToUse, sizeof(int)))
  1009. {
  1010. LOG((MSP_ERROR, "CMediaPumpPool::PickThePumpToUse - pPumpToUse[%p] is bad", pPumpToUse));
  1011. TM_ASSERT(FALSE);
  1012. return E_POINTER;
  1013. }
  1014. //
  1015. // calculate the optimal number of pumps needed for the current number of filters
  1016. //
  1017. int nPumpsNeeded = 0;
  1018. HRESULT hr = GetOptimalNumberOfPumps(&nPumpsNeeded);
  1019. if (FAILED(hr))
  1020. {
  1021. LOG((MSP_ERROR,
  1022. "CMediaPumpPool::PickThePumpToUse - GetOptimalNumberOfPumps failed hr = [%lx]",
  1023. hr));
  1024. return hr;
  1025. }
  1026. //
  1027. // if we don't have enough pumps, create more
  1028. //
  1029. int nTotalExistingPumps = m_aPumps.GetSize();
  1030. if (nTotalExistingPumps < nPumpsNeeded)
  1031. {
  1032. //
  1033. // this is how many more pumps we need to create
  1034. //
  1035. int nNewPumpsToCreate = nPumpsNeeded - nTotalExistingPumps;
  1036. //
  1037. // we will never need to create more than one new pump at a time
  1038. //
  1039. TM_ASSERT(1 == nNewPumpsToCreate);
  1040. //
  1041. // special case if we currently don't have any pumps -- create one pump
  1042. // for each processor. this will help us scale on symmetric
  1043. // multiprocessor machines.
  1044. //
  1045. if (0 == nTotalExistingPumps)
  1046. {
  1047. //
  1048. // get the number of processors. according to documentation,
  1049. // GetSystemInfo cannot fail, so there is return code to check
  1050. //
  1051. SYSTEM_INFO SystemInfo;
  1052. GetSystemInfo(&SystemInfo);
  1053. //
  1054. // we will want to create at least as many new pumps as we have
  1055. // processors, but maybe more if needed
  1056. //
  1057. //
  1058. // note: we may also want to look at the affinity mask, it may
  1059. // tell us how many CPUs are actually used
  1060. //
  1061. int nNumberOfProcessors = SystemInfo.dwNumberOfProcessors;
  1062. if (nNewPumpsToCreate < nNumberOfProcessors)
  1063. {
  1064. nNewPumpsToCreate = SystemInfo.dwNumberOfProcessors;
  1065. }
  1066. }
  1067. //
  1068. // we now have all the information needed to create the pumps we need.
  1069. //
  1070. hr = CreatePumps(nNewPumpsToCreate);
  1071. if (FAILED(hr))
  1072. {
  1073. LOG((MSP_ERROR,
  1074. "CMediaPumpPool::PickThePumpToUse - CreatePumps failed hr = [%lx]",
  1075. hr));
  1076. return hr;
  1077. }
  1078. LOG((MSP_TRACE, "CMediaPumpPool::PickThePumpToUse - create [%d] pumps", nNewPumpsToCreate));
  1079. }
  1080. //
  1081. // walk trough the pumps (only use pumps starting from the first N pumps,
  1082. // N being the number of pumps needed to service the number of filters that
  1083. // we are servicing
  1084. //
  1085. nTotalExistingPumps = m_aPumps.GetSize();
  1086. int nLowestLoad = INT_MAX;
  1087. int nLowestLoadPumpIndex = -1;
  1088. for (int nPumpIndex = 0; nPumpIndex < nTotalExistingPumps; nPumpIndex++)
  1089. {
  1090. int nNumberOfFiltersAtPump = 0;
  1091. //
  1092. // how many filters is this pump serving?
  1093. //
  1094. nNumberOfFiltersAtPump = m_aPumps[nPumpIndex]->CountFilters();
  1095. //
  1096. // if the pump we are looking at has less load then any of the
  1097. // previously evaluated pumps, remember it. if we don't find anything
  1098. // better, this is what we will use.
  1099. //
  1100. if (nNumberOfFiltersAtPump < nLowestLoad)
  1101. {
  1102. nLowestLoadPumpIndex = nPumpIndex;
  1103. nLowestLoad = nNumberOfFiltersAtPump;
  1104. }
  1105. }
  1106. //
  1107. // we had to get something!
  1108. //
  1109. if (-1 == nLowestLoadPumpIndex)
  1110. {
  1111. LOG((MSP_ERROR,
  1112. "CMediaPumpPool::PickThePumpToUse - did not find a pump to use"));
  1113. //
  1114. // we have a bug -- need to investigate
  1115. //
  1116. TM_ASSERT(FALSE);
  1117. return E_UNEXPECTED;
  1118. }
  1119. //
  1120. // we found a pump to use
  1121. //
  1122. *pPumpToUse = nLowestLoadPumpIndex;
  1123. LOG((MSP_TRACE,
  1124. "CMediaPumpPool::PickThePumpToUse - finish. using pump %d, current load %d",
  1125. *pPumpToUse, nLowestLoad));
  1126. return S_OK;
  1127. }
  1128. //////////////////////////////////////////////////////////////////////////////
  1129. //
  1130. // Register: This delegates to the individual pumps, creating new ones as
  1131. // needed.
  1132. //
  1133. HRESULT CMediaPumpPool::Register(
  1134. IN CMediaTerminalFilter *pFilter,
  1135. IN HANDLE hWaitEvent
  1136. )
  1137. {
  1138. LOG((MSP_TRACE, "CMediaPumpPool::Register - enter"));
  1139. CLock lock(m_CritSection);
  1140. //
  1141. // find the pump with which to register filter
  1142. //
  1143. int nPumpToUse = 0;
  1144. HRESULT hr = PickThePumpToUse(&nPumpToUse);
  1145. if (FAILED(hr))
  1146. {
  1147. LOG((MSP_ERROR,
  1148. "CMediaPumpPool::Register - failed to find the pump to be used to service the new filter, hr = [%lx]",
  1149. hr));
  1150. return hr;
  1151. }
  1152. //
  1153. // just to be on the safe side, make sure the index we got makes sense
  1154. //
  1155. int nTotalPumps = m_aPumps.GetSize();
  1156. if (nTotalPumps - 1 < nPumpToUse)
  1157. {
  1158. LOG((MSP_ERROR,
  1159. "CMediaPumpPool::Register - PickThePumpToUse return bad pump index [%d]",
  1160. nPumpToUse));
  1161. TM_ASSERT(FALSE);
  1162. return E_UNEXPECTED;
  1163. }
  1164. //
  1165. // ok, all is well, register with the pump
  1166. //
  1167. hr = m_aPumps[nPumpToUse]->Register(pFilter, hWaitEvent);
  1168. if (FAILED(hr))
  1169. {
  1170. LOG((MSP_ERROR,
  1171. "CMediaPumpPool::Register - failed to register with pump [%d] at [%p]",
  1172. nPumpToUse, m_aPumps[nPumpToUse]));
  1173. return hr;
  1174. }
  1175. LOG((MSP_TRACE, "CMediaPumpPool::Register - finished"));
  1176. return S_OK;
  1177. }
  1178. //////////////////////////////////////////////////////////////////////////////
  1179. //
  1180. // UnRegister: Unregister filter. This delegates to the individual pumps
  1181. //
  1182. HRESULT CMediaPumpPool::UnRegister(
  1183. IN HANDLE hWaitEvent
  1184. )
  1185. {
  1186. LOG((MSP_TRACE, "CMediaPumpPool::UnRegister - enter"));
  1187. HRESULT hr = E_FAIL;
  1188. //
  1189. // All of this is done within a single critical section, to
  1190. // synchronize access to our array
  1191. //
  1192. CLock lock(m_CritSection);
  1193. //
  1194. // try to unregister from a pump thread in the array
  1195. //
  1196. int iSize = m_aPumps.GetSize();
  1197. for (int i = 0; i < iSize; i++ )
  1198. {
  1199. //
  1200. // Try to unregister from this pump thread.
  1201. //
  1202. hr = m_aPumps[i]->UnRegister(hWaitEvent);
  1203. //
  1204. // If succeeded unregistering from this pump, then we are done.
  1205. // Otherwise just try the next one.
  1206. //
  1207. if ( hr == S_OK )
  1208. {
  1209. LOG((MSP_TRACE,
  1210. "CMediaPumpPool::UnRegister - unregistered with media pump %d",
  1211. i));
  1212. break;
  1213. }
  1214. }
  1215. LOG((MSP_TRACE,
  1216. "CMediaPumpPool::UnRegister - exit. hr = 0x%08x", hr));
  1217. return hr;
  1218. }
  1219. //
  1220. // eof
  1221. //