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.

793 lines
23 KiB

  1. //------------------------------------------------------------------------------
  2. // File: OutputQ.cpp
  3. //
  4. // Desc: DirectShow base classes - implements COutputQueue class used by an
  5. // output pin which may sometimes want to queue output samples on a
  6. // separate thread and sometimes call Receive() directly on the input
  7. // pin.
  8. //
  9. // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved.
  10. //------------------------------------------------------------------------------
  11. #include <streams.h>
  12. //
  13. // COutputQueue Constructor :
  14. //
  15. // Determines if a thread is to be created and creates resources
  16. //
  17. // pInputPin - the downstream input pin we're queueing samples to
  18. //
  19. // phr - changed to a failure code if this function fails
  20. // (otherwise unchanges)
  21. //
  22. // bAuto - Ask pInputPin if it can block in Receive by calling
  23. // its ReceiveCanBlock method and create a thread if
  24. // it can block, otherwise not.
  25. //
  26. // bQueue - if bAuto == FALSE then we create a thread if and only
  27. // if bQueue == TRUE
  28. //
  29. // lBatchSize - work in batches of lBatchSize
  30. //
  31. // bBatchEact - Use exact batch sizes so don't send until the
  32. // batch is full or SendAnyway() is called
  33. //
  34. // lListSize - If we create a thread make the list of samples queued
  35. // to the thread have this size cache
  36. //
  37. // dwPriority - If we create a thread set its priority to this
  38. //
  39. COutputQueue::COutputQueue(
  40. IPin *pInputPin, // Pin to send stuff to
  41. HRESULT *phr, // 'Return code'
  42. BOOL bAuto, // Ask pin if queue or not
  43. BOOL bQueue, // Send through queue
  44. LONG lBatchSize, // Batch
  45. BOOL bBatchExact, // Batch exactly to BatchSize
  46. LONG lListSize,
  47. DWORD dwPriority,
  48. bool bFlushingOpt // flushing optimization
  49. ) : m_lBatchSize(lBatchSize),
  50. m_bBatchExact(bBatchExact && (lBatchSize > 1)),
  51. m_hThread(NULL),
  52. m_hSem(NULL),
  53. m_List(NULL),
  54. m_pPin(pInputPin),
  55. m_ppSamples(NULL),
  56. m_lWaiting(0),
  57. m_pInputPin(NULL),
  58. m_bSendAnyway(FALSE),
  59. m_nBatched(0),
  60. m_bFlushing(FALSE),
  61. m_bFlushed(TRUE),
  62. m_bFlushingOpt(bFlushingOpt),
  63. m_bTerminate(FALSE),
  64. m_hEventPop(NULL),
  65. m_hr(S_OK)
  66. {
  67. ASSERT(m_lBatchSize > 0);
  68. if (FAILED(*phr)) {
  69. return;
  70. }
  71. // Check the input pin is OK and cache its IMemInputPin interface
  72. *phr = pInputPin->QueryInterface(IID_IMemInputPin, (void **)&m_pInputPin);
  73. if (FAILED(*phr)) {
  74. return;
  75. }
  76. // See if we should ask the downstream pin
  77. if (bAuto) {
  78. HRESULT hr = m_pInputPin->ReceiveCanBlock();
  79. if (SUCCEEDED(hr)) {
  80. bQueue = hr == S_OK;
  81. }
  82. }
  83. // Create our sample batch
  84. m_ppSamples = new PMEDIASAMPLE[m_lBatchSize];
  85. if (m_ppSamples == NULL) {
  86. *phr = E_OUTOFMEMORY;
  87. return;
  88. }
  89. // If we're queueing allocate resources
  90. if (bQueue) {
  91. DbgLog((LOG_TRACE, 2, TEXT("Creating thread for output pin")));
  92. m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
  93. if (m_hSem == NULL) {
  94. DWORD dwError = GetLastError();
  95. *phr = AmHresultFromWin32(dwError);
  96. return;
  97. }
  98. m_List = new CSampleList(NAME("Sample Queue List"),
  99. lListSize,
  100. FALSE // No lock
  101. );
  102. if (m_List == NULL) {
  103. *phr = E_OUTOFMEMORY;
  104. return;
  105. }
  106. DWORD dwThreadId;
  107. m_hThread = CreateThread(NULL,
  108. 0,
  109. InitialThreadProc,
  110. (LPVOID)this,
  111. 0,
  112. &dwThreadId);
  113. if (m_hThread == NULL) {
  114. DWORD dwError = GetLastError();
  115. *phr = AmHresultFromWin32(dwError);
  116. return;
  117. }
  118. SetThreadPriority(m_hThread, dwPriority);
  119. } else {
  120. DbgLog((LOG_TRACE, 2, TEXT("Calling input pin directly - no thread")));
  121. }
  122. }
  123. //
  124. // COutputQueuee Destructor :
  125. //
  126. // Free all resources -
  127. //
  128. // Thread,
  129. // Batched samples
  130. //
  131. COutputQueue::~COutputQueue()
  132. {
  133. DbgLog((LOG_TRACE, 3, TEXT("COutputQueue::~COutputQueue")));
  134. /* Free our pointer */
  135. if (m_pInputPin != NULL) {
  136. m_pInputPin->Release();
  137. }
  138. if (m_hThread != NULL) {
  139. {
  140. CAutoLock lck(this);
  141. m_bTerminate = TRUE;
  142. m_hr = S_FALSE;
  143. NotifyThread();
  144. }
  145. DbgWaitForSingleObject(m_hThread);
  146. EXECUTE_ASSERT(CloseHandle(m_hThread));
  147. // The thread frees the samples when asked to terminate
  148. ASSERT(m_List->GetCount() == 0);
  149. delete m_List;
  150. } else {
  151. FreeSamples();
  152. }
  153. if (m_hSem != NULL) {
  154. EXECUTE_ASSERT(CloseHandle(m_hSem));
  155. }
  156. delete [] m_ppSamples;
  157. }
  158. //
  159. // Call the real thread proc as a member function
  160. //
  161. DWORD WINAPI COutputQueue::InitialThreadProc(LPVOID pv)
  162. {
  163. HRESULT hrCoInit = CAMThread::CoInitializeHelper();
  164. COutputQueue *pSampleQueue = (COutputQueue *)pv;
  165. DWORD dwReturn = pSampleQueue->ThreadProc();
  166. if(hrCoInit == S_OK) {
  167. CoUninitialize();
  168. }
  169. return dwReturn;
  170. }
  171. //
  172. // Thread sending the samples downstream :
  173. //
  174. // When there is nothing to do the thread sets m_lWaiting (while
  175. // holding the critical section) and then waits for m_hSem to be
  176. // set (not holding the critical section)
  177. //
  178. DWORD COutputQueue::ThreadProc()
  179. {
  180. while (TRUE) {
  181. BOOL bWait = FALSE;
  182. IMediaSample *pSample;
  183. LONG lNumberToSend; // Local copy
  184. NewSegmentPacket* ppacket;
  185. //
  186. // Get a batch of samples and send it if possible
  187. // In any case exit the loop if there is a control action
  188. // requested
  189. //
  190. {
  191. CAutoLock lck(this);
  192. while (TRUE) {
  193. if (m_bTerminate) {
  194. FreeSamples();
  195. return 0;
  196. }
  197. if (m_bFlushing) {
  198. FreeSamples();
  199. SetEvent(m_evFlushComplete);
  200. }
  201. // Get a sample off the list
  202. pSample = m_List->RemoveHead();
  203. // inform derived class we took something off the queue
  204. if (m_hEventPop) {
  205. //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
  206. SetEvent(m_hEventPop);
  207. }
  208. if (pSample != NULL &&
  209. !IsSpecialSample(pSample)) {
  210. // If its just a regular sample just add it to the batch
  211. // and exit the loop if the batch is full
  212. m_ppSamples[m_nBatched++] = pSample;
  213. if (m_nBatched == m_lBatchSize) {
  214. break;
  215. }
  216. } else {
  217. // If there was nothing in the queue and there's nothing
  218. // to send (either because there's nothing or the batch
  219. // isn't full) then prepare to wait
  220. if (pSample == NULL &&
  221. (m_bBatchExact || m_nBatched == 0)) {
  222. // Tell other thread to set the event when there's
  223. // something do to
  224. ASSERT(m_lWaiting == 0);
  225. m_lWaiting++;
  226. bWait = TRUE;
  227. } else {
  228. // We break out of the loop on SEND_PACKET unless
  229. // there's nothing to send
  230. if (pSample == SEND_PACKET && m_nBatched == 0) {
  231. continue;
  232. }
  233. if (pSample == NEW_SEGMENT) {
  234. // now we need the parameters - we are
  235. // guaranteed that the next packet contains them
  236. ppacket = (NewSegmentPacket *) m_List->RemoveHead();
  237. // we took something off the queue
  238. if (m_hEventPop) {
  239. //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
  240. SetEvent(m_hEventPop);
  241. }
  242. ASSERT(ppacket);
  243. }
  244. // EOS_PACKET falls through here and we exit the loop
  245. // In this way it acts like SEND_PACKET
  246. }
  247. break;
  248. }
  249. }
  250. if (!bWait) {
  251. // We look at m_nBatched from the client side so keep
  252. // it up to date inside the critical section
  253. lNumberToSend = m_nBatched; // Local copy
  254. m_nBatched = 0;
  255. }
  256. }
  257. // Wait for some more data
  258. if (bWait) {
  259. DbgWaitForSingleObject(m_hSem);
  260. continue;
  261. }
  262. // OK - send it if there's anything to send
  263. // We DON'T check m_bBatchExact here because either we've got
  264. // a full batch or we dropped through because we got
  265. // SEND_PACKET or EOS_PACKET - both of which imply we should
  266. // flush our batch
  267. if (lNumberToSend != 0) {
  268. long nProcessed;
  269. if (m_hr == S_OK) {
  270. ASSERT(!m_bFlushed);
  271. HRESULT hr = m_pInputPin->ReceiveMultiple(m_ppSamples,
  272. lNumberToSend,
  273. &nProcessed);
  274. /* Don't overwrite a flushing state HRESULT */
  275. CAutoLock lck(this);
  276. if (m_hr == S_OK) {
  277. m_hr = hr;
  278. }
  279. ASSERT(!m_bFlushed);
  280. }
  281. while (lNumberToSend != 0) {
  282. m_ppSamples[--lNumberToSend]->Release();
  283. }
  284. if (m_hr != S_OK) {
  285. // In any case wait for more data - S_OK just
  286. // means there wasn't an error
  287. DbgLog((LOG_ERROR, 2, TEXT("ReceiveMultiple returned %8.8X"),
  288. m_hr));
  289. }
  290. }
  291. // Check for end of stream
  292. if (pSample == EOS_PACKET) {
  293. // We don't send even end of stream on if we've previously
  294. // returned something other than S_OK
  295. // This is because in that case the pin which returned
  296. // something other than S_OK should have either sent
  297. // EndOfStream() or notified the filter graph
  298. if (m_hr == S_OK) {
  299. DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()")));
  300. HRESULT hr = m_pPin->EndOfStream();
  301. if (FAILED(hr)) {
  302. DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()")));
  303. }
  304. }
  305. }
  306. // Data from a new source
  307. if (pSample == RESET_PACKET) {
  308. m_hr = S_OK;
  309. SetEvent(m_evFlushComplete);
  310. }
  311. if (pSample == NEW_SEGMENT) {
  312. m_pPin->NewSegment(ppacket->tStart, ppacket->tStop, ppacket->dRate);
  313. delete ppacket;
  314. }
  315. }
  316. }
  317. // Send batched stuff anyway
  318. void COutputQueue::SendAnyway()
  319. {
  320. if (!IsQueued()) {
  321. // m_bSendAnyway is a private parameter checked in ReceiveMultiple
  322. m_bSendAnyway = TRUE;
  323. LONG nProcessed;
  324. ReceiveMultiple(NULL, 0, &nProcessed);
  325. m_bSendAnyway = FALSE;
  326. } else {
  327. CAutoLock lck(this);
  328. QueueSample(SEND_PACKET);
  329. NotifyThread();
  330. }
  331. }
  332. void
  333. COutputQueue::NewSegment(
  334. REFERENCE_TIME tStart,
  335. REFERENCE_TIME tStop,
  336. double dRate)
  337. {
  338. if (!IsQueued()) {
  339. if (S_OK == m_hr) {
  340. if (m_bBatchExact) {
  341. SendAnyway();
  342. }
  343. m_pPin->NewSegment(tStart, tStop, dRate);
  344. }
  345. } else {
  346. if (m_hr == S_OK) {
  347. //
  348. // we need to queue the new segment to appear in order in the
  349. // data, but we need to pass parameters to it. Rather than
  350. // take the hit of wrapping every single sample so we can tell
  351. // special ones apart, we queue special pointers to indicate
  352. // special packets, and we guarantee (by holding the
  353. // critical section) that the packet immediately following a
  354. // NEW_SEGMENT value is a NewSegmentPacket containing the
  355. // parameters.
  356. NewSegmentPacket * ppack = new NewSegmentPacket;
  357. if (ppack == NULL) {
  358. return;
  359. }
  360. ppack->tStart = tStart;
  361. ppack->tStop = tStop;
  362. ppack->dRate = dRate;
  363. CAutoLock lck(this);
  364. QueueSample(NEW_SEGMENT);
  365. QueueSample( (IMediaSample*) ppack);
  366. NotifyThread();
  367. }
  368. }
  369. }
  370. //
  371. // End of Stream is queued to output device
  372. //
  373. void COutputQueue::EOS()
  374. {
  375. CAutoLock lck(this);
  376. if (!IsQueued()) {
  377. if (m_bBatchExact) {
  378. SendAnyway();
  379. }
  380. if (m_hr == S_OK) {
  381. DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()")));
  382. m_bFlushed = FALSE;
  383. HRESULT hr = m_pPin->EndOfStream();
  384. if (FAILED(hr)) {
  385. DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()")));
  386. }
  387. }
  388. } else {
  389. if (m_hr == S_OK) {
  390. m_bFlushed = FALSE;
  391. QueueSample(EOS_PACKET);
  392. NotifyThread();
  393. }
  394. }
  395. }
  396. //
  397. // Flush all the samples in the queue
  398. //
  399. void COutputQueue::BeginFlush()
  400. {
  401. if (IsQueued()) {
  402. {
  403. CAutoLock lck(this);
  404. // block receives -- we assume this is done by the
  405. // filter in which we are a component
  406. // discard all queued data
  407. m_bFlushing = TRUE;
  408. // Make sure we discard all samples from now on
  409. if (m_hr == S_OK) {
  410. m_hr = S_FALSE;
  411. }
  412. // Optimize so we don't keep calling downstream all the time
  413. if (m_bFlushed && m_bFlushingOpt) {
  414. return;
  415. }
  416. // Make sure we really wait for the flush to complete
  417. m_evFlushComplete.Reset();
  418. NotifyThread();
  419. }
  420. // pass this downstream
  421. m_pPin->BeginFlush();
  422. } else {
  423. // pass downstream first to avoid deadlocks
  424. m_pPin->BeginFlush();
  425. CAutoLock lck(this);
  426. // discard all queued data
  427. m_bFlushing = TRUE;
  428. // Make sure we discard all samples from now on
  429. if (m_hr == S_OK) {
  430. m_hr = S_FALSE;
  431. }
  432. }
  433. }
  434. //
  435. // leave flush mode - pass this downstream
  436. void COutputQueue::EndFlush()
  437. {
  438. {
  439. CAutoLock lck(this);
  440. ASSERT(m_bFlushing);
  441. if (m_bFlushingOpt && m_bFlushed && IsQueued()) {
  442. m_bFlushing = FALSE;
  443. m_hr = S_OK;
  444. return;
  445. }
  446. }
  447. // sync with pushing thread -- done in BeginFlush
  448. // ensure no more data to go downstream -- done in BeginFlush
  449. //
  450. // Because we are synching here there is no need to hold the critical
  451. // section (in fact we'd deadlock if we did!)
  452. if (IsQueued()) {
  453. m_evFlushComplete.Wait();
  454. } else {
  455. FreeSamples();
  456. }
  457. // Be daring - the caller has guaranteed no samples will arrive
  458. // before EndFlush() returns
  459. m_bFlushing = FALSE;
  460. m_bFlushed = TRUE;
  461. // call EndFlush on downstream pins
  462. m_pPin->EndFlush();
  463. m_hr = S_OK;
  464. }
  465. // COutputQueue::QueueSample
  466. //
  467. // private method to Send a sample to the output queue
  468. // The critical section MUST be held when this is called
  469. void COutputQueue::QueueSample(IMediaSample *pSample)
  470. {
  471. if (NULL == m_List->AddTail(pSample)) {
  472. if (!IsSpecialSample(pSample)) {
  473. pSample->Release();
  474. }
  475. }
  476. }
  477. //
  478. // COutputQueue::Receive()
  479. //
  480. // Send a single sample by the multiple sample route
  481. // (NOTE - this could be optimized if necessary)
  482. //
  483. // On return the sample will have been Release()'d
  484. //
  485. HRESULT COutputQueue::Receive(IMediaSample *pSample)
  486. {
  487. LONG nProcessed;
  488. return ReceiveMultiple(&pSample, 1, &nProcessed);
  489. }
  490. //
  491. // COutputQueue::ReceiveMultiple()
  492. //
  493. // Send a set of samples to the downstream pin
  494. //
  495. // ppSamples - array of samples
  496. // nSamples - how many
  497. // nSamplesProcessed - How many were processed
  498. //
  499. // On return all samples will have been Release()'d
  500. //
  501. HRESULT COutputQueue::ReceiveMultiple (
  502. IMediaSample **ppSamples,
  503. long nSamples,
  504. long *nSamplesProcessed)
  505. {
  506. CAutoLock lck(this);
  507. // Either call directly or queue up the samples
  508. if (!IsQueued()) {
  509. // If we already had a bad return code then just return
  510. if (S_OK != m_hr) {
  511. // If we've never received anything since the last Flush()
  512. // and the sticky return code is not S_OK we must be
  513. // flushing
  514. // ((!A || B) is equivalent to A implies B)
  515. ASSERT(!m_bFlushed || m_bFlushing);
  516. // We're supposed to Release() them anyway!
  517. *nSamplesProcessed = 0;
  518. for (int i = 0; i < nSamples; i++) {
  519. DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (direct) : Discarding %d samples code 0x%8.8X"),
  520. nSamples, m_hr));
  521. ppSamples[i]->Release();
  522. }
  523. return m_hr;
  524. }
  525. //
  526. // If we're flushing the sticky return code should be S_FALSE
  527. //
  528. ASSERT(!m_bFlushing);
  529. m_bFlushed = FALSE;
  530. ASSERT(m_nBatched < m_lBatchSize);
  531. ASSERT(m_nBatched == 0 || m_bBatchExact);
  532. // Loop processing the samples in batches
  533. LONG iLost = 0;
  534. for (long iDone = 0;
  535. iDone < nSamples || (m_nBatched != 0 && m_bSendAnyway);
  536. ) {
  537. //pragma message (REMIND("Implement threshold scheme"))
  538. ASSERT(m_nBatched < m_lBatchSize);
  539. if (iDone < nSamples) {
  540. m_ppSamples[m_nBatched++] = ppSamples[iDone++];
  541. }
  542. if (m_nBatched == m_lBatchSize ||
  543. nSamples == 0 && (m_bSendAnyway || !m_bBatchExact)) {
  544. LONG nDone;
  545. DbgLog((LOG_TRACE, 4, TEXT("Batching %d samples"),
  546. m_nBatched));
  547. if (m_hr == S_OK) {
  548. m_hr = m_pInputPin->ReceiveMultiple(m_ppSamples,
  549. m_nBatched,
  550. &nDone);
  551. } else {
  552. nDone = 0;
  553. }
  554. iLost += m_nBatched - nDone;
  555. for (LONG i = 0; i < m_nBatched; i++) {
  556. m_ppSamples[i]->Release();
  557. }
  558. m_nBatched = 0;
  559. }
  560. }
  561. *nSamplesProcessed = iDone - iLost;
  562. if (*nSamplesProcessed < 0) {
  563. *nSamplesProcessed = 0;
  564. }
  565. return m_hr;
  566. } else {
  567. /* We're sending to our thread */
  568. if (m_hr != S_OK) {
  569. *nSamplesProcessed = 0;
  570. DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (queued) : Discarding %d samples code 0x%8.8X"),
  571. nSamples, m_hr));
  572. for (int i = 0; i < nSamples; i++) {
  573. ppSamples[i]->Release();
  574. }
  575. return m_hr;
  576. }
  577. m_bFlushed = FALSE;
  578. for (long i = 0; i < nSamples; i++) {
  579. QueueSample(ppSamples[i]);
  580. }
  581. *nSamplesProcessed = nSamples;
  582. if (!m_bBatchExact ||
  583. m_nBatched + m_List->GetCount() >= m_lBatchSize) {
  584. NotifyThread();
  585. }
  586. return S_OK;
  587. }
  588. }
  589. // Get ready for new data - cancels sticky m_hr
  590. void COutputQueue::Reset()
  591. {
  592. if (!IsQueued()) {
  593. m_hr = S_OK;
  594. } else {
  595. CAutoLock lck(this);
  596. QueueSample(RESET_PACKET);
  597. NotifyThread();
  598. m_evFlushComplete.Wait();
  599. }
  600. }
  601. // Remove and Release() all queued and Batched samples
  602. void COutputQueue::FreeSamples()
  603. {
  604. CAutoLock lck(this);
  605. if (IsQueued()) {
  606. while (TRUE) {
  607. IMediaSample *pSample = m_List->RemoveHead();
  608. // inform derived class we took something off the queue
  609. if (m_hEventPop) {
  610. //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
  611. SetEvent(m_hEventPop);
  612. }
  613. if (pSample == NULL) {
  614. break;
  615. }
  616. if (!IsSpecialSample(pSample)) {
  617. pSample->Release();
  618. } else {
  619. if (pSample == NEW_SEGMENT) {
  620. // Free NEW_SEGMENT packet
  621. NewSegmentPacket *ppacket =
  622. (NewSegmentPacket *) m_List->RemoveHead();
  623. // inform derived class we took something off the queue
  624. if (m_hEventPop) {
  625. //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
  626. SetEvent(m_hEventPop);
  627. }
  628. ASSERT(ppacket != NULL);
  629. delete ppacket;
  630. }
  631. }
  632. }
  633. }
  634. for (int i = 0; i < m_nBatched; i++) {
  635. m_ppSamples[i]->Release();
  636. }
  637. m_nBatched = 0;
  638. }
  639. // Notify the thread if there is something to do
  640. //
  641. // The critical section MUST be held when this is called
  642. void COutputQueue::NotifyThread()
  643. {
  644. // Optimize - no need to signal if it's not waiting
  645. ASSERT(IsQueued());
  646. if (m_lWaiting) {
  647. ReleaseSemaphore(m_hSem, m_lWaiting, NULL);
  648. m_lWaiting = 0;
  649. }
  650. }
  651. // See if there's any work to do
  652. // Returns
  653. // TRUE if there is nothing on the queue and nothing in the batch
  654. // and all data has been sent
  655. // FALSE otherwise
  656. //
  657. BOOL COutputQueue::IsIdle()
  658. {
  659. CAutoLock lck(this);
  660. // We're idle if
  661. // there is no thread (!IsQueued()) OR
  662. // the thread is waiting for more work (m_lWaiting != 0)
  663. // AND
  664. // there's nothing in the current batch (m_nBatched == 0)
  665. if (IsQueued() && m_lWaiting == 0 || m_nBatched != 0) {
  666. return FALSE;
  667. } else {
  668. // If we're idle it shouldn't be possible for there
  669. // to be anything on the work queue
  670. ASSERT(!IsQueued() || m_List->GetCount() == 0);
  671. return TRUE;
  672. }
  673. }
  674. void COutputQueue::SetPopEvent(HANDLE hEvent)
  675. {
  676. m_hEventPop = hEvent;
  677. }